/** * ObjectUI * Copyright (c) 2024-present ObjectStack Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * @object-ui/types - Spec Report Bridge * * This module re-exports the authoritative `Report` protocol types defined in * `@objectstack/spec` (UI Protocol). It is intentionally separated from the * legacy `reports.ts` module so we can introduce spec compliance without * breaking existing consumers. * * ## Two layers, on purpose * * 1. **Definition layer (this file, `Spec*` prefixed):** What a Report *is* — * the data shape that survives storage, transport, AI generation, and * cross-stack reuse. Mirrors `@objectstack/spec` exactly. * * 2. **Presentation layer (`reports.ts`, kept as `ReportSchema`):** How the * ObjectUI runtime *renders* a report — sections, toolbar config, schedule * UI, conditional formatting, export buttons. These are ObjectUI-specific * UX enhancements that the protocol does not (and should not) prescribe. * * ## Why both * * - JSON authored against the spec must render in ObjectUI without rewriting. * - ObjectUI can still ship richer UX (sections, schedules, export presets) * without polluting the protocol. * - A converter (`specReportToPresentation`) lets the legacy renderer keep * working while we migrate to spec-native rendering. * * ## Aggregation naming * * The UI spec uses `aggregate: 'unique'` while the data layer (ObjectQL, * `@objectstack/spec/data`) uses `count_distinct`. Use {@link mapAggregateToQL} * when translating a Report column into an ObjectQL `AggregationNode`. */ import type { Report as SpecReportType_, ReportInput as SpecReportInputType_, ReportColumn as SpecReportColumnType_, ReportColumnInput as SpecReportColumnInputType_, ReportGrouping as SpecReportGroupingType_, ReportGroupingInput as SpecReportGroupingInputType_, ReportChart as SpecReportChartType_, ReportChartInput as SpecReportChartInputType_ } from '@objectstack/spec/ui'; export type SpecReport = SpecReportType_; export type SpecReportInput = SpecReportInputType_; export type SpecReportColumn = SpecReportColumnType_; export type SpecReportColumnInput = SpecReportColumnInputType_; export type SpecReportGrouping = SpecReportGroupingType_; export type SpecReportGroupingInput = SpecReportGroupingInputType_; export type SpecReportChart = SpecReportChartType_; export type SpecReportChartInput = SpecReportChartInputType_; /** * The four report types defined by the UI Protocol. * * - `tabular` — flat list, no grouping (think: "filtered SELECT") * - `summary` — row-wise grouping with aggregations (a.k.a. grouped report) * - `matrix` — row × column pivot (cross-tab) * - `joined` — multiple independent report blocks stacked vertically */ export type SpecReportTypeName = 'tabular' | 'summary' | 'matrix' | 'joined'; /** * Aggregation enum as it appears in the *UI* Report spec * (`@objectstack/spec/ui` Report.columns[].aggregate). * * Note that this intentionally differs from the data-layer `AggregationFunction` * enum used in ObjectQL — see {@link mapAggregateToQL}. */ export type SpecReportAggregate = 'sum' | 'avg' | 'max' | 'min' | 'count' | 'unique'; /** * Date granularity for time-based grouping * (`Report.groupingsDown[].dateGranularity`). */ export type SpecReportDateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year'; export declare const SpecReportSchema: import("zod").ZodObject<{ _lock: import("zod").ZodOptional>; _lockReason: import("zod").ZodOptional; _lockSource: import("zod").ZodOptional>; _provenance: import("zod").ZodOptional>; _packageId: import("zod").ZodOptional; _packageVersion: import("zod").ZodOptional; _lockDocsUrl: import("zod").ZodOptional; name: import("zod").ZodString; label: import("zod").ZodString; description: import("zod").ZodOptional; type: import("zod").ZodDefault>; dataset: import("zod").ZodOptional; rows: import("zod").ZodOptional>; columns: import("zod").ZodOptional>; values: import("zod").ZodOptional>; runtimeFilter: import("zod").ZodOptional>>; drilldown: import("zod").ZodDefault; chart: import("zod").ZodOptional; title: import("zod").ZodOptional; subtitle: import("zod").ZodOptional; description: import("zod").ZodOptional; series: import("zod").ZodOptional; type: import("zod").ZodOptional>; color: import("zod").ZodOptional; stack: import("zod").ZodOptional; yAxis: import("zod").ZodDefault>; variant: import("zod").ZodOptional>>; dashArray: import("zod").ZodOptional; opacity: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>>; colors: import("zod").ZodOptional>; height: import("zod").ZodOptional; showLegend: import("zod").ZodDefault; showDataLabels: import("zod").ZodDefault; annotations: import("zod").ZodOptional>; axis: import("zod").ZodDefault>; value: import("zod").ZodUnion; endValue: import("zod").ZodOptional>; color: import("zod").ZodOptional; label: import("zod").ZodOptional; style: import("zod").ZodDefault>; }, import("zod/v4/core").$strip>>>; interaction: import("zod").ZodOptional; zoom: import("zod").ZodDefault; brush: import("zod").ZodDefault; clickAction: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; aria: import("zod").ZodOptional; ariaDescribedBy: import("zod").ZodOptional; role: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; xAxis: import("zod").ZodString; yAxis: import("zod").ZodString; groupBy: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; aria: import("zod").ZodOptional; ariaDescribedBy: import("zod").ZodOptional; role: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; performance: import("zod").ZodOptional; virtualScroll: import("zod").ZodOptional; itemHeight: import("zod").ZodOptional; overscan: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; cacheStrategy: import("zod").ZodOptional>; prefetch: import("zod").ZodOptional; pageSize: import("zod").ZodOptional; debounceMs: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; blocks: import("zod").ZodOptional>>>; protection: import("zod").ZodOptional; reason: import("zod").ZodString; docsUrl: import("zod").ZodOptional; }, import("zod/v4/core").$strict>>; }, import("zod/v4/core").$strip>; export declare const SpecReportColumnSchema: import("zod").ZodObject<{ field: import("zod").ZodString; label: import("zod").ZodOptional; aggregate: import("zod").ZodOptional>; responsive: import("zod").ZodOptional>; hiddenOn: import("zod").ZodOptional>>; columns: import("zod").ZodOptional; sm: import("zod").ZodOptional; md: import("zod").ZodOptional; lg: import("zod").ZodOptional; xl: import("zod").ZodOptional; "2xl": import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; order: import("zod").ZodOptional; sm: import("zod").ZodOptional; md: import("zod").ZodOptional; lg: import("zod").ZodOptional; xl: import("zod").ZodOptional; "2xl": import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; }, import("zod/v4/core").$strip>>; }, import("zod/v4/core").$strip>; export declare const SpecReportGroupingSchema: import("zod").ZodObject<{ field: import("zod").ZodString; sortOrder: import("zod").ZodDefault>; dateGranularity: import("zod").ZodOptional>; }, import("zod/v4/core").$strip>; export declare const SpecReportChartSchema: import("zod").ZodObject<{ type: import("zod").ZodEnum<{ bar: "bar"; line: "line"; pie: "pie"; area: "area"; scatter: "scatter"; "horizontal-bar": "horizontal-bar"; column: "column"; donut: "donut"; funnel: "funnel"; treemap: "treemap"; sankey: "sankey"; gauge: "gauge"; "solid-gauge": "solid-gauge"; metric: "metric"; kpi: "kpi"; bullet: "bullet"; radar: "radar"; table: "table"; pivot: "pivot"; }>; title: import("zod").ZodOptional; subtitle: import("zod").ZodOptional; description: import("zod").ZodOptional; series: import("zod").ZodOptional; type: import("zod").ZodOptional>; color: import("zod").ZodOptional; stack: import("zod").ZodOptional; yAxis: import("zod").ZodDefault>; variant: import("zod").ZodOptional>>; dashArray: import("zod").ZodOptional; opacity: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>>; colors: import("zod").ZodOptional>; height: import("zod").ZodOptional; showLegend: import("zod").ZodDefault; showDataLabels: import("zod").ZodDefault; annotations: import("zod").ZodOptional>; axis: import("zod").ZodDefault>; value: import("zod").ZodUnion; endValue: import("zod").ZodOptional>; color: import("zod").ZodOptional; label: import("zod").ZodOptional; style: import("zod").ZodDefault>; }, import("zod/v4/core").$strip>>>; interaction: import("zod").ZodOptional; zoom: import("zod").ZodDefault; brush: import("zod").ZodDefault; clickAction: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; aria: import("zod").ZodOptional; ariaDescribedBy: import("zod").ZodOptional; role: import("zod").ZodOptional; }, import("zod/v4/core").$strip>>; xAxis: import("zod").ZodString; yAxis: import("zod").ZodString; groupBy: import("zod").ZodOptional; }, import("zod/v4/core").$strip>; export declare const SpecReportTypeEnum: import("zod").ZodEnum<{ summary: "summary"; tabular: "tabular"; matrix: "matrix"; joined: "joined"; }>; /** * Spec factory helper (`Report.create`). * Validates the input against the spec and returns a typed `SpecReport`. */ export declare const SpecReport: { readonly create: (config: SpecReportInputType_) => SpecReportType_; }; /** * The aggregation enum used by the ObjectQL data layer. * Kept as a string union (rather than importing the value from * `@objectstack/spec/data`) so this types-only package stays * runtime-dependency-free. */ export type QLAggregationFunction = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct' | 'array_agg' | 'string_agg'; /** * Translate a UI-layer aggregate name into the corresponding ObjectQL * `AggregationFunction`. The only non-trivial mapping is `unique → count_distinct`. * * @example * mapAggregateToQL('unique') // 'count_distinct' * mapAggregateToQL('sum') // 'sum' */ export declare function mapAggregateToQL(aggregate: SpecReportAggregate): QLAggregationFunction; export interface LegacyReportPresentationLike { type: 'report'; title?: string; description?: string; reportType?: 'tabular' | 'summary' | 'matrix'; fields?: Array<{ name: string; label?: string; aggregation?: 'sum' | 'avg' | 'min' | 'max' | 'count' | 'distinct'; }>; groupBy?: Array<{ field: string; label?: string; sort?: 'asc' | 'desc'; dateGranularity?: SpecReportDateGranularity; }>; } /** * Convert a spec `Report` into the legacy presentation `ReportSchema` so the * existing `ReportRenderer`/`ReportViewer` can render it during migration. * * Since the ADR-0021 single-form cutover (`@objectstack/spec` 9.0) a report is * **dataset-bound**: `rows` (dimension names) and `values` (measure names) are * plain `string[]` that reference the dataset's semantic layer — the per-column * `label` / `aggregate` and per-grouping `sortOrder` / `dateGranularity` now * live in the dataset definition, not the report. This conversion is therefore * **lossy by construction**: it maps the names through and leaves label / * aggregation / sort / granularity for the dataset-aware renderer to resolve. */ export declare function specReportToPresentation(report: SpecReport): LegacyReportPresentationLike; /** * Type guard: does this object look like a spec `Report` (vs. a legacy * presentation `ReportSchema`)? * * Heuristic (spec 9.0, dataset-bound): spec reports carry `name` + a report * `type` and are **not** the legacy presentation shape (which carries * `type: 'report'` + `fields`). A non-joined spec report references a `dataset`; * a joined one carries `blocks`. We accept any of those discriminators. */ export declare function isSpecReport(value: unknown): value is SpecReport; /** * UI-layer extension for `type: 'joined'` reports. * * The upstream spec (`@objectstack/spec`) declares the `'joined'` report type * but does not yet define how the constituent blocks are carried in the JSON. * ObjectUI bridges that gap with a `blocks` field on the report: each block is * a fully self-contained `SpecReport` rendered independently (its own data * fetch, aggregations, drill), stacked vertically. * * Semantics: * - `report.filter` is merged into every block as a logical `$and` (block * filters take precedence on key collisions — they're the "more specific" * constraint by convention). This lets a top-level report-wide filter * (e.g. "owner = me") flow down to all blocks without repetition. * - Each block keeps its own `objectName`, so blocks may query different * objects (e.g. new customers + churned customers + silent customers). * - `actionRunner`, `dataSource`, `drillView`, `drillOpenIn` propagate * uniformly so any block's drill behaves like a standalone report. * * Status: forward-compatible. When the upstream spec adopts a `blocks` field * the only churn here will be replacing this interface with a re-export. */ /** * A single block inside a `type: 'joined'` report. * * The block IS a (constrained) report — its `columns`, `groupingsDown`, * `groupingsAcross`, `filter`, `chart` etc. are merged with the block's own * `objectName` (or the joined container's `objectName` as fallback) at render * time. Blocks cannot themselves be `joined` (no recursion). */ export interface JoinedReportBlock { /** Stable name within the joined report — used for React keys + drill scoping. */ name: string; /** Display label rendered above the block. Falls back to `name`. */ label?: string | { default: string; translations?: Record; }; /** Optional description rendered below the label. */ description?: string | { default: string; translations?: Record; }; /** Block report type. `joined` is excluded — no recursion. Defaults to `tabular`. */ type?: 'tabular' | 'summary' | 'matrix'; /** Object queried by this block. Defaults to the container's `objectName`. */ objectName?: string; /** Columns to display / aggregate. Same shape as `Report.columns`. */ columns: Array<{ field: string; label?: string; aggregate?: string; [k: string]: unknown; }>; /** Row groupings. */ groupingsDown?: Array<{ field: string; sortOrder?: 'asc' | 'desc'; dateGranularity?: string; [k: string]: unknown; }>; /** Column groupings — only meaningful when `type: matrix`. */ groupingsAcross?: Array<{ field: string; sortOrder?: 'asc' | 'desc'; dateGranularity?: string; [k: string]: unknown; }>; /** Block-specific filter, ANDed with the container filter at render time. */ filter?: Record; /** Optional inline chart configuration. */ chart?: Record; /** Forward-compatibility escape hatch. */ [k: string]: unknown; } /** * A `SpecReport` of `type: 'joined'` carrying its constituent blocks. * Use this as the schema input to `ReportRenderer` (dataset-bound blocks render * through `DatasetReportRenderer`). */ export type JoinedSpecReport = SpecReport & { type: 'joined'; blocks: JoinedReportBlock[]; }; /** Type guard for joined reports with a `blocks` array. */ export declare function isJoinedSpecReport(value: unknown): value is JoinedSpecReport; //# sourceMappingURL=spec-report.d.ts.map