@pythia-software/query-table-core 0.6.0 → 0.7.1

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/README.md CHANGED
@@ -3,6 +3,10 @@
3
3
  Framework-agnostic query state, schema contracts, filtering, sorting,
4
4
  aggregation, serialization, and transport/storage interfaces for query-table.
5
5
 
6
+ Also exports object property view definitions, normalization/defaults, separate
7
+ property storage adapters, and foreign-key relationship mapping contracts. See
8
+ [object property views](../../docs/object-properties.md).
9
+
6
10
  ## Install
7
11
 
8
12
  ```bash
@@ -40,6 +44,17 @@ Query state is bounded whenever it crosses the library's URL, storage, or
40
44
  server-projection boundaries. SQL expressions remain trusted server-side schema
41
45
  configuration; request values are never SQL fragments.
42
46
 
47
+ ## Compact URLs and query snapshots
48
+
49
+ `encodeQuery` chooses versioned JSON or raw-DEFLATE tokens, packs intact set
50
+ filters once, and shares identical property layouts. `decodeQueryStrict` reports
51
+ invalid/unsupported/oversized links; `decodeQuery` is the tolerant convenience
52
+ decoder. `createQueryLinkService` keeps fitting views inline and oversized views
53
+ in a bounded FIFO browser cache. Its explicit `durable`/`snapshot` calls promote
54
+ immutable codes through an injected `QuerySnapshotStore`; ordinary `local` calls
55
+ never write to the server. `httpQuerySnapshotStore` implements the host REST
56
+ contract. See [URL state and saved-search links](../../docs/url-state-and-saved-search-design.md).
57
+
43
58
  Text filters include `matches_regex` / `not_matches_regex`. An order term may
44
59
  set `extract: { regex: "..." }` to compare the first capture group (or the whole
45
60
  match when there is no capture group); non-matches sort as nulls.
@@ -110,3 +125,10 @@ PostgreSQL numeric and also conservatively bounds SUM's absolute population sum.
110
125
  Do not claim identical acceptance, intermediate overflow, or bit-for-bit numeric
111
126
  parity; negotiate the backend profile and compare supported floating results
112
127
  with tolerances. The host must enforce the negotiated profile and execution budgets.
128
+
129
+ ## Cell presentation
130
+
131
+ `FieldDef.display` accepts the serializable `CellDisplay` union for JSON, temporal,
132
+ duration, numeric, text, and chip presentation. It is independent of `FieldType`
133
+ and backend query capabilities. `loadSchema` validates display options, and
134
+ codegen preserves them in frontend schemas. See the [cell display guide](../../docs/cell-displays.md).
package/dist/index.d.ts CHANGED
@@ -1,3 +1,50 @@
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;
15
+ }
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
+ };
47
+
1
48
  type FieldType = "text" | "number" | "enum" | "datetime" | "bool" | "textarray";
2
49
  type Align = "left" | "right" | "center";
3
50
  /** A cell renderer is resolved by name against a `RenderRegistry` (in
@@ -101,7 +148,14 @@ interface FieldDef<Row = any, V = unknown> {
101
148
  group?: string;
102
149
  alias?: string;
103
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;
104
154
  render?: string | CellRenderer<Row, V>;
155
+ property?: PropertyFieldConfig;
156
+ /** Structured provenance for relationship discovery; never SQL text. */
157
+ origin?: FieldOrigin;
158
+ relationship?: FieldRelationship;
105
159
  }
106
160
  /** The frontend's loaded schema for one dataset. */
107
161
  interface FieldSchema<Row = any> {
@@ -209,6 +263,17 @@ declare function formulaRuntime(ast: FormulaNode, inputs: Record<string, Formula
209
263
  type MetricValue = number | string | boolean | null;
210
264
  type MetricKey = MetricValue;
211
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
+ }
212
277
  interface MetricValueFormat {
213
278
  kind: "number" | "percent" | "duration" | "date" | "time" | "datetime";
214
279
  decimals?: number;
@@ -232,10 +297,18 @@ interface MetricDisplay {
232
297
  xFormat?: MetricValueFormat;
233
298
  yFormat?: MetricValueFormat;
234
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;
235
304
  list?: {
236
305
  showBars?: boolean;
237
306
  useGroupColors?: boolean;
238
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;
239
312
  };
240
313
  pivot?: {
241
314
  swap?: boolean;
@@ -315,6 +388,170 @@ interface MetricCapabilities {
315
388
  profile?: string;
316
389
  }
317
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
+
318
555
  /** All filter operators across both source projects, unioned. Every value can
319
556
  * be NULL, so `is_null`/`is_not_null` are valid for every field type. */
320
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";
@@ -402,6 +639,7 @@ type AggOp = "count" | "count_distinct" | "sum" | "avg" | "min" | "max";
402
639
  * page. Lives inside QueryState so it round-trips through `?q=`, saved queries,
403
640
  * and undo/redo for free — a saved query is a saved dashboard. */
404
641
  interface AggregationClause {
642
+ dateGrouping?: MetricDateGrouping;
405
643
  expression?: string;
406
644
  expressionY?: string;
407
645
  scope?: MetricScope;
@@ -424,9 +662,39 @@ interface AggregationClause {
424
662
  /** Panel header override; defaults to a derived label like "avg total". */
425
663
  label?: string;
426
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
+ }
427
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;
428
693
  /** Ordered SELECT list + per-column widths. Empty = the schema's default columns. */
429
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;
430
698
  /** WHERE in conjunctive normal form: the AND of these terms, each a single
431
699
  * predicate or an OR group. A flat list of literals (no groups) is the common
432
700
  * case and the legacy shape, decoded unchanged. */
@@ -468,6 +736,11 @@ declare function normalizeQueryState(input: unknown, fallback?: QueryState): Que
468
736
  * refetch is actually needed (avoids history spam / redundant fetches). */
469
737
  declare function queriesEqual(a: QueryState, b: QueryState): boolean;
470
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;
743
+
471
744
  /** The logical negation of a predicate. Prefers flipping to the complementary
472
745
  * operator (nicer to read, and the server's op allowlist already covers it);
473
746
  * falls back to toggling the `negated` flag when the op has no complement, or
@@ -534,11 +807,18 @@ declare function isMeasurable(field: FieldDef): boolean;
534
807
  * default off, but `aggregate.groupable: true` opts any backend field in. */
535
808
  declare function isGroupable(field: FieldDef): boolean;
536
809
 
537
- /** 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. */
538
816
  declare function encodeQuery(q: QueryState): string;
539
817
  /** base64url token → QueryState. Tolerant: bad input and legacy shapes both
540
818
  * normalize to a valid QueryState (never throws). */
541
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;
542
822
  /** The backend request derived from a QueryState + its schema. Mirrors the Go
543
823
  * `WireQuery`. View-only state (column widths) is intentionally absent. */
544
824
  interface ServerQuery {
@@ -608,6 +888,69 @@ interface AggregationResult {
608
888
  * unknown field are dropped — the server has no SQL for them. */
609
889
  declare function toAggregationQuery<Row>(q: QueryState, schema: FieldSchema<Row>): AggregationRequest;
610
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
+
611
954
  /** Reserved field token namespace. Query state stores IDs, never formula source. */
612
955
  declare const COMPUTED_PREFIX = "@computed/";
613
956
  declare const computedFieldName: (id: string) => string;
@@ -659,6 +1002,8 @@ interface ColumnPreview {
659
1002
  declare function groupPreview(dependencies: string[], inputs: Record<string, FormulaValue>[], results: FormulaResult[], total: number): ColumnPreview;
660
1003
  /** Server-issued capabilities are hints for client planning, never authorization. */
661
1004
  interface ComputedFieldCapabilities {
1005
+ /** Host-approved canonical predicates; omitted retains the closed default. */
1006
+ filter?: boolean;
662
1007
  type: FieldType;
663
1008
  select: boolean;
664
1009
  sort: boolean;
@@ -681,18 +1026,8 @@ type RowComputedValues = Record<string, {
681
1026
  };
682
1027
  }>;
683
1028
 
684
- /** Portable datetime/offset bound in milliseconds (also below MAX_SAFE_INTEGER). */
685
- declare const MAX_RELATIVE_TIME_MS = 8640000000000000;
686
- /** A signed elapsed offset, e.g. -1h or +8d2h10m. The sign applies to the
687
- * complete sum. Returns null for absolute values or invalid duration syntax.
688
- * Units are lowercase; amounts are whole integers. No whitespace, calendar
689
- * arithmetic, locale, or Date.parse heuristics are used. */
690
- declare function parseRelativeDuration(value: string): number | null;
691
- /** Local execution only. The server independently owns its execution clock. */
692
- interface QueryEvaluationOptions {
693
- now?: number;
694
- }
695
-
1029
+ /** Functions available only in aggregate expressions, separate from row formulas. */
1030
+ declare const METRIC_FUNCTIONS: FormulaFunction[];
696
1031
  interface MetricPlan extends FormulaPlan {
697
1032
  aggregates: Extract<FormulaNode, {
698
1033
  kind: "call";
@@ -791,6 +1126,63 @@ interface FetchRowsResultV2<Row> {
791
1126
  * authorize definitions or upload their source; the backend revalidates revisions. */
792
1127
  declare function toServerQueryV2<Row>(q: QueryState, schema: FieldSchema<Row>, execution: ComputedExecution): ServerQueryV2;
793
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;
1185
+
794
1186
  interface FetchRowsResult<Row> {
795
1187
  rows: Row[];
796
1188
  /** Total matching rows before pagination (for "N of M" + paging bounds). */
@@ -823,6 +1215,8 @@ interface FieldStats {
823
1215
  max?: string | number;
824
1216
  }
825
1217
  interface Transport<Row = any> {
1218
+ queryCapabilities?: QueryCapabilitiesV3;
1219
+ fetchQueryV3?(query: QueryRequestV3, signal?: AbortSignal): Promise<QueryResultV3>;
826
1220
  fetchRowsV2?(query: ServerQueryV2, signal?: AbortSignal): Promise<FetchRowsResultV2<Row>>;
827
1221
  metricCapabilities?: MetricCapabilities;
828
1222
  fetchMetrics?(query: MetricQuery, signal?: AbortSignal): Promise<AggregationResult>;
@@ -847,6 +1241,7 @@ interface Transport<Row = any> {
847
1241
  fetchFieldStats?(fields: string[], signal?: AbortSignal): Promise<Record<string, FieldStats>>;
848
1242
  }
849
1243
  interface SavedQuery {
1244
+ snapshotCode?: string;
850
1245
  id: string;
851
1246
  name: string;
852
1247
  /** Epoch millis; injected by the caller so core stays deterministic/testable. */
@@ -858,6 +1253,8 @@ interface SavedQuery {
858
1253
  * on one origin don't collide. Async to allow backend implementations; the
859
1254
  * browser adapters can still resolve synchronously. */
860
1255
  interface StorageAdapter {
1256
+ /** Optional authenticated server store. Ordinary URL sync never calls it. */
1257
+ snapshots?: QuerySnapshotStore;
861
1258
  loadLast(key: string): Promise<QueryState | null>;
862
1259
  saveLast(key: string, query: QueryState): Promise<void>;
863
1260
  listSaved(key: string): Promise<SavedQuery[]>;
@@ -868,7 +1265,10 @@ interface StorageAdapter {
868
1265
  setDefaultSaved?(key: string, id: string | null): Promise<void>;
869
1266
  /** Persist a named query snapshot. Rejects when a saved query with the same
870
1267
  * name already exists in the key namespace. */
871
- 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>;
872
1272
  deleteSaved(key: string, id: string): Promise<void>;
873
1273
  }
874
1274
  /** In-memory persistence used by default by the React package. It supports the
@@ -912,4 +1312,44 @@ declare function boxSummary(values: number[], whiskers?: "minmax" | "tukey"): {
912
1312
  declare function histogramEdges(values: number[], bins: number): number[];
913
1313
  declare function histogramCounts(values: number[], edges: number[]): number[];
914
1314
 
915
- 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 ComputedExecution, type ComputedFieldCapabilities, type DerivedField, type DistinctValuesQuery, type DistinctValuesResult, EMPTY_QUERY, FORMULA_FUNCTIONS, type FetchRowsResult, type FetchRowsResultV2, 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_RELATIVE_TIME_MS, MAX_SELECT_COLUMNS, MAX_WHERE_CLAUSES, type MetricAxisScale, type MetricBoxSummary, type MetricCapabilities, 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, type OpChoice, type OpPair, type OrGroup, type OrderByClause, type PreviewGroup, type QueryEvaluationOptions, type QueryState, type RegexInspection, type RowComputedValues, type RowId, type SavedQuery, type SelectColumn, type SelectConfig, type ServerQuery, type ServerQueryV2, type SetFilterMetadata, type SetFilterMode, type SortConfig, type StorageAdapter, type Transport, type WhereClause, type WhereTerm, aggOpAllowedForType, aggOpNeedsField, aggOpsForField, applyAggregations, applyQuery, boxSummary, coerceValue, compareMetricValues, compileFormula, compileMetric, computedFieldName, createSetFilter, decodeQuery, encodeQuery, evaluateMetricReductions, evaluateMetrics, filterValues, formulaRuntime, groupPreview, hasRegex, histogramCounts, histogramEdges, httpComputedColumnStore, indexFields, isComputedCellError, isComputedField, isFilterable, isGroupable, isMeasurable, isNegativePredicate, isOrGroup, isPushdownFilter, isSelectable, isSortable, loadSchema, localStorageAdapter, mapSetFilterValues, matchesClause, memoryComputedColumnStore, memoryStorageAdapter, metricAggregatePlans, metricComputationKey, metricDependencies, metricExpression, metricPlans, negateClause, normalizeQueryState, opAllowedForType, opPairsForField, opsForField, parseRelativeDuration, percentile, predicatesOf, queriesEqual, readFieldValue, readSetFilter, reduceMetricValues, resolveFieldName, selectMetricShownRows, selectedFields, stripSetFilter, toAggregationQuery, toMetricQuery, toServerQuery, toServerQueryV2, validateComputedColumn, validateMetric };
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 };