@pythia-software/query-table-react 0.6.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/README.md CHANGED
@@ -3,6 +3,20 @@
3
3
  Headless React hooks for query-table: query state, fetching, URL/storage sync,
4
4
  selection, saved queries, aggregations, and column interactions.
5
5
 
6
+ `usePropertyView` controls saved read-only presentations per object type;
7
+ `useObjectReferences` batches cancellable relationship lookups. See
8
+ [object property views](../../docs/object-properties.md).
9
+
10
+ ## Grouped rows
11
+
12
+ `useQueryTable({ schema, clientRows })` enables one row-producing grouping stage
13
+ for complete local datasets. The standard query builder supplies GROUP BY,
14
+ HAVING, aggregate column editing and representative-row ordering. Use
15
+ `api.schema`, `api.visibleFields` and `api.rowId` for the active result shape;
16
+ set `grouping: false` to opt out. Backend transports currently reject grouped
17
+ row queries. See [grouping integration](../../docs/grouping.md) for the query
18
+ shape, source-stage controls, saved state and execution boundaries.
19
+
6
20
  ## Install
7
21
 
8
22
  ```bash
@@ -20,6 +34,20 @@ function Orders({ schema, rows }) {
20
34
  }
21
35
  ```
22
36
 
37
+ Row selection supports editable ranges. `selection.toggle(id)` toggles a row
38
+ and starts a range; `selection.toggle(id, true)` grows or shrinks it from that
39
+ fixed anchor, preserving earlier selections. If the anchor was unchecked,
40
+ the range unchecks rows instead, restoring their previous checks when shrunk.
41
+ `selection.toggle(id, true, true)` adds the span to the current selection and
42
+ commits existing checks before subsequent range adjustments. The existing
43
+ one- and two-argument calls remain supported.
44
+
45
+ Ranges use the current page's complete displayed order, including virtualized
46
+ rows outside the viewport. Changes to that order or page reset the active range
47
+ while retaining selected IDs; refreshing the same ordered IDs retains it.
48
+ Page-wide selection changes, `clear`, `replace`, and `retain` reset the range.
49
+ Shift without a valid anchor behaves like an ordinary toggle.
50
+
23
51
  `table.selection.replace(ids)` atomically makes any iterable of stable row IDs
24
52
  the complete selection, including IDs outside the displayed page.
25
53
  `table.selection.retain(ids)` atomically intersects the current selection with
@@ -39,6 +67,28 @@ history, referrers, logs, analytics, bookmarks, and screenshots. Pass
39
67
  is appropriate; sensitive applications should use an access-controlled server
40
68
  `StorageAdapter`.
41
69
 
70
+ With `syncUrl: true`, oversized queries use a browser-local FIFO cache and a
71
+ temporary short URL. Configure its size/budget with `links: { cache,
72
+ maxUrlBytes }`; the default complete URL budget is 2,000 bytes. Local links
73
+ can expire through cache eviction and do not transfer with browser bookmark sync.
74
+ Configure `storage.snapshots: httpQuerySnapshotStore("/api/query-snapshots")`
75
+ for permanent Save/Share links. `api.links.create()` also supports a host's explicit
76
+ Bookmark action. Normal edits never write snapshots to the server. Missing
77
+ references fail visibly before rows/metrics execute. See the
78
+ [link integration contract](../../docs/url-state-and-saved-search-design.md).
79
+
80
+ `api.saved.update(id)` persists the current snapshot before replacing the saved
81
+ search through `storage.updateNamed`. The adapter must update atomically and
82
+ retain the saved ID and default selection; failed updates preserve the original.
83
+ Both built-in adapters support updates. For custom adapters, QueryBuilder offers
84
+ Update only when `api.saved.canUpdate` is true. Optional last/default storage read
85
+ failures fall back to table defaults; explicit broken URL links still block
86
+ execution. A single empty `?q=` is treated as an absent query.
87
+
88
+ Item-detail property views hydrate independently, but synchronize into the
89
+ collection query only after URL/saved-query restoration settles. Detail layouts
90
+ encoded in the restored query take precedence over stored property views.
91
+
42
92
  See the [repository README](https://github.com/Pythia-Software/query-table#readme)
43
93
  for transport and schema examples.
44
94
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,125 @@
1
- import { FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, RowId, SelectColumn, QueryState, FieldSchema, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, WhereClause, DistinctValuesResult, FieldStats, OrderByClause, Transport, ComputedColumnStore, ComputedExecution } from '@pythia-software/query-table-core';
1
+ import { QueryState, GroupedRow, GroupingModel, FieldSchema, GroupingView, SummaryOp, RepresentativeRow, SummaryColumn, SelectColumn, PropertyViewState, SavedPropertyView, PropertyViewStorage, QueryLink, QuerySnapshot, ItemPropertyMode, ItemPageOptions, ItemDetailMode, ItemDetailsState, RelationshipAdapter, RowId, FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, WhereClause, DistinctValuesResult, FieldStats, OrderByClause, Transport, ComputedColumnStore, ComputedExecution, QueryLinkOptions, ObjectReference, ResolvedObjectReference } from '@pythia-software/query-table-core';
2
+
3
+ /** Filter controls for either the result query or grouping's source query. */
4
+ type QueryFilterApi<Row> = Pick<QueryTableApi<Row>, "query" | "setQuery" | "loading" | "addFilter" | "updateFilter" | "removeFilter" | "clearFilters" | "updatePredicate" | "removePredicate" | "negatePredicate" | "reorderFilters" | "mergeFilters" | "filterValues" | "fieldStats">;
5
+
6
+ /** Source-stage controls. Rows/columns/metrics remain on the active result API. */
7
+ type GroupingSourceApi<Row> = QueryFilterApi<Row> & Pick<QueryTableApi<Row>, "itemDetails" | "rowId"> & {
8
+ schema: FieldSchema<Row>;
9
+ };
10
+ interface GroupingApi<Row> {
11
+ inputsReady?: boolean;
12
+ preview?: (query: QueryState, signal?: AbortSignal) => Promise<{
13
+ rows: GroupedRow[];
14
+ sourceTotal: number;
15
+ total: number;
16
+ }>;
17
+ supported: boolean;
18
+ model: GroupingModel<Row>;
19
+ api: QueryTableApi<Row>;
20
+ source: GroupingSourceApi<Row>;
21
+ grouped: QueryTableApi<Row>;
22
+ schema: FieldSchema<Row>;
23
+ view: GroupingView;
24
+ inputs: Row[];
25
+ notice: string;
26
+ setNotice: (notice: string) => void;
27
+ changeKeys: (keys: string[]) => void;
28
+ addSummary: (op: SummaryOp, field?: string) => void;
29
+ changeSummary: (id: string, op: SummaryOp) => void;
30
+ changeRepresentative: (rule: RepresentativeRow | null) => void;
31
+ representativeImpact: () => string;
32
+ applyColumns: (summaries: SummaryColumn[], columns: SelectColumn[]) => void;
33
+ }
34
+
35
+ interface UsePropertyViewOptions<Row> {
36
+ schema: FieldSchema<Row>;
37
+ /** Object type, never object ID. Defaults to schema.name. */
38
+ key?: string;
39
+ storage?: PropertyViewStorage;
40
+ initialView?: PropertyViewState;
41
+ }
42
+ interface PropertyViewApi {
43
+ view: PropertyViewState;
44
+ loading: boolean;
45
+ error: Error | null;
46
+ saved: SavedPropertyView[];
47
+ defaultId: string | null;
48
+ setView(view: PropertyViewState): void;
49
+ reset(): void;
50
+ undo(): void;
51
+ redo(): void;
52
+ canUndo: boolean;
53
+ canRedo: boolean;
54
+ load(id: string): void;
55
+ save(name: string): Promise<SavedPropertyView>;
56
+ remove(id: string): Promise<void>;
57
+ setDefault(id: string | null): Promise<void>;
58
+ }
59
+ /** Owns presentation only. Changing the inspected row never resets this controller. */
60
+ declare function usePropertyView<Row>(options: UsePropertyViewOptions<Row>): PropertyViewApi;
61
+
62
+ interface QueryLinksApi {
63
+ maxUrlBytes: number;
64
+ kind: QueryLink["kind"] | null;
65
+ busy: boolean;
66
+ error: Error | null;
67
+ /** Explicit Share/Bookmark: server snapshot if configured, otherwise a fitting inline link. */
68
+ create(query?: QueryState, options?: {
69
+ baseUrl?: string;
70
+ parameter?: string;
71
+ }): Promise<QueryLink>;
72
+ /** Synchronous local-only URL for navigation, never a server write. */
73
+ local(query: QueryState, baseUrl?: string, parameter?: string): QueryLink;
74
+ /** Save uses the same immutable snapshot and promotion path as Share. */
75
+ snapshot(query: QueryState): Promise<QuerySnapshot | null>;
76
+ }
77
+
78
+ interface ItemDetailRecord<Row> {
79
+ row: Row;
80
+ /** Omit for complete records. Supply for a projection to distinguish missing values from NULL. */
81
+ loadedFields?: readonly string[];
82
+ }
83
+ interface ItemDetailsOptions<Row> {
84
+ /** Detail catalogue; defaults to the table catalogue. Useful for non-column properties. */
85
+ schema?: FieldSchema<Row>;
86
+ /** Legacy collection URL parameter. useQueryTable.queryParameter takes precedence. */
87
+ queryParameter?: string;
88
+ /** Creator-approved modalities; users can enable/disable these in Select columns. Defaults to all. */
89
+ modes?: ItemDetailMode[];
90
+ /** Initially selected buttons. Defaults to Modal (or the first allowed mode). */
91
+ defaultModes?: ItemDetailMode[];
92
+ /** Creator's initial Page link settings; users can override in the column editor. */
93
+ page?: ItemPageOptions;
94
+ storage?: PropertyViewStorage;
95
+ objectType?: string;
96
+ relationships?: RelationshipAdapter;
97
+ /** Defaults to complete clientRows or Transport.fetchRow, then the current projected row. */
98
+ loadRow?: (id: RowId, signal: AbortSignal) => Promise<ItemDetailRecord<Row> | null>;
99
+ }
100
+ interface ItemDetailsApi<Row> {
101
+ links?: QueryLinksApi;
102
+ schema: FieldSchema<Row>;
103
+ /** Collection catalogue used to resolve Page template fields. */
104
+ pageSchema: FieldSchema<Row>;
105
+ queryParameter: string;
106
+ /** Modal controller, retained for standalone cards. */
107
+ properties: PropertyViewApi;
108
+ propertiesByMode: Record<ItemPropertyMode, PropertyViewApi>;
109
+ page: ItemPageOptions;
110
+ availableModes: ItemDetailMode[];
111
+ modes: ItemDetailMode[];
112
+ active: ItemDetailsState["active"] | null;
113
+ record: ItemDetailRecord<Row> | null;
114
+ loading: boolean;
115
+ error: Error | null;
116
+ relationships?: RelationshipAdapter;
117
+ setModes(modes: ItemDetailMode[]): void;
118
+ open(id: RowId, mode: "modal" | "expand"): void;
119
+ close(): void;
120
+ retry(): void;
121
+ loadRow(id: RowId, signal: AbortSignal): Promise<ItemDetailRecord<Row> | null>;
122
+ }
2
123
 
3
124
  type FormulaWorkerFactory = () => Worker;
4
125
  /** Host applications with a restrictive CSP can supply their own worker factory. */
@@ -34,10 +155,11 @@ interface RequestActivityApi {
34
155
  interface SelectionApi {
35
156
  selected: Set<RowId>;
36
157
  isSelected: (id: RowId) => boolean;
37
- /** Toggle one row. With `shiftKey`, extend from the last-toggled anchor to `id`
38
- * across the *currently displayed* order (falls back to a plain toggle if the
39
- * anchor row is no longer present). */
40
- toggle: (id: RowId, shiftKey?: boolean) => void;
158
+ /** Toggle one row and start an editable range. With `shiftKey`, resize that
159
+ * range in the current page's order, checking or unchecking according to the
160
+ * anchor's toggle. `additive` + Shift adds a span to the current selection.
161
+ * Missing anchors or changed display order fall back to a plain toggle. */
162
+ toggle: (id: RowId, shiftKey?: boolean, additive?: boolean) => void;
41
163
  /** Select / clear every row currently on the page. */
42
164
  setPage: (ids: RowId[], selected: boolean) => void;
43
165
  /** Atomically replace the complete selection with arbitrary stable row ids. */
@@ -100,6 +222,10 @@ interface SavedQueriesApi {
100
222
  defaultId: string | null;
101
223
  /** Persist the current query under a name. Rejects on duplicate names. */
102
224
  save: (name: string) => Promise<SavedQuery>;
225
+ /** Whether the storage adapter supports atomic updates. */
226
+ canUpdate: boolean;
227
+ /** Replace a saved query with the current state, retaining its id and default. */
228
+ update: (id: string) => Promise<SavedQuery>;
103
229
  /** Load a saved query into the live controller. */
104
230
  load: (id: string) => void;
105
231
  /** Mark a saved query as the default view. No-op when storage lacks support. */
@@ -110,17 +236,24 @@ interface SavedQueriesApi {
110
236
  }
111
237
  declare function useSavedQueries(key: string, currentQuery: QueryState, applyQueryState: (q: QueryState) => void, storage: StorageAdapter,
112
238
  /** Injected clock so saves are deterministic/testable. */
113
- now: () => number): SavedQueriesApi;
239
+ now: () => number, snapshot?: (query: QueryState) => Promise<QuerySnapshot | null>): SavedQueriesApi;
114
240
 
115
241
  interface UseQueryTableOptions<Row> {
242
+ /** Pause execution and URL synchronization while a mounted host view is hidden. */
243
+ active?: boolean;
244
+ /** Capture evaluation time for grouped executions and remote query requests. */
245
+ evaluationNow?: () => number;
116
246
  /** Normalize external aliases without storing presentation in query state. Must be pure and not throw. */
117
247
  canonicalizeQuery?: (query: QueryState) => QueryState;
118
248
  /** Reject an entire query before rows or metrics execute. Errors retain editable query state. */
119
249
  validateQuery?: (query: QueryState) => void;
250
+ /** Disable row-producing grouping for this instance. Enabled for complete client datasets by default. */
251
+ grouping?: boolean;
120
252
  schema: FieldSchema<Row>;
121
253
  /** Data access. Omit for a purely client-side table driven by `clientRows`. */
122
254
  transport?: Transport<Row>;
123
- /** All rows, for client-side mode (applyQuery runs locally over these). */
255
+ /** Dataset already available in memory. Remote grouping/aggregation belongs
256
+ * on the server; do not download a population to supply this option. */
124
257
  clientRows?: Row[];
125
258
  /** Saved/last-query persistence. Defaults to a non-durable in-memory adapter.
126
259
  * Pass localStorageAdapter() explicitly only when filter values are safe to
@@ -139,6 +272,13 @@ interface UseQueryTableOptions<Row> {
139
272
  * base64url is encoding, not encryption: filters will appear in browser
140
273
  * history, referrers, logs, analytics, and screenshots when enabled. */
141
274
  syncUrl?: boolean;
275
+ /** Collection URL parameter, independent of item views. Takes precedence
276
+ * over the legacy itemDetails.queryParameter; otherwise defaults to q. */
277
+ queryParameter?: string;
278
+ /** URL budget and browser-local FIFO cache; remote snapshots come from storage.snapshots. */
279
+ links?: QueryLinkOptions;
280
+ /** Enable schema-driven click popovers, item modals and row expansion. */
281
+ itemDetails?: boolean | ItemDetailsOptions<Row>;
142
282
  /** Debounce (ms) between a query change and the fetch it triggers. Default 200. */
143
283
  debounceMs?: number;
144
284
  /** Injected clock for deterministic saved-query timestamps. Default Date.now. */
@@ -203,6 +343,23 @@ interface AggregationsApi {
203
343
  contextKey?: string;
204
344
  }
205
345
  interface QueryTableApi<Row> {
346
+ /** Dependent source fetches can use ready without parsing display errors. */
347
+ restoration: {
348
+ status: "resolving" | "ready" | "error";
349
+ ready: boolean;
350
+ error: Error | null;
351
+ };
352
+ /** Call after a host pushState/router update, with an absolute URL or a location
353
+ * relative to window.location.href. Supersedes prior resolution;
354
+ * abort or an intervening query edit prevents stale publication. */
355
+ restoreLocation: (location: string | URL, signal?: AbortSignal) => Promise<void>;
356
+ /** Row-producing GROUP BY controls and source stage, when supported. */
357
+ grouping?: GroupingApi<Row>;
358
+ /** Current result schema; changes when entering or leaving grouping. */
359
+ schema?: FieldSchema<Row>;
360
+ links: QueryLinksApi;
361
+ /** Built-in item details, enabled by the instance creator. */
362
+ itemDetails?: ItemDetailsApi<Row>;
206
363
  query: QueryState;
207
364
  /** Schema defaults used for reset controls. */
208
365
  defaults: QueryState;
@@ -211,9 +368,11 @@ interface QueryTableApi<Row> {
211
368
  canUndo: boolean;
212
369
  /** Can move forward to a previously undone local query state. */
213
370
  canRedo: boolean;
214
- /** Move backward through local, unsaved query history. */
371
+ /** Move backward through local, unsaved query history. Empty history leaves
372
+ * incoming link restoration and errors unchanged. */
215
373
  undo: () => void;
216
- /** Move forward through local, unsaved query history. */
374
+ /** Move forward through local, unsaved query history. Empty history leaves
375
+ * incoming link restoration and errors unchanged. */
217
376
  redo: () => void;
218
377
  requestActivity: RequestActivityApi;
219
378
  rows: Row[];
@@ -270,7 +429,13 @@ interface QueryTableApi<Row> {
270
429
  declare function useQueryTable<Row>(opts: UseQueryTableOptions<Row>): QueryTableApi<Row>;
271
430
 
272
431
  interface MetricExecutionContext<Row> {
432
+ executeV3?: (query: QueryState, signal?: AbortSignal) => Promise<AggregationResult>;
433
+ queryContextKey?: string;
273
434
  schema: FieldSchema<Row>;
435
+ /** One clock shared by source grouping, HAVING, rows and local metrics. */
436
+ now?: number;
437
+ /** Group projections use typed semantics even for shorthand metric clauses. */
438
+ strictLocalMetrics?: boolean;
274
439
  transport?: Transport<Row> | undefined;
275
440
  clientRows?: Row[] | undefined;
276
441
  shownRows?: Row[] | undefined;
@@ -290,6 +455,12 @@ interface AggregationsApiState {
290
455
  preview: (clauses: AggregationClause[], signal?: AbortSignal) => Promise<AggregationResult>;
291
456
  }
292
457
  /** Fetch state is keyed by computation and scope, never card presentation. */
293
- declare function useAggregations<Row>(query: QueryState, schema: FieldSchema<Row>, transport: Transport<Row> | undefined, clientRows: Row[] | undefined, debounceMs: number, nonce: number, validateQuery?: (q: QueryState) => void, options?: Omit<MetricExecutionContext<Row>, "schema" | "transport" | "clientRows">): AggregationsApiState;
458
+ declare function useAggregations<Row>(query: QueryState, schema: FieldSchema<Row>, transport: Transport<Row> | undefined, clientRows: Row[] | undefined, debounceMs: number, nonce: number, validateQuery?: (q: QueryState) => void, options?: Omit<MetricExecutionContext<Row>, "schema" | "transport" | "clientRows">, enabled?: boolean): AggregationsApiState;
459
+
460
+ /** One cancellable batch per displayed reference set; rejects unrelated results. */
461
+ declare function useObjectReferences(references: ObjectReference[], adapter?: RelationshipAdapter): {
462
+ values: Map<string, ResolvedObjectReference>;
463
+ error: Error | null;
464
+ };
294
465
 
295
- export { type AggregationPatch, type AggregationsApi, type AggregationsApiState, type AutoRefreshApi, type AutoRefreshConfig, type AutoRefreshStatus, type ColumnDragApi, type ComputedColumnsApi, type FormulaWorkerFactory, type QueryTableApi, type RequestActivityApi, type RequestActivityEntry, type SavedQueriesApi, type SelectApi, type SelectionApi, type UseQueryTableOptions, createFormulaWorker, evaluateFormulaRows, useAggregations, useColumnDrag, useQueryTable, useSavedQueries, useSelect, useSelection };
466
+ export { type AggregationPatch, type AggregationsApi, type AggregationsApiState, type AutoRefreshApi, type AutoRefreshConfig, type AutoRefreshStatus, type ColumnDragApi, type ComputedColumnsApi, type FormulaWorkerFactory, type GroupingApi, type GroupingSourceApi, type ItemDetailRecord, type ItemDetailsApi, type ItemDetailsOptions, type PropertyViewApi, type QueryFilterApi, type QueryLinksApi, type QueryTableApi, type RequestActivityApi, type RequestActivityEntry, type SavedQueriesApi, type SelectApi, type SelectionApi, type UsePropertyViewOptions, type UseQueryTableOptions, createFormulaWorker, evaluateFormulaRows, useAggregations, useColumnDrag, useObjectReferences, usePropertyView, useQueryTable, useSavedQueries, useSelect, useSelection };