@toclocoinc/lattice-grid 1.16.0 → 1.18.0

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/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.16.0, type declarations
2
+ * Lattice Grid 1.18.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -610,6 +610,20 @@ export interface Column {
610
610
  pivot?: { enabled?: boolean; index?: number } | boolean;
611
611
  /** The reduction shown in the totals row and in group footers. */
612
612
  total?: TotalName | TotalFn;
613
+ /**
614
+ * The reduction for group subtotals — group footers, tree-node rollups and
615
+ * pivot cells — where it should differ from the grand total. Overrides
616
+ * `total` for those scopes only; when omitted the column's `total` applies to
617
+ * both. Lets a column average within each group while the grand total sums,
618
+ * for example (BACKLOG-0000726).
619
+ */
620
+ groupTotal?: TotalName | TotalFn;
621
+ /**
622
+ * The reduction for the pinned grand-total row, where it should differ from
623
+ * the group subtotals. Overrides `total` for the grand total only; when
624
+ * omitted the column's `total` applies (BACKLOG-0000726).
625
+ */
626
+ grandTotal?: TotalName | TotalFn;
613
627
  /**
614
628
  * A value the grid maintains about this column's own history, rather than a
615
629
  * field in the data. `{of: 'price', kind: 'delta'}`, or the bare kind to
@@ -688,6 +702,16 @@ export interface ResolvedColumn {
688
702
  group: { enabled: boolean; index: number; explode: boolean };
689
703
  pivot: { enabled: boolean; index: number };
690
704
  total: TotalName | TotalFn | null;
705
+ /**
706
+ * The group-subtotal override, or null when group subtotals follow `total`
707
+ * (BACKLOG-0000726).
708
+ */
709
+ groupTotal: TotalName | TotalFn | null;
710
+ /**
711
+ * The grand-total override, or null when the grand total follows `total`
712
+ * (BACKLOG-0000726).
713
+ */
714
+ grandTotal: TotalName | TotalFn | null;
691
715
  layout: ColumnLayoutSpec;
692
716
  header: ColumnHeaderSpec;
693
717
  export: ColumnExportSpec;
@@ -779,6 +803,41 @@ export interface IngestConfig {
779
803
  */
780
804
  retainSource?: boolean;
781
805
 
806
+ /**
807
+ * Release the caller's row objects from the *source layer* once the column
808
+ * store has been built, so the columns become the sole resident copy of the
809
+ * data. Default `false`, which keeps today's behaviour.
810
+ *
811
+ * `retainSource:false` stops the {@link https://en.wikipedia.org/wiki/Column-oriented_DBMS column store}
812
+ * from holding the caller's objects, but the memory source and the grid config
813
+ * still retain the supplied array by reference — so the objects stay alive and
814
+ * the resident footprint does not actually fall. This flag closes that gap: it
815
+ * clears `MemorySource`'s retained array and drops the array from the grid
816
+ * config, leaving nothing on the heap but the packed columns. That is where
817
+ * the large reduction comes from (roughly an order of magnitude at a million
818
+ * rows), not from `retainSource` on its own.
819
+ *
820
+ * Implies `retainSource:false`: dropping the caller's objects while the store
821
+ * still expects to read through them would leave the source with no data at
822
+ * all, so setting this on forces the store to reconstruct rows from columns.
823
+ * Every read is therefore served from the columns — `at()`, `byKey()`,
824
+ * `get()`, `value()`, `values()`, filtering, sorting, grouping, totals and
825
+ * export are all unaffected in their values. What changes is the same three
826
+ * identity behaviours `retainSource:false` documents: `rows.data()` returns
827
+ * freshly reconstructed objects (so `row === sourceObject` no longer holds), a
828
+ * custom renderer reaching for `row.sourceObject` gets a reconstruction, and
829
+ * equality against a row becomes value-based.
830
+ *
831
+ * One consumer cannot be served from the columns: an *impure computed column*
832
+ * (a shadow, or a rank/positional column) is deliberately never materialised
833
+ * into the store, so its handle is built by reading the source objects. Under
834
+ * `dropSourceRows` those objects are gone, so such a column reduces over
835
+ * nothing and warns once rather than returning a silently wrong figure. Do not
836
+ * enable `dropSourceRows` on a grid that sorts, filters, groups or totals on a
837
+ * shadow or a positional column.
838
+ */
839
+ dropSourceRows?: boolean;
840
+
782
841
  /**
783
842
  * Columnize `stream`-source ingest on a Worker so a large load does not block
784
843
  * the main thread. Default `false`. When on, an arriving chunk that clears
@@ -1158,6 +1217,21 @@ export interface GridConfig {
1158
1217
  */
1159
1218
  cornerRadius?: boolean | number | string;
1160
1219
 
1220
+ /**
1221
+ * Shade alternate data rows (zebra striping).
1222
+ *
1223
+ * Off by default, and strictly opt-in: an existing grid must look exactly the
1224
+ * same on upgrade. When `true`, every other data row takes the theme's
1225
+ * `--lattice-surface-alt` background, which every palette already defines, so
1226
+ * dark, high-contrast and terminal stripe correctly without extra work.
1227
+ *
1228
+ * Parity follows the row's *logical* index, not its position in the DOM, so a
1229
+ * row keeps its stripe across a scroll even though the rows are recycled.
1230
+ * Structural rows — group headings, group footers and the grand total — are
1231
+ * never striped, and both selection and hover still win over the stripe.
1232
+ */
1233
+ stripedRows?: boolean;
1234
+
1161
1235
  /**
1162
1236
  * Show a bar above the column headings for filtering columns by tag.
1163
1237
  *
@@ -1312,7 +1386,12 @@ export interface GridConfig {
1312
1386
  * only the sort, filter and menu controls inside them.
1313
1387
  */
1314
1388
  showHeader?: boolean;
1315
- /** Header height in pixels. */
1389
+ /**
1390
+ * Header height in pixels. Omitted, the header takes its height from the
1391
+ * density-scaled `--lattice-header-height` token, so `density` sizes the
1392
+ * header as it sizes the rows. A number names one explicitly and outranks the
1393
+ * token.
1394
+ */
1316
1395
  headerHeight?: number;
1317
1396
  /** How many rows to render beyond the viewport. More costs memory and
1318
1397
  * smooths fast scrolling; fewer is lighter and can show a gap. */
@@ -1562,9 +1641,11 @@ export interface GridConfig {
1562
1641
  * Keep the enclosing group headings pinned above the viewport while
1563
1642
  * scrolling inside a group.
1564
1643
  *
1565
- * On by default, stacking at most two. `false` turns it off; a number, or
1566
- * `{ depth }`, sets how many may stack: each costs a row of viewport, so a
1567
- * deep grouping would otherwise spend the screen describing itself.
1644
+ * Off by default — a deliberate product default; sticky group headers are
1645
+ * opt-in. `true` turns it on, stacking at most two; a number, or `{ depth }`,
1646
+ * sets how many may stack: each costs a row of viewport, so a deep grouping
1647
+ * would otherwise spend the screen describing itself. `false` is off, the
1648
+ * same as leaving it unset.
1568
1649
  */
1569
1650
  stickyGroupHeaders?: boolean | number | { depth?: number };
1570
1651
  /**
@@ -1748,6 +1829,10 @@ export interface ColumnState {
1748
1829
  groupIndex?: number | null;
1749
1830
  pivotIndex?: number | null;
1750
1831
  total?: TotalName | null;
1832
+ /** The group-subtotal override, when one differs from `total`. */
1833
+ groupTotal?: TotalName | null;
1834
+ /** The grand-total override, when one differs from `total`. */
1835
+ grandTotal?: TotalName | null;
1751
1836
  }
1752
1837
 
1753
1838
  export interface GridState {
@@ -1892,6 +1977,106 @@ export interface PushdownPlan {
1892
1977
  needsAll: boolean;
1893
1978
  /** Which parts could not be pushed: `filter`, `sort`, `quick`. */
1894
1979
  unpushed: string[];
1980
+ /**
1981
+ * Whether the whole result was fetched because `fullDataset` is on, rather
1982
+ * than only because residual work forced it. When true, totals and statistics
1983
+ * reduce over the whole matching set and the windowed-stat warning is silent.
1984
+ */
1985
+ full: boolean;
1986
+ /**
1987
+ * Per-aggregate provenance, present only when the last request computed
1988
+ * aggregates (BACKLOG-0000730 Part B): which statistics the engine computed
1989
+ * and which the client did, with the class the pushdown map assigned each.
1990
+ * Under grouping it also carries the `groupBy` the subtotals were computed
1991
+ * over. Build-time inspection, not a runtime per-figure marker.
1992
+ */
1993
+ aggregates?: {
1994
+ engine: AggregateProvenance[];
1995
+ client: AggregateProvenance[];
1996
+ groupBy?: string[];
1997
+ };
1998
+ }
1999
+
2000
+ /**
2001
+ * Opt-in, sticky full-dataset pull for a pushdown/remote source
2002
+ * (BACKLOG-0000730). Off by default. When enabled, the source materialises the
2003
+ * entire matching set client-side once per query signature and serves every
2004
+ * window, total and statistic from it, so those figures are computed over the
2005
+ * whole set rather than the loaded window. A set past either limit is refused
2006
+ * with a visible `source:error` — never silently truncated.
2007
+ */
2008
+ export interface PushdownFullDatasetConfig {
2009
+ /** Sticky: hold the whole matching set client-side. Default `false`. */
2010
+ enabled?: boolean;
2011
+ /** Refuse (visible error) past this many rows. Default `1_000_000`. */
2012
+ maxRows?: number;
2013
+ /** Refuse past this estimated heap cost, in bytes. Default `512 * 1024 * 1024`. */
2014
+ maxBytesEstimate?: number;
2015
+ }
2016
+
2017
+ /** How one requested aggregate should be computed. */
2018
+ export type AggregateMode = 'engine' | 'client' | 'engine-if-identical';
2019
+
2020
+ /**
2021
+ * Design-time aggregate-pushdown policy for a pushdown source
2022
+ * (BACKLOG-0000730 Part B). The developer chooses, at grid setup
2023
+ * before render, whether each statistic is computed by the engine (fast, over
2024
+ * the matching set) or client-side (the grid's exact definition, needs a
2025
+ * full-dataset pull). It is fixed for the life of the grid, never a runtime
2026
+ * toggle, and never surfaced to an end user.
2027
+ *
2028
+ * Absent, every aggregate is computed client-side — today's behaviour, so no
2029
+ * existing caller regresses. `engine-if-identical` is the recommended setting
2030
+ * for a windowed DuckDB source: it pushes only the statistics whose engine
2031
+ * result is verified identical to the grid kernel, keeping the documented
2032
+ * MAY-DIFFER stats (e.g. `mode`) client-side. The engine is used only when the
2033
+ * filter is fully pushed; a residual filter forces every aggregate client-side,
2034
+ * so an engine figure and a client figure never mix in one result set.
2035
+ */
2036
+ export interface PushdownAggregatesConfig {
2037
+ /**
2038
+ * The default policy for stats the engine can express. `'engine'` pushes
2039
+ * everything expressible (using the engine's method for MAY-DIFFER stats);
2040
+ * `'engine-if-identical'` pushes only the verified-identical ones; `'client'`
2041
+ * computes everything client-side. Default `'client'`.
2042
+ */
2043
+ default?: AggregateMode;
2044
+ /** Per-stat overrides, winning over `default`. A stat the engine cannot
2045
+ * express (`weightedQuantile`) is always client-side regardless. */
2046
+ overrides?: Record<string, 'engine' | 'client'>;
2047
+ }
2048
+
2049
+ /**
2050
+ * One aggregate the grid asks the source to compute over the matching set.
2051
+ * `params` carries e.g. `{ share: 0.1 }` so an adapter emits the matching SQL;
2052
+ * `weight` names the second column for a two-column stat like `correlation`.
2053
+ */
2054
+ export interface AggregateRequest {
2055
+ /** Keys the result back to the request. */
2056
+ id: string;
2057
+ /** The column to reduce. */
2058
+ col: string;
2059
+ /** The statistic name, as used in `total: '<name>'`. */
2060
+ fn: string;
2061
+ /** The second column, for a two-column statistic. */
2062
+ weight?: string;
2063
+ /** Parameters the statistic takes, e.g. a trim share. */
2064
+ params?: Record<string, unknown>;
2065
+ }
2066
+
2067
+ /** How one aggregate was routed, for `lastPlan()` provenance. */
2068
+ export interface AggregateProvenance {
2069
+ id: string;
2070
+ col: string;
2071
+ fn: string;
2072
+ /** How the engine result relates to the grid kernel. */
2073
+ class: 'identical' | 'may-differ' | 'fallback';
2074
+ /** Why it is client-side, when it is (config, fallback, or the guard). */
2075
+ reason?: string;
2076
+ weight?: string;
2077
+ /** Parameters the statistic takes, carried through so an adapter emits the
2078
+ * matching SQL (e.g. a trim share). */
2079
+ params?: Record<string, unknown>;
1895
2080
  }
1896
2081
 
1897
2082
  export interface PushdownSourceConfig {
@@ -1899,6 +2084,28 @@ export interface PushdownSourceConfig {
1899
2084
  /** The compute barrel, for applying whatever the engine could not. */
1900
2085
  compute?: object;
1901
2086
  pageSize?: number;
2087
+ /**
2088
+ * Opt-in full-dataset pull. Off unless `fullDataset.enabled` is set. See
2089
+ * {@link PushdownFullDatasetConfig}.
2090
+ */
2091
+ fullDataset?: PushdownFullDatasetConfig;
2092
+ /**
2093
+ * Design-time aggregate-pushdown policy. Absent = client-side (today's
2094
+ * behaviour). See {@link PushdownAggregatesConfig}.
2095
+ */
2096
+ aggregates?: PushdownAggregatesConfig;
2097
+ /**
2098
+ * Accept a partial/paged result to a whole-set request when residual work
2099
+ * (a filter, sort or quick search) will run over it client-side. Off by
2100
+ * default: such a shortfall is refused with a thrown error, because filtering
2101
+ * or sorting a fraction of the result presents the wrong rows as the whole
2102
+ * filtered set — a wrong answer, not a slow one. Set `true` only when you
2103
+ * knowingly accept that risk (e.g. an adapter that cannot page and a result
2104
+ * small enough not to matter); the old warn-once-and-proceed behaviour is
2105
+ * then kept. It never changes the fullDataset memory-guard or the
2106
+ * no-residual short-return warning.
2107
+ */
2108
+ allowPartialResults?: boolean;
1902
2109
  }
1903
2110
 
1904
2111
  export interface StatisticsApi {
@@ -2287,8 +2494,21 @@ export interface RowsApi {
2287
2494
  }
2288
2495
 
2289
2496
  export interface ColumnsApi {
2290
- /** Set or clear a column's totals-row reduction. */
2291
- setTotal(id: string, fn: TotalName | TotalFn | null): void;
2497
+ /**
2498
+ * Set or clear a column's totals-row reduction.
2499
+ *
2500
+ * With no `scope`, `fn` becomes the column's single `total`, applied to both
2501
+ * group subtotals and the grand total, and any independent group/grand
2502
+ * overrides are cleared — the same one-property behaviour as before
2503
+ * (BACKLOG-0000726). Pass `scope: 'group'` or `scope: 'grand'` to set just
2504
+ * that scope's reduction independently, leaving the other and the base
2505
+ * `total` untouched; the scope that has no override falls back to `total`.
2506
+ */
2507
+ setTotal(
2508
+ id: string,
2509
+ fn: TotalName | TotalFn | null,
2510
+ opts?: { scope?: 'group' | 'grand' },
2511
+ ): void;
2292
2512
  /**
2293
2513
  * The aggregate names meaningful for a column, honouring its type's
2294
2514
  * `totals.supported` declaration (§9.4). What the aggregate chooser offers.
@@ -3310,6 +3530,11 @@ export interface UnitConfig {
3310
3530
  group?: boolean;
3311
3531
  space?: string;
3312
3532
  placement?: 'suffix' | 'prefix';
3533
+ /** Render one stored number across an ordered subset of the system's units,
3534
+ * e.g. `['ft', 'in']` for `5 ft 11 in`. Display and parse only: the stored
3535
+ * value stays a single base-unit number, so sort, filter and total are
3536
+ * unchanged. Parsing sums the parts. */
3537
+ compound?: string[];
3313
3538
  }
3314
3539
 
3315
3540
  export function defineUnit(
@@ -3414,7 +3639,51 @@ export function toneOf(direction: string, goodWhen: string): 'good' | 'bad' | 'f
3414
3639
  */
3415
3640
  export function createPushdownSource(
3416
3641
  config: PushdownSourceConfig,
3417
- ): SourceConfig & { lastPlan(): PushdownPlan | null };
3642
+ ): SourceConfig & {
3643
+ lastPlan(): PushdownPlan | null;
3644
+ /**
3645
+ * Compute a set of aggregates over the matching set, splitting them between
3646
+ * the engine and the client by the design-time `aggregates` config
3647
+ * (BACKLOG-0000730 Part B). Ungrouped, returns the engine-computed `values`
3648
+ * keyed by id. When the request carries a `groupBy`, returns `groups` instead:
3649
+ * one entry per subtotal level and the grand total (`level: 0`, produced by a
3650
+ * single `GROUP BY ROLLUP`), each with its key values and its aggregate values
3651
+ * keyed by id. The client list is what the caller computes itself over the
3652
+ * full set. Aggregates are pushed only when the filter is fully pushed and —
3653
+ * under grouping — every grouping key is a plain column the engine can group
3654
+ * by; a residual filter or an unpushable group key forces every aggregate
3655
+ * client-side (no mixed provenance).
3656
+ */
3657
+ aggregate(
3658
+ request: RemoteRequest,
3659
+ requested: AggregateRequest[],
3660
+ ): Promise<{
3661
+ values: Record<string, unknown>;
3662
+ groups?: Array<{
3663
+ keys: unknown[];
3664
+ grouping?: number[];
3665
+ level: number;
3666
+ values: Record<string, unknown>;
3667
+ }>;
3668
+ engine: AggregateProvenance[];
3669
+ client: AggregateProvenance[];
3670
+ }>;
3671
+ };
3672
+
3673
+ /**
3674
+ * The pushdown map (BACKLOG-0000730 Part B): one published record per statistic
3675
+ * giving whether the engine can express it, the DuckDB aggregate SQL it emits,
3676
+ * and whether that result is IDENTICAL to the grid's own kernel or MAY-DIFFER.
3677
+ * The single source of truth the push router, the docs and `lastPlan()` all read.
3678
+ */
3679
+ export const STAT_PUSHDOWN: Readonly<Record<string, {
3680
+ pushable: boolean;
3681
+ class: 'identical' | 'may-differ' | 'fallback';
3682
+ sql?: string;
3683
+ note?: string;
3684
+ twoColumn?: boolean;
3685
+ blankAware?: boolean;
3686
+ }>>;
3418
3687
 
3419
3688
  /**
3420
3689
  * The capability set an adapter that declares nothing is treated as having: