@pythia-software/query-table-core 0.5.0 → 0.7.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/dist/index.d.ts CHANGED
@@ -1,142 +1,49 @@
1
- /** All filter operators across both source projects, unioned. Every value can
2
- * be NULL, so `is_null`/`is_not_null` are valid for every field type. */
3
- type FilterOp = "=" | "!=" | ">" | ">=" | "<" | "<=" | "contains" | "starts_with" | "ends_with" | "matches_regex" | "not_matches_regex" | "length_gt" | "length_lt" | "length_eq" | "includes" | "is_null" | "is_not_null";
4
- /** A single filter predicate (a "literal" in CNF terms). `value` is always the
5
- * raw string the UI captured; coercion to number/bool/date happens at
6
- * apply/compile time based on the field's declared type (never on the value's
7
- * runtime shape). Unused for the nullary ops (`is_null`/`is_not_null`).
8
- *
9
- * `negated` wraps the predicate in a logical NOT. It is only ever set for ops
10
- * that have no complementary operator (`contains`/`starts_with`/`ends_with`/
11
- * `includes`); ops with a complement negate by flipping the op itself (`>=`→`<`,
12
- * `=`→`!=`, `is_null`→`is_not_null`, `matches_regex`→`not_matches_regex`) — see
13
- * `negateClause` in ops.ts. NOT is null-exclusive: a NULL/empty value satisfies
14
- * neither the predicate nor its negation, matching `not_matches_regex`. */
15
- interface WhereClause {
16
- field: string;
17
- op: FilterOp;
18
- value: string;
19
- negated?: boolean;
20
- setFilter?: SetFilterMetadata;
21
- }
22
- type SetFilterMode = "any" | "all" | "none" | "empty";
23
- /** Presentation-only identity; execution always uses the accompanying predicates. */
24
- interface SetFilterMetadata {
25
- id: string;
26
- mode: SetFilterMode;
27
- values: string[];
28
- }
29
- /** A disjunction of predicates — the inner OR of conjunctive normal form. Every
30
- * member is a plain literal; nesting is intentionally not allowed yet (CNF
31
- * only), but the object shape leaves room to grow. */
32
- interface OrGroup {
33
- any: WhereClause[];
34
- setFilter?: SetFilterMetadata;
35
- }
36
- /** One conjunct of the WHERE clause: either a single predicate or an OR group.
37
- * `QueryState.where` is the AND of these terms, so the whole WHERE is
38
- * conjunctive normal form — `(a) AND (b OR c) AND (d)`. A bare literal is
39
- * equivalent to a one-element OR group; normalization flattens singleton groups
40
- * back to a literal so equality/encoding stay canonical. */
41
- type WhereTerm = WhereClause | OrGroup;
42
- /** Narrow a term to an OR group. A literal always carries `field`; a group never
43
- * does and always carries an `any` array. */
44
- declare function isOrGroup(term: WhereTerm): term is OrGroup;
45
- /** The predicate literals a term contributes, in order: the term itself for a
46
- * literal, or its members for a group. Handy for anything that must visit every
47
- * predicate regardless of grouping (field allowlisting, SELECT projection). */
48
- declare function predicatesOf(term: WhereTerm): WhereClause[];
49
- /** One ORDER BY term. Array order in `QueryState.orderBy` is the sort priority. */
50
- interface OrderByClause {
51
- field: string;
52
- dir: "asc" | "desc";
53
- /** NULL placement. Omitted = "last" (the historical default both projects used,
54
- * preserved so old `?q=` links round-trip identically). */
55
- nulls?: "first" | "last";
56
- /** Sort by the first capture group of a regex match (or by the whole match
57
- * when the pattern has no capture group). A non-match produces NULL. Regex
58
- * syntax is interpreted by the executor (JavaScript locally, PostgreSQL on
59
- * the bundled Go backend), so portable patterns should use their common
60
- * syntax subset. */
61
- extract?: {
62
- regex: string;
63
- };
64
- }
65
- /** One column in the SELECT list, plus the view state that travels with it.
66
- * Order in `QueryState.select` is the left-to-right display order. */
67
- interface SelectColumn {
68
- field: string;
69
- /** Pixel width override. Omitted = FieldDef.select.width, else the type default. */
70
- width?: number;
71
- }
72
- /** A single aggregate operation. `count` is the only op that needs no measure
73
- * column (it counts rows); the rest reduce one column's values. The server-side
74
- * matrix (backends/go aggOpAllowed + the TS AGG_OPS_BY_TYPE) decides which ops a
75
- * field's type permits. */
76
- type AggOp = "count" | "count_distinct" | "sum" | "avg" | "min" | "max";
77
- /** One optional "metric" pinned above the table: a single aggregate over a single
78
- * measure column, optionally broken down by one or more group columns.
79
- *
80
- * Scope is deliberately the *whole filtered set* — the same WHERE as the table,
81
- * but NOT its ORDER BY / LIMIT / OFFSET. It is always evaluated on the server
82
- * (a real GROUP BY), so it reflects every matching row, not just the visible
83
- * page. Lives inside QueryState so it round-trips through `?q=`, saved queries,
84
- * and undo/redo for free — a saved query is a saved dashboard. */
85
- interface AggregationClause {
86
- /** Stable id; keys the metric panel and survives a `?q=` round-trip. */
87
- id: string;
88
- op: AggOp;
89
- /** Measure column (FieldDef.name). Omit only for `count` (⇒ COUNT(*)). */
90
- field?: string;
91
- /** Group-by columns, in axis order. `[]` = a single grand-total value.
92
- * One ⇒ a list/bar breakdown; two ⇒ an x/y pivot; three+ ⇒ a flat table. */
93
- groupBy: string[];
94
- /** Panel header override; defaults to a derived label like "avg total". */
95
- label?: string;
96
- }
97
- interface QueryState {
98
- /** Ordered SELECT list + per-column widths. Empty = the schema's default columns. */
99
- select: SelectColumn[];
100
- /** WHERE in conjunctive normal form: the AND of these terms, each a single
101
- * predicate or an OR group. A flat list of literals (no groups) is the common
102
- * case and the legacy shape, decoded unchanged. */
103
- where: WhereTerm[];
104
- /** Multi-sort terms in priority order. Empty = the schema's default sort. */
105
- orderBy: OrderByClause[];
106
- /** Page size. */
107
- limit: number;
108
- /** Page offset (rows). */
109
- offset: number;
110
- /** Optional aggregate metrics shown above the table. Omitted/empty = none.
111
- * Each runs as its own server GROUP BY over the WHERE-filtered set (no paging). */
112
- aggregations?: AggregationClause[];
1
+ /** Presentation hints only: field types still determine query/filter semantics. */
2
+ interface JsonCellDisplay {
3
+ kind: "json";
4
+ /** auto accepts objects, JSON text and repeatedly encoded/escaped JSON text.
5
+ * value keeps a native string literal; json parses text once; escaped unwraps it. */
6
+ encoding?: "auto" | "value" | "json" | "escaped";
7
+ /** Maximum string-unwrapping passes, 1–8. Default 4. */
8
+ maxDecodeDepth?: number;
9
+ /** Inline preview length, 20–500 characters. Default 80. */
10
+ previewLength?: number;
11
+ /** Override initially expanded container levels, 0–8.
12
+ * The root starts open. By default, nested containers with up to 3 entries
13
+ * open; larger nested containers collapse. */
14
+ expandDepth?: number;
113
15
  }
114
- /** A row's stable identity, used for selection and per-row refresh. */
115
- type RowId = string | number;
116
- /** The query a fresh table starts from before schema defaults are layered on.
117
- * Empty `select`/`orderBy`/`where` mean "defer to the schema", which also keeps
118
- * encoded `?q=` tokens short (defaults are omitted). */
119
- declare const EMPTY_QUERY: QueryState;
120
- /** Structural limits applied whenever query state crosses a trust boundary
121
- * (URL, storage, an imperative setQuery call, or a server projection). Row
122
- * limits may use the full safe-integer range; consumers and their backends own
123
- * any smaller operational cap appropriate for their dataset. */
124
- declare const MAX_QUERY_LIMIT: number;
125
- declare const MAX_QUERY_OFFSET = 1000000;
126
- declare const MAX_SELECT_COLUMNS = 200;
127
- declare const MAX_WHERE_CLAUSES = 100;
128
- declare const MAX_ORDER_BY_TERMS = 20;
129
- declare const MAX_AGGREGATIONS = 20;
130
- declare const MAX_GROUP_BY_FIELDS = 20;
131
- declare const MAX_QUERY_TOKEN_LENGTH: number;
132
- /** Convert unknown input into a bounded, structurally valid QueryState.
133
- * Invalid members are dropped; invalid scalar paging values use the supplied
134
- * fallback. This function is intentionally schema-agnostic—field allowlisting
135
- * still happens in toServerQuery and in the backend compiler. */
136
- declare function normalizeQueryState(input: unknown, fallback?: QueryState): QueryState;
137
- /** Structural equality on two queries — used to decide whether a `?q=` write or a
138
- * refetch is actually needed (avoids history spam / redundant fetches). */
139
- declare function queriesEqual(a: QueryState, b: QueryState): boolean;
16
+ type CalendarDisplay = {
17
+ style?: "human" | "iso" | "custom";
18
+ pattern?: string;
19
+ /** UTC by default; an IANA timezone or local may be selected explicitly. */
20
+ timeZone?: string;
21
+ locale?: string;
22
+ };
23
+ type CellDisplay = JsonCellDisplay | ({
24
+ kind: "date" | "datetime";
25
+ sourceUnit?: "iso" | "epochSeconds" | "epochMilliseconds";
26
+ } & CalendarDisplay) | ({
27
+ kind: "time";
28
+ sourceUnit?: "iso" | "epochSeconds" | "epochMilliseconds" | "secondsOfDay" | "millisecondsOfDay";
29
+ } & CalendarDisplay) | {
30
+ kind: "duration";
31
+ sourceUnit?: "milliseconds" | "seconds" | "minutes" | "hours" | "days";
32
+ style?: "human" | "long" | "clock" | "custom";
33
+ pattern?: string;
34
+ decimals?: number;
35
+ } | {
36
+ kind: "number" | "percent";
37
+ decimals?: number;
38
+ locale?: string;
39
+ } | {
40
+ kind: "chip";
41
+ labels?: Record<string, string>;
42
+ tones?: Record<string, "neutral" | "success" | "warning" | "danger">;
43
+ } | {
44
+ kind: "text";
45
+ truncate?: boolean;
46
+ };
140
47
 
141
48
  type FieldType = "text" | "number" | "enum" | "datetime" | "bool" | "textarray";
142
49
  type Align = "left" | "right" | "center";
@@ -241,7 +148,14 @@ interface FieldDef<Row = any, V = unknown> {
241
148
  group?: string;
242
149
  alias?: string;
243
150
  aliases?: string[];
151
+ /** Declarative built-in presentation, independent of the query's field type.
152
+ * An explicit render function or registered render key takes precedence. */
153
+ display?: CellDisplay;
244
154
  render?: string | CellRenderer<Row, V>;
155
+ property?: PropertyFieldConfig;
156
+ /** Structured provenance for relationship discovery; never SQL text. */
157
+ origin?: FieldOrigin;
158
+ relationship?: FieldRelationship;
245
159
  }
246
160
  /** The frontend's loaded schema for one dataset. */
247
161
  interface FieldSchema<Row = any> {
@@ -257,27 +171,575 @@ interface FieldSchema<Row = any> {
257
171
  /** Page size for a fresh query. */
258
172
  defaultLimit?: number;
259
173
  }
260
- declare function isFilterable(f: FieldDef): boolean;
261
- declare function isPushdownFilter(f: FieldDef): boolean;
262
- declare function isSortable(f: FieldDef): boolean;
263
- declare function isSelectable(f: FieldDef): boolean;
264
- declare function filterValues(f: FieldDef): FilterValues;
265
- /** Index fields by name for O(1) lookup. */
266
- declare function indexFields<Row>(schema: FieldSchema<Row>): Map<string, FieldDef<Row>>;
267
- /** Resolve the ordered, selectable FieldDefs for a query, falling back to schema
268
- * defaults, then to every `select.default` field. Unknown/duplicate/unselectable
269
- * names in `select` are dropped. */
270
- declare function selectedFields<Row>(schema: FieldSchema<Row>, q: {
271
- select: SelectColumn[];
272
- }): FieldDef<Row>[];
273
- /** Read a field's value from a row: derived `accessor` if present, else the
274
- * backend `path` (dotted) / `name`. Returns null when any path segment is missing. */
275
- declare function readFieldValue<Row, V = unknown>(field: FieldDef<Row, V>, row: Row): V | null;
276
- /** Parse + validate a raw JSON schema document into a FieldSchema. The doc is the
277
- * same one the Go backend loads; here we drop backend `bindings` (the frontend
278
- * never sees SQL) and leave `render` as a string key the consumer maps to a
279
- * renderer at runtime. Throws with a readable message on a malformed document. */
280
- declare function loadSchema<Row = any>(doc: unknown): FieldSchema<Row>;
174
+ declare function isFilterable(f: FieldDef): boolean;
175
+ declare function isPushdownFilter(f: FieldDef): boolean;
176
+ declare function isSortable(f: FieldDef): boolean;
177
+ declare function isSelectable(f: FieldDef): boolean;
178
+ declare function filterValues(f: FieldDef): FilterValues;
179
+ /** Index fields by name for O(1) lookup. */
180
+ declare function indexFields<Row>(schema: FieldSchema<Row>): Map<string, FieldDef<Row>>;
181
+ /** Resolve the ordered, selectable FieldDefs for a query, falling back to schema
182
+ * defaults, then to every `select.default` field. Unknown/duplicate/unselectable
183
+ * names in `select` are dropped. */
184
+ declare function selectedFields<Row>(schema: FieldSchema<Row>, q: {
185
+ select: SelectColumn[];
186
+ }): FieldDef<Row>[];
187
+ /** Return the canonical name for a query token, or undefined when unresolved. */
188
+ declare function resolveFieldName<Row>(schema: FieldSchema<Row>, field: string): string | undefined;
189
+ /** Read a field's value from a row: derived `accessor` if present, else the
190
+ * backend `path` (dotted) / `name`. Returns null when any path segment is missing. */
191
+ declare function readFieldValue<Row, V = unknown>(field: FieldDef<Row, V>, row: Row): V | null;
192
+ /** Parse + validate a raw JSON schema document into a FieldSchema. The doc is the
193
+ * same one the Go backend loads; here we drop backend `bindings` (the frontend
194
+ * never sees SQL) and leave `render` as a string key the consumer maps to a
195
+ * renderer at runtime. Throws with a readable message on a malformed document. */
196
+ declare function loadSchema<Row = any>(doc: unknown): FieldSchema<Row>;
197
+
198
+ type FormulaType = Exclude<FieldType, "enum"> | "null";
199
+ type FormulaValue = string | number | boolean | string[] | null;
200
+ interface FormulaDiagnostic {
201
+ message: string;
202
+ from: number;
203
+ to: number;
204
+ }
205
+ type FormulaNode = {
206
+ kind: "literal";
207
+ value: FormulaValue;
208
+ from: number;
209
+ to: number;
210
+ } | {
211
+ kind: "field";
212
+ name: string;
213
+ valueType?: FormulaType;
214
+ from: number;
215
+ to: number;
216
+ } | {
217
+ kind: "call";
218
+ name: string;
219
+ args: FormulaNode[];
220
+ from: number;
221
+ to: number;
222
+ };
223
+ interface FormulaPlan {
224
+ ast: FormulaNode;
225
+ dependencies: string[];
226
+ type: FormulaType;
227
+ }
228
+ interface RegexInspection {
229
+ input: string;
230
+ start: number;
231
+ end: number;
232
+ groups: (string | null)[];
233
+ }
234
+ interface FormulaResult {
235
+ value: FormulaValue;
236
+ error?: string;
237
+ regex?: RegexInspection;
238
+ }
239
+ interface FormulaFunction {
240
+ name: string;
241
+ signature: string;
242
+ description: string;
243
+ min: number;
244
+ max: number;
245
+ result: FormulaType | "branch";
246
+ args: FormulaType[] | "any";
247
+ }
248
+ declare const FORMULA_FUNCTIONS: FormulaFunction[];
249
+ declare class FormulaError extends Error {
250
+ from: number;
251
+ to: number;
252
+ constructor(message: string, from?: number, to?: number);
253
+ }
254
+ declare function compileFormula(source: string, fields: readonly {
255
+ name: string;
256
+ type: FieldType;
257
+ }[], resolve?: (name: string) => FormulaPlan | undefined, extension?: (node: FormulaNode, check: (node: FormulaNode) => FormulaType) => FormulaType | undefined): FormulaPlan;
258
+ /** Self-contained runtime: serialized into the browser worker, never user code. */
259
+ declare function formulaRuntime(ast: FormulaNode, inputs: Record<string, FormulaValue>, extension?: (node: Extract<FormulaNode, {
260
+ kind: "call";
261
+ }>) => FormulaResult | undefined): FormulaResult;
262
+
263
+ type MetricValue = number | string | boolean | null;
264
+ type MetricKey = MetricValue;
265
+ type MetricScope = "allMatching" | "shownRows";
266
+ /** Calendar grouping, currently executed over complete local populations. */
267
+ interface MetricDateGrouping {
268
+ field: string;
269
+ interval: "hour" | "day" | "week" | "month" | "year";
270
+ /** IANA timezone identifier; omitted settings retain UTC behavior. */
271
+ timeZone?: string;
272
+ includeEmpty: boolean;
273
+ range: "query" | "observed" | "custom";
274
+ start?: string;
275
+ end?: string;
276
+ }
277
+ interface MetricValueFormat {
278
+ kind: "number" | "percent" | "duration" | "date" | "time" | "datetime";
279
+ decimals?: number;
280
+ sourceUnit?: "milliseconds" | "seconds" | "minutes" | "hours" | "days" | "iso" | "epochSeconds" | "epochMilliseconds" | "secondsOfDay" | "millisecondsOfDay";
281
+ style?: "human" | "long" | "clock" | "custom" | "iso";
282
+ pattern?: string;
283
+ timeZone?: string;
284
+ }
285
+ /** Bounds use raw result units; omitted bounds are inferred from the data. */
286
+ interface MetricAxisScale {
287
+ mode?: "linear" | "log";
288
+ min?: number;
289
+ max?: number;
290
+ }
291
+ interface MetricDisplay {
292
+ kind: "auto" | "value" | "table" | "list" | "bar-horizontal" | "bar-vertical" | "line" | "pie" | "donut" | "scatter" | "box" | "histogram";
293
+ valueLabel?: string;
294
+ xLabel?: string;
295
+ yLabel?: string;
296
+ format?: MetricValueFormat;
297
+ xFormat?: MetricValueFormat;
298
+ yFormat?: MetricValueFormat;
299
+ legendPosition?: "left" | "right" | "top" | "bottom" | "none";
300
+ /** Maximum displayed groups; does not change the computed result or ordering. */
301
+ maxGroups?: number;
302
+ /** Show the exact additive remainder in pie/donut (default true) or list (default false). */
303
+ showOther?: boolean;
304
+ list?: {
305
+ showBars?: boolean;
306
+ useGroupColors?: boolean;
307
+ showValues?: boolean;
308
+ /** Place the line bar beside the label (default) or underneath a wrapping label. */
309
+ barPosition?: "inline" | "below";
310
+ /** Maximum wrapped label lines for below bars (positive integer); omitted means unlimited. */
311
+ maxLabelLines?: number;
312
+ };
313
+ pivot?: {
314
+ swap?: boolean;
315
+ rowDir?: "asc" | "desc";
316
+ columnDir?: "asc" | "desc";
317
+ };
318
+ boxMean?: boolean;
319
+ xScale?: MetricAxisScale;
320
+ yScale?: MetricAxisScale;
321
+ }
322
+ interface MetricLayout {
323
+ widthRem: number;
324
+ heightRem: number;
325
+ minWidthRem: number;
326
+ minHeightRem: number;
327
+ }
328
+ interface MetricDistribution {
329
+ kind: "box" | "histogram";
330
+ input: string;
331
+ whiskers?: "minmax" | "tukey";
332
+ bins?: number;
333
+ }
334
+ interface MetricSort {
335
+ key: "value" | "y" | "count" | "samples" | `group${number}`;
336
+ dir: "asc" | "desc";
337
+ nulls?: "first" | "last";
338
+ }
339
+ interface MetricBoxSummary {
340
+ n: number;
341
+ min: number;
342
+ q1: number;
343
+ median: number;
344
+ q3: number;
345
+ max: number;
346
+ mean: number;
347
+ low: number;
348
+ high: number;
349
+ outliers: number[];
350
+ outlierCount: number;
351
+ whiskers: "minmax" | "tukey";
352
+ method: "exact-linear";
353
+ }
354
+ type MetricDistributionResult = {
355
+ kind: "box";
356
+ summary: MetricBoxSummary | null;
357
+ } | {
358
+ kind: "histogram";
359
+ edges: number[];
360
+ counts: number[];
361
+ n: number;
362
+ };
363
+ interface MetricDiagnostic extends FormulaDiagnostic {
364
+ metricId?: string;
365
+ code?: string;
366
+ }
367
+ /** Host must enforce snapshot against the row response; this token alone does not pin data. */
368
+ interface MetricQuery {
369
+ snapshot?: string;
370
+ profile?: string;
371
+ expectedRevisions?: Record<string, string>;
372
+ planToken?: string;
373
+ version: 2;
374
+ where: WhereTerm[];
375
+ orderBy: OrderByClause[];
376
+ limit: number;
377
+ offset: number;
378
+ metrics: AggregationClause[];
379
+ diagnostics: MetricDiagnostic[];
380
+ }
381
+ interface MetricCapabilities {
382
+ version: 2;
383
+ expressions?: boolean;
384
+ shownRows?: boolean;
385
+ distributions?: boolean;
386
+ computedFields?: boolean;
387
+ maxGroups?: number;
388
+ profile?: string;
389
+ }
390
+
391
+ /** Formats change presentation only; values retain their original types/units. */
392
+ type PropertyFormat = MetricValueFormat | {
393
+ kind: "text" | "code" | "boolean" | "chips";
394
+ } | JsonCellDisplay | Extract<CellDisplay, {
395
+ kind: "chip";
396
+ }> | {
397
+ kind: "relative";
398
+ sourceUnit?: "iso" | "epochSeconds" | "epochMilliseconds";
399
+ } | {
400
+ kind: "currency";
401
+ currency: string;
402
+ decimals?: number;
403
+ };
404
+ interface PropertyFieldConfig {
405
+ enabled?: boolean;
406
+ default?: boolean;
407
+ /** Detail-specific renderer. Table renderers remain unchanged. */
408
+ render?: string;
409
+ format?: PropertyFormat;
410
+ }
411
+ interface PropertySelection {
412
+ /** Instance identity allows the same field to appear with different formats. */
413
+ id: string;
414
+ field: string;
415
+ label?: string;
416
+ render?: string;
417
+ format?: PropertyFormat;
418
+ }
419
+ /** Independent of object identity and collection QueryState. [] shows no fields. */
420
+ interface PropertyViewState {
421
+ version: 1;
422
+ properties: PropertySelection[];
423
+ /** Property label column width as a percentage, shared with the saved view. */
424
+ labelWidth?: number;
425
+ }
426
+ declare const MAX_PROPERTIES = 200;
427
+ declare function normalizePropertyFormat(input: unknown): PropertyFormat | undefined;
428
+ /** Strict envelope/version checks prevent interpreting future saved views as empty. */
429
+ declare function normalizePropertyView(input: unknown): PropertyViewState;
430
+ declare function createPropertyView<Row>(schema: FieldSchema<Row>, names?: string[]): PropertyViewState;
431
+ interface FieldOrigin {
432
+ table: string;
433
+ column: string;
434
+ }
435
+ /** Backend discovery groups composite key columns in constraint order. */
436
+ interface ForeignKey {
437
+ sourceTable: string;
438
+ sourceColumns: string[];
439
+ targetTable: string;
440
+ targetColumns: string[];
441
+ }
442
+ interface ObjectTypeDefinition {
443
+ name: string;
444
+ table: string;
445
+ keyFields: string[];
446
+ }
447
+ interface FieldRelationship {
448
+ objectType: string;
449
+ /** Target key field -> local catalogue field, including composite keys. */
450
+ key: Record<string, string>;
451
+ }
452
+ interface ObjectReference {
453
+ objectType: string;
454
+ key: Record<string, RowId>;
455
+ }
456
+ interface ResolvedObjectReference {
457
+ reference: ObjectReference;
458
+ label?: string;
459
+ href?: string;
460
+ }
461
+ /** Hosts resolve a batch through their authorized APIs, and own application routes. */
462
+ interface RelationshipAdapter {
463
+ resolve(references: ObjectReference[], signal?: AbortSignal): Promise<ResolvedObjectReference[]>;
464
+ }
465
+ declare function objectReferenceKey(reference: ObjectReference): string;
466
+ declare function readObjectReference<Row>(field: FieldDef<Row>, fields: FieldDef<Row>[], row: Row): ObjectReference | null;
467
+ /** Only unambiguous, fully mapped constraints become links. Explicit metadata wins. */
468
+ declare function discoverFieldRelationships<Row>(fields: FieldDef<Row>[], keys: ForeignKey[], types: ObjectTypeDefinition[]): FieldDef<Row>[];
469
+
470
+ /** Portable datetime/offset bound in milliseconds (also below MAX_SAFE_INTEGER). */
471
+ declare const MAX_RELATIVE_TIME_MS = 8640000000000000;
472
+ /** A signed elapsed offset, e.g. -1h or +8d2h10m. The sign applies to the
473
+ * complete sum. Returns null for absolute values or invalid duration syntax.
474
+ * Units are lowercase; amounts are whole integers. No whitespace, calendar
475
+ * arithmetic, locale, or Date.parse heuristics are used. */
476
+ declare function parseRelativeDuration(value: string): number | null;
477
+ /** Local execution only. The server independently owns its execution clock. */
478
+ interface QueryEvaluationOptions {
479
+ now?: number;
480
+ }
481
+
482
+ type GroupedRow = Record<string, unknown>;
483
+ type Row = GroupedRow;
484
+ type SummaryOp = AggOp | "row" | "formula";
485
+ interface RepresentativeOrder {
486
+ field: string;
487
+ dir: "asc" | "desc";
488
+ nulls: "first" | "last";
489
+ }
490
+ interface RepresentativeRow {
491
+ orderBy: RepresentativeOrder[];
492
+ }
493
+ interface SummaryColumn {
494
+ id: string;
495
+ op: SummaryOp;
496
+ field?: string;
497
+ expression?: string;
498
+ label?: string;
499
+ }
500
+ interface GroupingView {
501
+ version: 2;
502
+ mode: "rows" | "groups";
503
+ keys: string[];
504
+ summaries: SummaryColumn[];
505
+ representative: RepresentativeRow | null;
506
+ input: QueryState;
507
+ result: QueryState;
508
+ }
509
+ /** One grouping stage over the complete source population. QueryState.where is
510
+ * HAVING in this mode; source.where is the pre-group WHERE. The source snapshot
511
+ * also restores the record layout, paging, metrics and item details on exit. */
512
+ interface GroupingDefinition {
513
+ keys: string[];
514
+ summaries: SummaryColumn[];
515
+ representative: RepresentativeRow | null;
516
+ diagnostics?: string[];
517
+ }
518
+ interface GroupingState extends GroupingDefinition {
519
+ source: QueryState;
520
+ }
521
+ declare const GROUP_ID_FIELD = "__qt_group_id__";
522
+ declare const MAX_REPRESENTATIVE_SORTS = 8;
523
+ declare const MAX_SUMMARY_COLUMNS = 64;
524
+ declare const MAX_RESULT_GROUPS = 10000;
525
+ declare function withoutGrouping(query: QueryState): QueryState;
526
+ declare const OP_LABEL: Record<SummaryOp, string>;
527
+ /** Bind aggregation helpers to this instance's source catalogue. */
528
+ declare function createGroupingModel<InputRow>(inputSchema: FieldSchema<InputRow>): {
529
+ inputSchema: FieldSchema<InputRow>;
530
+ sourceField: (name: string) => FieldDef<InputRow, unknown> | undefined;
531
+ groupableFields: FieldDef<InputRow, unknown>[];
532
+ summaryFields: FieldDef<InputRow, unknown>[];
533
+ representativeFields: FieldDef<InputRow, unknown>[];
534
+ representativeLabel: (rule: RepresentativeRow) => string;
535
+ allowedOps: (field?: string) => SummaryOp[];
536
+ summaryExpression: (column: SummaryColumn) => string;
537
+ summaryProblem: (column: SummaryColumn) => string | null;
538
+ summaryLabel: (column: SummaryColumn) => string;
539
+ resultSchema: (keys: string[], summaries: SummaryColumn[]) => FieldSchema<Row>;
540
+ groupRows: (rows: InputRow[], schema: FieldSchema<InputRow>, keys: string[], summaries: SummaryColumn[], rule?: RepresentativeRow | null) => Row[];
541
+ filteredInput: (rows: InputRow[], query: QueryState, schema: FieldSchema<InputRow>, options?: QueryEvaluationOptions) => InputRow[];
542
+ reconcileRepresentative: (query: QueryState, summaries: SummaryColumn[], schema: FieldSchema<Row>, remove: boolean) => QueryState;
543
+ validate: (definition: GroupingDefinition) => void;
544
+ };
545
+ type GroupingModel<Row> = ReturnType<typeof createGroupingModel<Row>>;
546
+ /** Evaluate the WHERE and grouping stages once; callers apply HAVING and paging
547
+ * with the resulting schema and withoutGrouping(query). Never accepts a page
548
+ * fetched by a transport as a complete source dataset. */
549
+ declare function prepareGrouping<Row>(rows: Row[], schema: FieldSchema<Row>, grouping: GroupingState, options?: QueryEvaluationOptions): {
550
+ inputs: Row[];
551
+ rows: GroupedRow[];
552
+ schema: FieldSchema<GroupedRow>;
553
+ };
554
+
555
+ /** All filter operators across both source projects, unioned. Every value can
556
+ * be NULL, so `is_null`/`is_not_null` are valid for every field type. */
557
+ type FilterOp = "=" | "!=" | ">" | ">=" | "<" | "<=" | "contains" | "starts_with" | "ends_with" | "matches_regex" | "not_matches_regex" | "length_gt" | "length_lt" | "length_eq" | "includes" | "is_null" | "is_not_null";
558
+ /** A single filter predicate (a "literal" in CNF terms). `value` is always the
559
+ * raw string the UI captured; coercion to number/bool/date happens at
560
+ * apply/compile time based on the field's declared type (never on the value's
561
+ * runtime shape). Datetime comparisons also accept signed duration offsets
562
+ * such as "-1h" or "+8d2h10m", resolved relative to execution time.
563
+ * Unused for the nullary ops (`is_null`/`is_not_null`).
564
+ *
565
+ * `negated` wraps the predicate in a logical NOT. It is only ever set for ops
566
+ * that have no complementary operator (`contains`/`starts_with`/`ends_with`/
567
+ * `includes`); ops with a complement negate by flipping the op itself (`>=`→`<`,
568
+ * `=`→`!=`, `is_null`→`is_not_null`, `matches_regex`→`not_matches_regex`) — see
569
+ * `negateClause` in ops.ts. NOT is null-exclusive: a NULL/empty value satisfies
570
+ * neither the predicate nor its negation, matching `not_matches_regex`. */
571
+ interface WhereClause {
572
+ field: string;
573
+ op: FilterOp;
574
+ value: string;
575
+ negated?: boolean;
576
+ setFilter?: SetFilterMetadata;
577
+ }
578
+ type SetFilterMode = "any" | "all" | "none" | "empty";
579
+ /** Presentation-only identity; execution always uses the accompanying predicates. */
580
+ interface SetFilterMetadata {
581
+ id: string;
582
+ mode: SetFilterMode;
583
+ values: string[];
584
+ }
585
+ /** A disjunction of predicates — the inner OR of conjunctive normal form. Every
586
+ * member is a plain literal; nesting is intentionally not allowed yet (CNF
587
+ * only), but the object shape leaves room to grow. */
588
+ interface OrGroup {
589
+ any: WhereClause[];
590
+ setFilter?: SetFilterMetadata;
591
+ }
592
+ /** One conjunct of the WHERE clause: either a single predicate or an OR group.
593
+ * `QueryState.where` is the AND of these terms, so the whole WHERE is
594
+ * conjunctive normal form — `(a) AND (b OR c) AND (d)`. A bare literal is
595
+ * equivalent to a one-element OR group; normalization flattens singleton groups
596
+ * back to a literal so equality/encoding stay canonical. */
597
+ type WhereTerm = WhereClause | OrGroup;
598
+ /** Narrow a term to an OR group. A literal always carries `field`; a group never
599
+ * does and always carries an `any` array. */
600
+ declare function isOrGroup(term: WhereTerm): term is OrGroup;
601
+ /** The predicate literals a term contributes, in order: the term itself for a
602
+ * literal, or its members for a group. Handy for anything that must visit every
603
+ * predicate regardless of grouping (field allowlisting, SELECT projection). */
604
+ declare function predicatesOf(term: WhereTerm): WhereClause[];
605
+ /** One ORDER BY term. Array order in `QueryState.orderBy` is the sort priority. */
606
+ interface OrderByClause {
607
+ field: string;
608
+ dir: "asc" | "desc";
609
+ /** NULL placement. Omitted = "last" (the historical default both projects used,
610
+ * preserved so old `?q=` links round-trip identically). */
611
+ nulls?: "first" | "last";
612
+ /** Sort by the first capture group of a regex match (or by the whole match
613
+ * when the pattern has no capture group). A non-match produces NULL. Regex
614
+ * syntax is interpreted by the executor (JavaScript locally, PostgreSQL on
615
+ * the bundled Go backend), so portable patterns should use their common
616
+ * syntax subset. */
617
+ extract?: {
618
+ regex: string;
619
+ };
620
+ }
621
+ /** One column in the SELECT list, plus the view state that travels with it.
622
+ * Order in `QueryState.select` is the left-to-right display order. */
623
+ interface SelectColumn {
624
+ field: string;
625
+ /** Pixel width override. Omitted = FieldDef.select.width, else the type default. */
626
+ width?: number;
627
+ }
628
+ /** A single aggregate operation. `count` is the only op that needs no measure
629
+ * column (it counts rows); the rest reduce one column's values. The server-side
630
+ * matrix (backends/go aggOpAllowed + the TS AGG_OPS_BY_TYPE) decides which ops a
631
+ * field's type permits. */
632
+ type AggOp = "count" | "count_distinct" | "sum" | "avg" | "min" | "max";
633
+ /** One optional "metric" pinned above the table: a single aggregate over a single
634
+ * measure column, optionally broken down by one or more group columns.
635
+ *
636
+ * Scope is deliberately the *whole filtered set* — the same WHERE as the table,
637
+ * but NOT its ORDER BY / LIMIT / OFFSET. It is always evaluated on the server
638
+ * (a real GROUP BY), so it reflects every matching row, not just the visible
639
+ * page. Lives inside QueryState so it round-trips through `?q=`, saved queries,
640
+ * and undo/redo for free — a saved query is a saved dashboard. */
641
+ interface AggregationClause {
642
+ dateGrouping?: MetricDateGrouping;
643
+ expression?: string;
644
+ expressionY?: string;
645
+ scope?: MetricScope;
646
+ sort?: MetricSort[];
647
+ groupLimit?: number;
648
+ display?: MetricDisplay;
649
+ layout?: MetricLayout;
650
+ distribution?: MetricDistribution;
651
+ diagnostics?: string[];
652
+ /** Preserved source operation when this client does not implement it. */
653
+ unsupportedOp?: string;
654
+ /** Stable id; keys the metric panel and survives a `?q=` round-trip. */
655
+ id: string;
656
+ op: AggOp;
657
+ /** Measure column (FieldDef.name). Omit only for `count` (⇒ COUNT(*)). */
658
+ field?: string;
659
+ /** Group-by columns, in axis order. `[]` = a single grand-total value.
660
+ * One ⇒ a list/bar breakdown; two ⇒ an x/y pivot; three+ ⇒ a flat table. */
661
+ groupBy: string[];
662
+ /** Panel header override; defaults to a derived label like "avg total". */
663
+ label?: string;
664
+ }
665
+ /** Detail affordances are presentation state, never backend SELECT fields. */
666
+ type ItemPropertyMode = "hover" | "modal" | "expand";
667
+ type ItemDetailMode = ItemPropertyMode | "page";
668
+ interface ItemPageOptions {
669
+ /** Empty uses the current page with the item modal and query state. */
670
+ urlTemplate: string;
671
+ target: "_blank" | "_self";
672
+ }
673
+ /** Reserved presentation column; never sent to a data source. */
674
+ declare const ITEM_DETAILS_COLUMN = "__qt_item_details__";
675
+ interface ItemDetailsState {
676
+ modes: ItemDetailMode[];
677
+ /** A self-contained view travels with shared links; saved-view IDs are device-local. */
678
+ view?: PropertyViewState;
679
+ /** Independent layouts for each modality. Legacy view is the fallback. */
680
+ views?: Partial<Record<ItemPropertyMode, PropertyViewState>>;
681
+ page?: ItemPageOptions;
682
+ active?: {
683
+ id: RowId;
684
+ mode: "modal" | "expand";
685
+ };
686
+ }
687
+ interface QueryState {
688
+ /** Optional row-producing stage. `grouping.source` is the original record
689
+ * query (its WHERE runs first). This query selects/filters/sorts the groups;
690
+ * its `where` therefore represents HAVING. Only one stage is supported. */
691
+ grouping?: GroupingState;
692
+ itemDetails?: ItemDetailsState;
693
+ /** Ordered SELECT list + per-column widths. Empty = the schema's default columns. */
694
+ select: SelectColumn[];
695
+ /** Refit columns to each viewer's table viewport after loading. Manual width
696
+ * changes clear this view preference; omitted/false uses stored widths. */
697
+ fitToScreen?: boolean;
698
+ /** WHERE in conjunctive normal form: the AND of these terms, each a single
699
+ * predicate or an OR group. A flat list of literals (no groups) is the common
700
+ * case and the legacy shape, decoded unchanged. */
701
+ where: WhereTerm[];
702
+ /** Multi-sort terms in priority order. Empty = the schema's default sort. */
703
+ orderBy: OrderByClause[];
704
+ /** Page size. */
705
+ limit: number;
706
+ /** Page offset (rows). */
707
+ offset: number;
708
+ /** Optional aggregate metrics shown above the table. Omitted/empty = none.
709
+ * Each runs as its own server GROUP BY over the WHERE-filtered set (no paging). */
710
+ aggregations?: AggregationClause[];
711
+ }
712
+ /** A row's stable identity, used for selection and per-row refresh. */
713
+ type RowId = string | number;
714
+ /** The query a fresh table starts from before schema defaults are layered on.
715
+ * Empty `select`/`orderBy`/`where` mean "defer to the schema", which also keeps
716
+ * encoded `?q=` tokens short (defaults are omitted). */
717
+ declare const EMPTY_QUERY: QueryState;
718
+ /** Structural limits applied whenever query state crosses a trust boundary
719
+ * (URL, storage, an imperative setQuery call, or a server projection). Row
720
+ * limits may use the full safe-integer range; consumers and their backends own
721
+ * any smaller operational cap appropriate for their dataset. */
722
+ declare const MAX_QUERY_LIMIT: number;
723
+ declare const MAX_QUERY_OFFSET = 1000000;
724
+ declare const MAX_SELECT_COLUMNS = 200;
725
+ declare const MAX_WHERE_CLAUSES = 100;
726
+ declare const MAX_ORDER_BY_TERMS = 20;
727
+ declare const MAX_AGGREGATIONS = 20;
728
+ declare const MAX_GROUP_BY_FIELDS = 20;
729
+ declare const MAX_QUERY_TOKEN_LENGTH: number;
730
+ /** Convert unknown input into a bounded, structurally valid QueryState.
731
+ * Invalid members are dropped; invalid scalar paging values use the supplied
732
+ * fallback. This function is intentionally schema-agnostic—field allowlisting
733
+ * still happens in toServerQuery and in the backend compiler. */
734
+ declare function normalizeQueryState(input: unknown, fallback?: QueryState): QueryState;
735
+ /** Structural equality on two queries — used to decide whether a `?q=` write or a
736
+ * refetch is actually needed (avoids history spam / redundant fetches). */
737
+ declare function queriesEqual(a: QueryState, b: QueryState): boolean;
738
+
739
+ /** Literal templates only: substitution never evaluates code or nested paths. */
740
+ declare function itemPageFields(template: string): string[];
741
+ declare function validateItemPageTemplate<Row>(template: string, schema: FieldSchema<Row>): string | null;
742
+ declare function renderItemPageTemplate<Row>(template: string, schema: FieldSchema<Row>, row: Row, id: RowId | null, query: string): string | null;
281
743
 
282
744
  /** The logical negation of a predicate. Prefers flipping to the complementary
283
745
  * operator (nicer to read, and the server's op allowlist already covers it);
@@ -320,7 +782,7 @@ declare function opAllowedForType(type: FieldType, op: FilterOp): boolean;
320
782
  /** Coerce a raw string value to the type the field expects, for client-side
321
783
  * comparison. Throws on invalid input (e.g. a non-numeric value on a number
322
784
  * field) so callers can surface it rather than silently mis-filtering. */
323
- declare function coerceValue(type: FieldType, raw: string): string | number | boolean;
785
+ declare function coerceValue(type: FieldType, raw: string, now?: number): string | number | boolean;
324
786
 
325
787
  /** Default aggregate ops per field type. A FieldDef.aggregate.ops overrides this.
326
788
  * `count`/`count_distinct` work on anything countable; sum/avg are numeric-only;
@@ -345,11 +807,18 @@ declare function isMeasurable(field: FieldDef): boolean;
345
807
  * default off, but `aggregate.groupable: true` opts any backend field in. */
346
808
  declare function isGroupable(field: FieldDef): boolean;
347
809
 
348
- /** QueryState → base64url token (omitting defaults). All-default query → "". */
810
+ declare const MAX_QUERY_STATE_BYTES: number;
811
+ declare class QueryDecodeError extends Error {
812
+ readonly code: "invalid" | "too_large" | "unsupported_version";
813
+ constructor(code: "invalid" | "too_large" | "unsupported_version", message: string);
814
+ }
815
+ /** v2 JSON or raw-DEFLATE UTF-8, whichever produces the shorter URL token. */
349
816
  declare function encodeQuery(q: QueryState): string;
350
817
  /** base64url token → QueryState. Tolerant: bad input and legacy shapes both
351
818
  * normalize to a valid QueryState (never throws). */
352
819
  declare function decodeQuery(token: string): QueryState;
820
+ /** Navigation uses this decoder so corrupt links cannot silently run defaults. */
821
+ declare function decodeQueryStrict(token: string): QueryState;
353
822
  /** The backend request derived from a QueryState + its schema. Mirrors the Go
354
823
  * `WireQuery`. View-only state (column widths) is intentionally absent. */
355
824
  interface ServerQuery {
@@ -371,22 +840,40 @@ declare function toServerQuery<Row>(q: QueryState, schema: FieldSchema<Row>): Se
371
840
  * metrics describe every matching row, not the visible page. Mirrors the Go
372
841
  * AggSpec list. */
373
842
  interface AggregationRequest {
843
+ diagnostics?: MetricDiagnostic[];
374
844
  where: WhereTerm[];
375
845
  aggregations: AggregationClause[];
376
846
  }
377
847
  /** One group's result. `keys` has one entry per AggregationClause.groupBy field,
378
848
  * in axis order (`[]` for a grand total); a null key is the NULL/empty bucket. */
379
849
  interface AggregationBucket {
380
- keys: (string | null)[];
850
+ keys: MetricKey[];
851
+ error?: string;
852
+ y?: MetricValue;
853
+ yError?: string;
854
+ components?: {
855
+ expression: string;
856
+ value: MetricValue;
857
+ }[];
858
+ nullCount?: number;
859
+ inputErrorCount?: number;
860
+ distribution?: MetricDistributionResult;
381
861
  /** The metric: a number for count/sum/avg, or the column's value for min/max
382
862
  * (which may be a string for text/datetime). null when undefined (e.g. avg of
383
863
  * an all-null column). */
384
- value: number | string | null;
864
+ value: MetricValue;
385
865
  /** COUNT(*) of rows in the group — always present, even when `value` isn't a
386
866
  * count, so the panel can show group sizes / shares. */
387
867
  count: number;
388
868
  }
389
869
  interface AggregationResultEntry {
870
+ /** Exact omitted additive quantity; unavailable for ratios/averages/distinct. */
871
+ other?: AggregationBucket;
872
+ error?: string;
873
+ scope?: MetricScope;
874
+ processedRows?: number;
875
+ groupCount?: number;
876
+ coverage?: "exact" | "partial";
390
877
  /** Echoes AggregationClause.id. */
391
878
  id: string;
392
879
  buckets: AggregationBucket[];
@@ -401,6 +888,200 @@ interface AggregationResult {
401
888
  * unknown field are dropped — the server has no SQL for them. */
402
889
  declare function toAggregationQuery<Row>(q: QueryState, schema: FieldSchema<Row>): AggregationRequest;
403
890
 
891
+ interface QuerySnapshot {
892
+ version: 1;
893
+ code: string;
894
+ token: string;
895
+ createdAt: number;
896
+ }
897
+ /** The host owns authenticated, durable storage. Save is immutable/idempotent
898
+ * by code: an existing code may never be assigned a different token. */
899
+ interface QuerySnapshotStore {
900
+ save(key: string, snapshot: QuerySnapshot, signal?: AbortSignal): Promise<QuerySnapshot>;
901
+ resolve(key: string, code: string, signal?: AbortSignal): Promise<QuerySnapshot | null>;
902
+ }
903
+ interface QuerySnapshotCache {
904
+ get(key: string, code: string): QuerySnapshot | null;
905
+ find(key: string, token: string): QuerySnapshot | null;
906
+ put(key: string, snapshot: QuerySnapshot): void;
907
+ isDurable?(key: string, code: string): boolean;
908
+ markDurable?(key: string, code: string): void;
909
+ }
910
+ interface QueryLink {
911
+ url: string;
912
+ kind: "inline" | "local" | "durable";
913
+ bytes: number;
914
+ snapshotCode?: string;
915
+ }
916
+ interface QueryLinkOptions {
917
+ /** Complete URL budget, including host parameters and fragment. Default 2000. */
918
+ maxUrlBytes?: number;
919
+ cache?: QuerySnapshotCache;
920
+ }
921
+ declare function validateQuerySnapshot(input: unknown): QuerySnapshot;
922
+ /** FIFO entries are scoped by dataset. One bounded envelope makes each write
923
+ * atomic; the new entry is protected from eviction and stored before URL use.
924
+ * Browser profiles, origins, and devices do not share these entries. */
925
+ declare function localQuerySnapshotCache(options?: {
926
+ storage?: Pick<Storage, "getItem" | "setItem">;
927
+ storageKey?: string;
928
+ maxEntries?: number;
929
+ maxBytes?: number;
930
+ }): QuerySnapshotCache;
931
+ declare function memoryQuerySnapshotCache(): QuerySnapshotCache;
932
+ type QuerySnapshotHttpErrorCode = "quota" | "forbidden" | "conflict" | "unavailable" | "storage" | "http";
933
+ /** HTTP failures retain structured information for host error formatting. The
934
+ * message is plain text suitable for displaying in the Save/Share UI. */
935
+ declare class QuerySnapshotHttpError extends Error {
936
+ readonly status: number;
937
+ readonly code: QuerySnapshotHttpErrorCode;
938
+ readonly detail?: string | undefined;
939
+ readonly serverCode?: string | undefined;
940
+ readonly name = "QuerySnapshotHttpError";
941
+ constructor(status: number, code: QuerySnapshotHttpErrorCode, message: string, detail?: string | undefined, serverCode?: string | undefined);
942
+ }
943
+ declare function httpQuerySnapshotStore(base: string, fetcher?: typeof fetch): QuerySnapshotStore;
944
+ interface QueryLinkService {
945
+ local(query: QueryState, baseUrl: string, parameter?: string): QueryLink;
946
+ durable(query: QueryState, baseUrl: string, parameter?: string): Promise<QueryLink>;
947
+ snapshot(query: QueryState): Promise<QuerySnapshot | null>;
948
+ resolve(url: string, parameter?: string, signal?: AbortSignal): Promise<QueryState | null>;
949
+ }
950
+ /** Local URL synchronization never writes to the server. Only explicit
951
+ * snapshot/durable calls (Save/Share/Bookmark) promote state to the host store. */
952
+ declare function createQueryLinkService(key: string, store?: QuerySnapshotStore, options?: QueryLinkOptions): QueryLinkService;
953
+
954
+ /** Reserved field token namespace. Query state stores IDs, never formula source. */
955
+ declare const COMPUTED_PREFIX = "@computed/";
956
+ declare const computedFieldName: (id: string) => string;
957
+ declare const isComputedField: (name: string) => boolean;
958
+ interface ComputedColumn {
959
+ id: string;
960
+ label: string;
961
+ expression: {
962
+ language: "qt-expr";
963
+ version: 1;
964
+ source: string;
965
+ };
966
+ /** Optimistic concurrency token issued by the store. */
967
+ revision: string;
968
+ }
969
+ type ComputedColumnDraft = Omit<ComputedColumn, "revision">;
970
+ interface ComputedColumnStore {
971
+ /** Return the complete authorized catalogue for this dataset. */
972
+ list(dataset: string, signal?: AbortSignal): Promise<ComputedColumn[]>;
973
+ /** null creates a new ID; updates must match the current revision. */
974
+ save(dataset: string, column: ComputedColumnDraft, expectedRevision: string | null): Promise<ComputedColumn>;
975
+ /** Optional push invalidation, including changes from other sessions. */
976
+ subscribe?(dataset: string, listener: () => void): () => void;
977
+ }
978
+ declare function validateComputedColumn(input: unknown): ComputedColumn;
979
+ declare function memoryComputedColumnStore(seed?: Record<string, ComputedColumn[]>): ComputedColumnStore;
980
+ /** REST adapter. The implementing server owns authorization and DB persistence.
981
+ * GET base?dataset=… → ComputedColumn[]
982
+ * PUT base?dataset=… → {column, expectedRevision} → ComputedColumn
983
+ * Return HTTP 409 for a stale revision. No formula evaluation happens there. */
984
+ declare function httpComputedColumnStore(base: string, fetcher?: typeof fetch): ComputedColumnStore;
985
+ interface ComputedCellError {
986
+ computedError: string;
987
+ }
988
+ declare function isComputedCellError(value: unknown): value is ComputedCellError;
989
+ interface PreviewGroup {
990
+ inputs: FormulaValue[];
991
+ result: FormulaResult;
992
+ count: number;
993
+ }
994
+ interface ColumnPreview {
995
+ dependencies: string[];
996
+ groups: PreviewGroup[];
997
+ processed: number;
998
+ total: number;
999
+ nulls: number;
1000
+ errors: number;
1001
+ }
1002
+ declare function groupPreview(dependencies: string[], inputs: Record<string, FormulaValue>[], results: FormulaResult[], total: number): ColumnPreview;
1003
+ /** Server-issued capabilities are hints for client planning, never authorization. */
1004
+ interface ComputedFieldCapabilities {
1005
+ /** Host-approved canonical predicates; omitted retains the closed default. */
1006
+ filter?: boolean;
1007
+ type: FieldType;
1008
+ select: boolean;
1009
+ sort: boolean;
1010
+ measure: boolean;
1011
+ group: boolean;
1012
+ reason?: string;
1013
+ }
1014
+ interface ComputedExecution {
1015
+ profile: string;
1016
+ planToken: string;
1017
+ resolvedRevisions: Record<string, string>;
1018
+ fields: Record<string, ComputedFieldCapabilities>;
1019
+ fingerprint?: string;
1020
+ snapshot?: string;
1021
+ }
1022
+ type RowComputedValues = Record<string, {
1023
+ value: MetricValue;
1024
+ error?: {
1025
+ code: string;
1026
+ };
1027
+ }>;
1028
+
1029
+ /** Functions available only in aggregate expressions, separate from row formulas. */
1030
+ declare const METRIC_FUNCTIONS: FormulaFunction[];
1031
+ interface MetricPlan extends FormulaPlan {
1032
+ aggregates: Extract<FormulaNode, {
1033
+ kind: "call";
1034
+ }>[];
1035
+ aggregateTypes: FormulaType[];
1036
+ }
1037
+ declare function compileMetric(expression: string, schema: FieldSchema | readonly FieldDef[], resolve?: (name: string) => FormulaPlan | undefined): MetricPlan;
1038
+ /** Row plans for worker evaluation. Null denotes COUNT(), which requires only row count. */
1039
+ declare function metricAggregatePlans(plan: MetricPlan): (FormulaPlan | null)[];
1040
+ /** Reduce exact per-row worker results; errors are not silently treated as NULL. */
1041
+ interface MetricReductionOptions {
1042
+ /** Retained distinct entries, independently bounded from row count. */
1043
+ maxDistinctValues?: number;
1044
+ /** Conservative UTF-16 payload plus per-entry storage estimate. */
1045
+ maxDistinctBytes?: number;
1046
+ }
1047
+ declare function reduceMetricValues(node: Extract<FormulaNode, {
1048
+ kind: "call";
1049
+ }>, results: readonly FormulaResult[], options?: MetricReductionOptions): FormulaResult;
1050
+ /** Evaluate the group AST lazily from reductions indexed like plan.aggregates.
1051
+ * A dead IF/COALESCE branch does not observe a failed aggregate in that branch. */
1052
+ declare function evaluateMetricReductions(plan: MetricPlan, reductions: readonly FormulaResult[]): FormulaResult;
1053
+ declare function metricExpression(clause: AggregationClause): string;
1054
+ declare function hasRegex(plan: FormulaPlan): boolean;
1055
+ declare function metricPlans(c: AggregationClause, schema: FieldSchema, resolve?: (name: string) => FormulaPlan | undefined): FormulaPlan[];
1056
+ declare function validateMetric(c: AggregationClause, schema: FieldSchema, resolve?: (name: string) => FormulaPlan | undefined): FormulaDiagnostic[];
1057
+ declare function metricDependencies(c: AggregationClause, schema: FieldSchema, resolve?: (name: string) => FormulaPlan | undefined): string[];
1058
+ declare function metricComputationKey(clauses: readonly AggregationClause[]): string;
1059
+ declare function toMetricQuery<Row>(q: QueryState, schema: FieldSchema<Row>, execution?: ComputedExecution): MetricQuery;
1060
+ /** Safe pre-worker page selection. A supplied committed page needs no query execution.
1061
+ * Query regex is never executed here; all-matching regex filtering is unsupported. */
1062
+ declare function selectMetricShownRows<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>, options?: MetricEvaluationOptions<Row>): Row[];
1063
+ interface MetricEvaluationOptions<Row = unknown> extends QueryEvaluationOptions, MetricReductionOptions {
1064
+ resolveComputed?: (name: string) => FormulaPlan | undefined;
1065
+ /** Called only for regex row ASTs, using cached results from a terminable worker. */
1066
+ evaluateInput?: (node: FormulaNode, row: Row, plan: FormulaPlan) => FormulaResult;
1067
+ /** Exact committed page supplied by the host after filters/order/window. */
1068
+ /** Escaped tuple length bound, in UTF-16 code units (default 65,536). */
1069
+ maxGroupKeyLength?: number;
1070
+ /** Retained group keys, map/bucket overhead and row references, plus key scratch space (default 16 MB). */
1071
+ maxGroupBytes?: number;
1072
+ shownRows?: Row[];
1073
+ maxRows?: number;
1074
+ maxGroups?: number;
1075
+ /** Per-metric operation budget (default 10,000,000, allowing simple paired
1076
+ * aggregates across the default one-million-row population limit). */
1077
+ maxOperations?: number;
1078
+ /** @deprecated Regex requires cached evaluateInput; this flag grants no execution permission. */ allowRegex?: boolean;
1079
+ signal?: AbortSignal;
1080
+ }
1081
+ /** Typed lexicographic ordering with null placement independent of direction. */
1082
+ declare function compareMetricValues(a: MetricValue, b: MetricValue): number;
1083
+ declare function evaluateMetrics<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>, options?: MetricEvaluationOptions<Row>): AggregationResult;
1084
+
404
1085
  interface ApplyResult<Row> {
405
1086
  /** The page slice (after filter + sort + offset/limit). */
406
1087
  rows: Row[];
@@ -408,17 +1089,99 @@ interface ApplyResult<Row> {
408
1089
  total: number;
409
1090
  }
410
1091
  /** Filter + multi-sort + paginate `rows` per `q`, resolving field types/paths
411
- * from `schema`. Pure; never mutates `rows`. Clauses/sorts on unknown fields are
1092
+ * from `schema`. Never mutates `rows`. Relative predicates use options.now or
1093
+ * one captured Date.now(). Clauses/sorts on unknown fields are
412
1094
  * ignored (the backend already enforced its own allowlist). */
413
- declare function applyQuery<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>): ApplyResult<Row>;
1095
+ declare function applyQuery<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>, options?: QueryEvaluationOptions): ApplyResult<Row>;
414
1096
  /** Does one row satisfy one clause? Exposed for the CellMenu preview + tests. */
415
- declare function matchesClause<Row>(row: Row, clause: WhereClause, schema: FieldSchema<Row>): boolean;
1097
+ declare function matchesClause<Row>(row: Row, clause: WhereClause, schema: FieldSchema<Row>, options?: QueryEvaluationOptions): boolean;
416
1098
  /** Client-side mirror of the backend GROUP BY (the executor for `clientRows`
417
1099
  * mode / the demo). Scope matches the server contract: the WHERE-filtered set
418
1100
  * only — ORDER BY / LIMIT / OFFSET are intentionally ignored, so a metric
419
1101
  * reflects every matching row, not the visible page. Must agree with
420
1102
  * backends/go's aggregate compile on op semantics. */
421
- declare function applyAggregations<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>): AggregationResult;
1103
+ declare function applyAggregations<Row>(rows: Row[], q: QueryState, schema: FieldSchema<Row>, options?: MetricEvaluationOptions<Row>): AggregationResult;
1104
+
1105
+ /** Versioned row projection keeps server-computed payload separate from host row fields. */
1106
+ interface ServerQueryV2 extends ServerQuery {
1107
+ version: 2;
1108
+ profile: string;
1109
+ expectedRevisions: Record<string, string>;
1110
+ planToken?: string;
1111
+ /** Host must enforce this binding against the row response. */
1112
+ snapshot?: string;
1113
+ diagnostics: MetricDiagnostic[];
1114
+ }
1115
+ interface FetchRowsResultV2<Row> {
1116
+ version: 2;
1117
+ rows: Row[];
1118
+ total: number;
1119
+ computed: {
1120
+ id: RowId;
1121
+ values: RowComputedValues;
1122
+ }[];
1123
+ execution: ComputedExecution;
1124
+ }
1125
+ /** Project with a previously resolved server capability envelope. This does not
1126
+ * authorize definitions or upload their source; the backend revalidates revisions. */
1127
+ declare function toServerQueryV2<Row>(q: QueryState, schema: FieldSchema<Row>, execution: ComputedExecution): ServerQueryV2;
1128
+
1129
+ interface QueryCapabilitiesV3 {
1130
+ version: 3;
1131
+ groupedRows: boolean;
1132
+ calendarGrouping: boolean;
1133
+ computedSourceFilters: boolean;
1134
+ regex: boolean;
1135
+ regexSourceFilters?: boolean;
1136
+ regexAggregateExpressions?: boolean;
1137
+ maxRows: number;
1138
+ maxGroups: number;
1139
+ maxPopulation: number;
1140
+ profile: string;
1141
+ }
1142
+ interface QueryRequestV3 {
1143
+ version: 3;
1144
+ profile: string;
1145
+ now: number;
1146
+ expectedRevisions: Record<string, string>;
1147
+ planToken?: string;
1148
+ snapshot?: string;
1149
+ source: ServerQuery;
1150
+ result: ServerQuery;
1151
+ grouping?: GroupingDefinition;
1152
+ rows: boolean;
1153
+ metrics: AggregationClause[];
1154
+ diagnostics: string[];
1155
+ }
1156
+ interface QueryResultV3 {
1157
+ version: 3;
1158
+ now: number;
1159
+ sourceTotal: number;
1160
+ rows: Record<string, unknown>[];
1161
+ total: number;
1162
+ schema: {
1163
+ name: string;
1164
+ idField: string;
1165
+ fields: {
1166
+ name: string;
1167
+ type: string;
1168
+ }[];
1169
+ };
1170
+ metrics: AggregationResult["metrics"];
1171
+ execution: ComputedExecution;
1172
+ }
1173
+ /** The handshake supplies authority, including capabilities for computed keys
1174
+ * absent from the base schema. Labels may be supplied by the editable catalogue. */
1175
+ declare function sourceExecutionSchema<Row>(schema: FieldSchema<Row>, execution?: ComputedExecution, catalogue?: readonly {
1176
+ id: string;
1177
+ label: string;
1178
+ revision: string;
1179
+ }[]): FieldSchema<Row>;
1180
+ declare function toQueryRequestV3<Row>(query: QueryState, baseSchema: FieldSchema<Row>, capabilities: QueryCapabilitiesV3, execution?: ComputedExecution, options?: {
1181
+ now?: number;
1182
+ rows?: boolean;
1183
+ }): QueryRequestV3;
1184
+ declare function validateQueryResultV3(result: QueryResultV3, request: QueryRequestV3, schema: FieldSchema): void;
422
1185
 
423
1186
  interface FetchRowsResult<Row> {
424
1187
  rows: Row[];
@@ -452,6 +1215,11 @@ interface FieldStats {
452
1215
  max?: string | number;
453
1216
  }
454
1217
  interface Transport<Row = any> {
1218
+ queryCapabilities?: QueryCapabilitiesV3;
1219
+ fetchQueryV3?(query: QueryRequestV3, signal?: AbortSignal): Promise<QueryResultV3>;
1220
+ fetchRowsV2?(query: ServerQueryV2, signal?: AbortSignal): Promise<FetchRowsResultV2<Row>>;
1221
+ metricCapabilities?: MetricCapabilities;
1222
+ fetchMetrics?(query: MetricQuery, signal?: AbortSignal): Promise<AggregationResult>;
455
1223
  /** Run a server query. The only required method — a purely client-side table
456
1224
  * can omit a Transport entirely and rely on applyQuery over local rows. */
457
1225
  fetchRows(query: ServerQuery, signal?: AbortSignal): Promise<FetchRowsResult<Row>>;
@@ -473,6 +1241,7 @@ interface Transport<Row = any> {
473
1241
  fetchFieldStats?(fields: string[], signal?: AbortSignal): Promise<Record<string, FieldStats>>;
474
1242
  }
475
1243
  interface SavedQuery {
1244
+ snapshotCode?: string;
476
1245
  id: string;
477
1246
  name: string;
478
1247
  /** Epoch millis; injected by the caller so core stays deterministic/testable. */
@@ -484,6 +1253,8 @@ interface SavedQuery {
484
1253
  * on one origin don't collide. Async to allow backend implementations; the
485
1254
  * browser adapters can still resolve synchronously. */
486
1255
  interface StorageAdapter {
1256
+ /** Optional authenticated server store. Ordinary URL sync never calls it. */
1257
+ snapshots?: QuerySnapshotStore;
487
1258
  loadLast(key: string): Promise<QueryState | null>;
488
1259
  saveLast(key: string, query: QueryState): Promise<void>;
489
1260
  listSaved(key: string): Promise<SavedQuery[]>;
@@ -494,7 +1265,10 @@ interface StorageAdapter {
494
1265
  setDefaultSaved?(key: string, id: string | null): Promise<void>;
495
1266
  /** Persist a named query snapshot. Rejects when a saved query with the same
496
1267
  * name already exists in the key namespace. */
497
- saveNamed(key: string, name: string, query: QueryState, savedAt: number): Promise<SavedQuery>;
1268
+ saveNamed(key: string, name: string, query: QueryState, savedAt: number, snapshotCode?: string): Promise<SavedQuery>;
1269
+ /** Atomically replace a named query's state, preserving its id, name and default
1270
+ * selection. On rejection the original entry must remain unchanged. */
1271
+ updateNamed?(key: string, id: string, query: QueryState, savedAt: number, snapshotCode?: string): Promise<SavedQuery>;
498
1272
  deleteSaved(key: string, id: string): Promise<void>;
499
1273
  }
500
1274
  /** In-memory persistence used by default by the React package. It supports the
@@ -506,119 +1280,6 @@ declare function memoryStorageAdapter(): StorageAdapter;
506
1280
  * datasets. Falls back to no persistence when storage is unavailable. */
507
1281
  declare function localStorageAdapter(): StorageAdapter;
508
1282
 
509
- type FormulaType = Exclude<FieldType, "enum"> | "null";
510
- type FormulaValue = string | number | boolean | string[] | null;
511
- interface FormulaDiagnostic {
512
- message: string;
513
- from: number;
514
- to: number;
515
- }
516
- type FormulaNode = {
517
- kind: "literal";
518
- value: FormulaValue;
519
- from: number;
520
- to: number;
521
- } | {
522
- kind: "field";
523
- name: string;
524
- valueType?: FormulaType;
525
- from: number;
526
- to: number;
527
- } | {
528
- kind: "call";
529
- name: string;
530
- args: FormulaNode[];
531
- from: number;
532
- to: number;
533
- };
534
- interface FormulaPlan {
535
- ast: FormulaNode;
536
- dependencies: string[];
537
- type: FormulaType;
538
- }
539
- interface RegexInspection {
540
- input: string;
541
- start: number;
542
- end: number;
543
- groups: (string | null)[];
544
- }
545
- interface FormulaResult {
546
- value: FormulaValue;
547
- error?: string;
548
- regex?: RegexInspection;
549
- }
550
- interface FormulaFunction {
551
- name: string;
552
- signature: string;
553
- description: string;
554
- min: number;
555
- max: number;
556
- result: FormulaType | "branch";
557
- args: FormulaType[] | "any";
558
- }
559
- declare const FORMULA_FUNCTIONS: FormulaFunction[];
560
- declare class FormulaError extends Error {
561
- from: number;
562
- to: number;
563
- constructor(message: string, from?: number, to?: number);
564
- }
565
- declare function compileFormula(source: string, fields: readonly {
566
- name: string;
567
- type: FieldType;
568
- }[], resolve?: (name: string) => FormulaPlan | undefined): FormulaPlan;
569
- /** Self-contained runtime: serialized into the browser worker, never user code. */
570
- declare function formulaRuntime(ast: FormulaNode, inputs: Record<string, FormulaValue>): FormulaResult;
571
-
572
- /** Reserved field token namespace. Query state stores IDs, never formula source. */
573
- declare const COMPUTED_PREFIX = "@computed/";
574
- declare const computedFieldName: (id: string) => string;
575
- declare const isComputedField: (name: string) => boolean;
576
- interface ComputedColumn {
577
- id: string;
578
- label: string;
579
- expression: {
580
- language: "qt-expr";
581
- version: 1;
582
- source: string;
583
- };
584
- /** Optimistic concurrency token issued by the store. */
585
- revision: string;
586
- }
587
- type ComputedColumnDraft = Omit<ComputedColumn, "revision">;
588
- interface ComputedColumnStore {
589
- /** Return the complete authorized catalogue for this dataset. */
590
- list(dataset: string, signal?: AbortSignal): Promise<ComputedColumn[]>;
591
- /** null creates a new ID; updates must match the current revision. */
592
- save(dataset: string, column: ComputedColumnDraft, expectedRevision: string | null): Promise<ComputedColumn>;
593
- /** Optional push invalidation, including changes from other sessions. */
594
- subscribe?(dataset: string, listener: () => void): () => void;
595
- }
596
- declare function validateComputedColumn(input: unknown): ComputedColumn;
597
- declare function memoryComputedColumnStore(seed?: Record<string, ComputedColumn[]>): ComputedColumnStore;
598
- /** REST adapter. The implementing server owns authorization and DB persistence.
599
- * GET base?dataset=… → ComputedColumn[]
600
- * PUT base?dataset=… → {column, expectedRevision} → ComputedColumn
601
- * Return HTTP 409 for a stale revision. No formula evaluation happens there. */
602
- declare function httpComputedColumnStore(base: string, fetcher?: typeof fetch): ComputedColumnStore;
603
- interface ComputedCellError {
604
- computedError: string;
605
- }
606
- declare function isComputedCellError(value: unknown): value is ComputedCellError;
607
- interface PreviewGroup {
608
- inputs: FormulaValue[];
609
- result: FormulaResult;
610
- count: number;
611
- }
612
- interface ColumnPreview {
613
- dependencies: string[];
614
- groups: PreviewGroup[];
615
- processed: number;
616
- total: number;
617
- nulls: number;
618
- errors: number;
619
- }
620
- declare function groupPreview(dependencies: string[], inputs: Record<string, FormulaValue>[], results: FormulaResult[], total: number): ColumnPreview;
621
-
622
1283
  /** Compile an explicitly identified editor into existing CNF predicates. */
623
1284
  declare function createSetFilter(field: string, mode: SetFilterMode, values: string[], id: string): WhereTerm[];
624
1285
  /** Remove presentation metadata at transport boundaries, including inner literals. */
@@ -632,4 +1293,63 @@ declare function readSetFilter(where: WhereTerm[], index: number): {
632
1293
  /** Canonicalize consumer-owned value aliases without disconnecting editor metadata. */
633
1294
  declare function mapSetFilterValues(where: WhereTerm[], canonicalize: (field: string, value: string) => string): WhereTerm[];
634
1295
 
635
- export { AGG_OPS_BY_TYPE, AGG_OPS_NEEDING_FIELD, type AggOp, type AggregateConfig, type AggregationBucket, type AggregationClause, type AggregationRequest, type AggregationResult, type AggregationResultEntry, type Align, type ApplyResult, type BackendField, COMPUTED_PREFIX, type CellRenderer, type ColumnPreview, type ComputedCellError, type ComputedColumn, type ComputedColumnDraft, type ComputedColumnStore, type DerivedField, type DistinctValuesQuery, type DistinctValuesResult, EMPTY_QUERY, FORMULA_FUNCTIONS, type FetchRowsResult, type FieldDef, type FieldSchema, type FieldSource, type FieldStats, type FieldType, type FilterConfig, type FilterOp, type FilterValues, type FormulaDiagnostic, FormulaError, type FormulaFunction, type FormulaNode, type FormulaPlan, type FormulaResult, type FormulaType, type FormulaValue, MAX_AGGREGATIONS, MAX_GROUP_BY_FIELDS, MAX_ORDER_BY_TERMS, MAX_QUERY_LIMIT, MAX_QUERY_OFFSET, MAX_QUERY_TOKEN_LENGTH, MAX_SELECT_COLUMNS, MAX_WHERE_CLAUSES, NULLARY_OPS, OPS_BY_TYPE, type OpChoice, type OpPair, type OrGroup, type OrderByClause, type PreviewGroup, type QueryState, type RegexInspection, type RowId, type SavedQuery, type SelectColumn, type SelectConfig, type ServerQuery, type SetFilterMetadata, type SetFilterMode, type SortConfig, type StorageAdapter, type Transport, type WhereClause, type WhereTerm, aggOpAllowedForType, aggOpNeedsField, aggOpsForField, applyAggregations, applyQuery, coerceValue, compileFormula, computedFieldName, createSetFilter, decodeQuery, encodeQuery, filterValues, formulaRuntime, groupPreview, httpComputedColumnStore, indexFields, isComputedCellError, isComputedField, isFilterable, isGroupable, isMeasurable, isNegativePredicate, isOrGroup, isPushdownFilter, isSelectable, isSortable, loadSchema, localStorageAdapter, mapSetFilterValues, matchesClause, memoryComputedColumnStore, memoryStorageAdapter, negateClause, normalizeQueryState, opAllowedForType, opPairsForField, opsForField, predicatesOf, queriesEqual, readFieldValue, readSetFilter, selectedFields, stripSetFilter, toAggregationQuery, toServerQuery, validateComputedColumn };
1296
+ declare function percentile(sorted: number[], p: number): number | null;
1297
+ declare function boxSummary(values: number[], whiskers?: "minmax" | "tukey"): {
1298
+ n: number;
1299
+ min: number;
1300
+ q1: number;
1301
+ median: number;
1302
+ q3: number;
1303
+ max: number;
1304
+ mean: number;
1305
+ low: number;
1306
+ high: number;
1307
+ outliers: number[];
1308
+ outlierCount: number;
1309
+ whiskers: "minmax" | "tukey";
1310
+ method: "exact-linear";
1311
+ } | null;
1312
+ declare function histogramEdges(values: number[], bins: number): number[];
1313
+ declare function histogramCounts(values: number[], edges: number[]): number[];
1314
+
1315
+ declare const METRIC_DATE_INTERVALS: readonly ["hour", "day", "week", "month", "year"];
1316
+ /** Strict calendar input; timestamps require an explicit offset. */
1317
+ declare function metricDateTime(value: MetricValue, timeZone?: string): number;
1318
+ declare function metricDateFloor(time: number, interval: MetricDateGrouping["interval"], timeZone?: string): number;
1319
+ declare function metricDateNext(time: number, interval: MetricDateGrouping["interval"], timeZone?: string): number;
1320
+ declare function validateDateGrouping(config: MetricDateGrouping): void;
1321
+ /** Only conjunctive bounds prove a domain; OR alternatives are not bounds. */
1322
+ declare function metricDateRange(config: MetricDateGrouping, query: QueryState, observed: readonly number[], now: number, canonical: (field: string) => string): [number, number] | undefined;
1323
+
1324
+ interface SavedPropertyView {
1325
+ id: string;
1326
+ name: string;
1327
+ savedAt: number;
1328
+ view: PropertyViewState;
1329
+ }
1330
+ interface PropertyViewStorage {
1331
+ loadLast(objectType: string): Promise<PropertyViewState | null>;
1332
+ saveLast(objectType: string, view: PropertyViewState): Promise<void>;
1333
+ listSaved(objectType: string): Promise<SavedPropertyView[]>;
1334
+ saveNamed(objectType: string, name: string, view: PropertyViewState, savedAt: number): Promise<SavedPropertyView>;
1335
+ deleteSaved(objectType: string, id: string): Promise<void>;
1336
+ loadDefaultSaved(objectType: string): Promise<SavedPropertyView | null>;
1337
+ setDefaultSaved(objectType: string, id: string | null): Promise<void>;
1338
+ }
1339
+ /** The JSON-backed factory also supports application-owned synchronous preferences. */
1340
+ declare function jsonPropertyViewStorage(read: (key: string) => string | null, write: (key: string, value: string) => void): PropertyViewStorage;
1341
+ declare function memoryPropertyViewStorage(): PropertyViewStorage;
1342
+ /** Durable browser persistence is explicitly opt-in, independently of table storage. */
1343
+ declare function localPropertyViewStorage(): PropertyViewStorage;
1344
+
1345
+ interface SourceDependencies {
1346
+ fields: string[];
1347
+ baseFields: string[];
1348
+ identity: string;
1349
+ paths: Record<string, string>;
1350
+ }
1351
+ /** Source inputs are distinct from result/HAVING aliases. Includes hidden ROW
1352
+ * inputs and transitive computed/derived dependencies, even when not selected. */
1353
+ declare function sourceDependencies<Row>(query: QueryState, schema: FieldSchema<Row>, resolveComputed?: (name: string) => FormulaPlan | undefined): SourceDependencies;
1354
+
1355
+ export { AGG_OPS_BY_TYPE, AGG_OPS_NEEDING_FIELD, type AggOp, type AggregateConfig, type AggregationBucket, type AggregationClause, type AggregationRequest, type AggregationResult, type AggregationResultEntry, type Align, type ApplyResult, type BackendField, COMPUTED_PREFIX, type CellDisplay, type CellRenderer, type ColumnPreview, type ComputedCellError, type ComputedColumn, type ComputedColumnDraft, type ComputedColumnStore, type ComputedExecution, type ComputedFieldCapabilities, type DerivedField, type DistinctValuesQuery, type DistinctValuesResult, EMPTY_QUERY, FORMULA_FUNCTIONS, type FetchRowsResult, type FetchRowsResultV2, type FieldDef, type FieldOrigin, type FieldRelationship, type FieldSchema, type FieldSource, type FieldStats, type FieldType, type FilterConfig, type FilterOp, type FilterValues, type ForeignKey, type FormulaDiagnostic, FormulaError, type FormulaFunction, type FormulaNode, type FormulaPlan, type FormulaResult, type FormulaType, type FormulaValue, GROUP_ID_FIELD, type GroupedRow, type GroupingDefinition, type GroupingModel, type GroupingState, type GroupingView, ITEM_DETAILS_COLUMN, type ItemDetailMode, type ItemDetailsState, type ItemPageOptions, type ItemPropertyMode, type JsonCellDisplay, MAX_AGGREGATIONS, MAX_GROUP_BY_FIELDS, MAX_ORDER_BY_TERMS, MAX_PROPERTIES, MAX_QUERY_LIMIT, MAX_QUERY_OFFSET, MAX_QUERY_STATE_BYTES, MAX_QUERY_TOKEN_LENGTH, MAX_RELATIVE_TIME_MS, MAX_REPRESENTATIVE_SORTS, MAX_RESULT_GROUPS, MAX_SELECT_COLUMNS, MAX_SUMMARY_COLUMNS, MAX_WHERE_CLAUSES, METRIC_DATE_INTERVALS, METRIC_FUNCTIONS, type MetricAxisScale, type MetricBoxSummary, type MetricCapabilities, type MetricDateGrouping, type MetricDiagnostic, type MetricDisplay, type MetricDistribution, type MetricDistributionResult, type MetricEvaluationOptions, type MetricKey, type MetricLayout, type MetricPlan, type MetricQuery, type MetricReductionOptions, type MetricScope, type MetricSort, type MetricValue, type MetricValueFormat, NULLARY_OPS, OPS_BY_TYPE, OP_LABEL, type ObjectReference, type ObjectTypeDefinition, type OpChoice, type OpPair, type OrGroup, type OrderByClause, type PreviewGroup, type PropertyFieldConfig, type PropertyFormat, type PropertySelection, type PropertyViewState, type PropertyViewStorage, type QueryCapabilitiesV3, QueryDecodeError, type QueryEvaluationOptions, type QueryLink, type QueryLinkOptions, type QueryLinkService, type QueryRequestV3, type QueryResultV3, type QuerySnapshot, type QuerySnapshotCache, QuerySnapshotHttpError, type QuerySnapshotHttpErrorCode, type QuerySnapshotStore, type QueryState, type RegexInspection, type RelationshipAdapter, type RepresentativeOrder, type RepresentativeRow, type ResolvedObjectReference, type RowComputedValues, type RowId, type SavedPropertyView, type SavedQuery, type SelectColumn, type SelectConfig, type ServerQuery, type ServerQueryV2, type SetFilterMetadata, type SetFilterMode, type SortConfig, type SourceDependencies, type StorageAdapter, type SummaryColumn, type SummaryOp, type Transport, type WhereClause, type WhereTerm, aggOpAllowedForType, aggOpNeedsField, aggOpsForField, applyAggregations, applyQuery, boxSummary, coerceValue, compareMetricValues, compileFormula, compileMetric, computedFieldName, createGroupingModel, createPropertyView, createQueryLinkService, createSetFilter, decodeQuery, decodeQueryStrict, discoverFieldRelationships, encodeQuery, evaluateMetricReductions, evaluateMetrics, filterValues, formulaRuntime, groupPreview, hasRegex, histogramCounts, histogramEdges, httpComputedColumnStore, httpQuerySnapshotStore, indexFields, isComputedCellError, isComputedField, isFilterable, isGroupable, isMeasurable, isNegativePredicate, isOrGroup, isPushdownFilter, isSelectable, isSortable, itemPageFields, jsonPropertyViewStorage, loadSchema, localPropertyViewStorage, localQuerySnapshotCache, localStorageAdapter, mapSetFilterValues, matchesClause, memoryComputedColumnStore, memoryPropertyViewStorage, memoryQuerySnapshotCache, memoryStorageAdapter, metricAggregatePlans, metricComputationKey, metricDateFloor, metricDateNext, metricDateRange, metricDateTime, metricDependencies, metricExpression, metricPlans, negateClause, normalizePropertyFormat, normalizePropertyView, normalizeQueryState, objectReferenceKey, opAllowedForType, opPairsForField, opsForField, parseRelativeDuration, percentile, predicatesOf, prepareGrouping, queriesEqual, readFieldValue, readObjectReference, readSetFilter, reduceMetricValues, renderItemPageTemplate, resolveFieldName, selectMetricShownRows, selectedFields, sourceDependencies, sourceExecutionSchema, stripSetFilter, toAggregationQuery, toMetricQuery, toQueryRequestV3, toServerQuery, toServerQueryV2, validateComputedColumn, validateDateGrouping, validateItemPageTemplate, validateMetric, validateQueryResultV3, validateQuerySnapshot, withoutGrouping };