@genesislcap/grid-pro 15.30.0 → 15.30.2

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.
Files changed (49) hide show
  1. package/dist/custom-elements.json +8196 -7794
  2. package/dist/dts/column-selection/column-selection.d.ts +4 -3
  3. package/dist/dts/column-selection/column-selection.d.ts.map +1 -1
  4. package/dist/dts/column-selection/column-selection.styles.d.ts.map +1 -1
  5. package/dist/dts/datasource/base.datasource.d.ts +69 -8
  6. package/dist/dts/datasource/base.datasource.d.ts.map +1 -1
  7. package/dist/dts/datasource/base.types.d.ts +10 -0
  8. package/dist/dts/datasource/base.types.d.ts.map +1 -1
  9. package/dist/dts/datasource/dataserver-result.filter.d.ts +17 -0
  10. package/dist/dts/datasource/dataserver-result.filter.d.ts.map +1 -0
  11. package/dist/dts/datasource/delivered-block.ledger.d.ts +37 -0
  12. package/dist/dts/datasource/delivered-block.ledger.d.ts.map +1 -0
  13. package/dist/dts/datasource/infinite.datasource.d.ts +65 -1
  14. package/dist/dts/datasource/infinite.datasource.d.ts.map +1 -1
  15. package/dist/dts/datasource/infinite.resource.d.ts +136 -6
  16. package/dist/dts/datasource/infinite.resource.d.ts.map +1 -1
  17. package/dist/dts/datasource/server-side.datasource.d.ts +8 -4
  18. package/dist/dts/datasource/server-side.datasource.d.ts.map +1 -1
  19. package/dist/dts/datasource/server-side.resource-base.d.ts +23 -0
  20. package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
  21. package/dist/dts/datasource/server-side.resource-dataserver.d.ts +28 -0
  22. package/dist/dts/datasource/server-side.resource-dataserver.d.ts.map +1 -1
  23. package/dist/dts/datasource/server-side.resource-reqrep.d.ts +1 -1
  24. package/dist/dts/datasource/server-side.resource-reqrep.d.ts.map +1 -1
  25. package/dist/dts/grid-pro-beta.d.ts +6 -0
  26. package/dist/dts/grid-pro-beta.d.ts.map +1 -1
  27. package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts +4 -0
  28. package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts.map +1 -1
  29. package/dist/dts/grid-pro.d.ts.map +1 -1
  30. package/dist/dts/react.d.ts +20 -20
  31. package/dist/esm/column-selection/column-selection.js +11 -9
  32. package/dist/esm/column-selection/column-selection.styles.js +0 -2
  33. package/dist/esm/column-selection/column-selection.template.js +1 -1
  34. package/dist/esm/datasource/base.datasource.js +182 -27
  35. package/dist/esm/datasource/dataserver-result.filter.js +28 -0
  36. package/dist/esm/datasource/delivered-block.ledger.js +52 -0
  37. package/dist/esm/datasource/infinite.datasource.js +131 -20
  38. package/dist/esm/datasource/infinite.resource.js +221 -16
  39. package/dist/esm/datasource/server-side.datasource.js +80 -29
  40. package/dist/esm/datasource/server-side.resource-base.js +39 -0
  41. package/dist/esm/datasource/server-side.resource-dataserver.js +110 -23
  42. package/dist/esm/datasource/server-side.resource-reqrep.js +25 -3
  43. package/dist/esm/grid-pro-beta.js +20 -2
  44. package/dist/esm/grid-pro.js +4 -1
  45. package/dist/grid-pro.api.json +66 -66
  46. package/dist/grid-pro.d.ts +294 -21
  47. package/dist/react.cjs +23 -23
  48. package/dist/react.mjs +21 -21
  49. package/package.json +13 -13
@@ -1,5 +1,6 @@
1
1
  import { __awaiter, __decorate } from "tslib";
2
2
  import { Connect, Datasource, DatasourceDefaults, DatasourceEventHandler, FieldTypeEnum, logger, normaliseCriteria, ResourceType, } from '@genesislcap/foundation-comms';
3
+ import { JSONSerializer } from '@genesislcap/foundation-utils';
3
4
  import { attr, nullableNumberConverter, observable, volatile } from '@microsoft/fast-element';
4
5
  import { FoundationElement } from '@microsoft/fast-foundation';
5
6
  import { BehaviorSubject } from 'rxjs';
@@ -141,11 +142,23 @@ export class GenesisGridDatasourceElement extends DatasourceEventHandler(Foundat
141
142
  requestAutoSetup: this.requestAutoSetup,
142
143
  };
143
144
  }
145
+ /**
146
+ * The criteria the resource should be read with: the `criteria` attribute plus anything added
147
+ * through `setFilter()`.
148
+ * @internal
149
+ */
144
150
  buildCriteria() {
145
- var _a;
146
- const initialCriteria = ((_a = this.criteria) === null || _a === void 0 ? void 0 : _a.split(criteriaDelimiter)) || [];
147
- const criteria = initialCriteria.concat(Array.from(this.criteriaFromFilters.values()));
148
- const normalisedCriteria = normaliseCriteria(criteria.join(criteriaJoin), criteriaDelimiter);
151
+ var _a, _b;
152
+ // Blank fragments are dropped before joining. `criteria` is often bound to an empty string
153
+ // rather than left unset, and ''.split(';') is [''], so without this a single setFilter()
154
+ // produced " && FIELD == 'x'" - a leading operator the server rejects as invalid Groovy.
155
+ const parts = [
156
+ ...((_b = (_a = this.criteria) === null || _a === void 0 ? void 0 : _a.split(criteriaDelimiter)) !== null && _b !== void 0 ? _b : []),
157
+ ...this.criteriaFromFilters.values(),
158
+ ]
159
+ .map((part) => part === null || part === void 0 ? void 0 : part.trim())
160
+ .filter(Boolean);
161
+ const normalisedCriteria = normaliseCriteria(parts.join(criteriaJoin), criteriaDelimiter);
149
162
  return normalisedCriteria ? normalisedCriteria : undefined;
150
163
  }
151
164
  setFilter(fieldName, newFilter) {
@@ -360,11 +373,9 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
360
373
  this.dataSubWasLoggedOff = false;
361
374
  this.keepColDefsOnClearRowData = false;
362
375
  this.rowData = new Map();
363
- this.transactionData = {
364
- add: [],
365
- remove: [],
366
- update: [],
367
- };
376
+ this.transactionData = { add: [], remove: [], update: [] };
377
+ /** So rows without a row id are warned about once per element, not once per block. @internal */
378
+ this.missingRowIdWarned = false;
368
379
  this._isDisconnected = false;
369
380
  }
370
381
  subscribeToConnection() {
@@ -467,32 +478,60 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
467
478
  }
468
479
  });
469
480
  }
481
+ /**
482
+ * Resolves the id of the held row a stream row refers to.
483
+ * @remarks Under a custom `row-id` a DATASERVER push may carry only the default id
484
+ * (`ROW_REF`), so the held row with that default id supplies the custom one. Returns
485
+ * `undefined` when neither id is present - callers skip the row rather than matching
486
+ * `undefined === undefined` against the first held row.
487
+ * @internal
488
+ */
489
+ resolveRowId(row) {
490
+ const rowId = row === null || row === void 0 ? void 0 : row[this.rowId];
491
+ if (rowId !== undefined && rowId !== null) {
492
+ return rowId;
493
+ }
494
+ if (this.hasDefaultRowId || this.isRequestServer) {
495
+ return undefined;
496
+ }
497
+ const defaultRowId = this.defaultRowIdByResourceType;
498
+ const defaultKey = row === null || row === void 0 ? void 0 : row[defaultRowId];
499
+ if (defaultKey === undefined || defaultKey === null) {
500
+ return undefined;
501
+ }
502
+ for (const held of this.rowData.values()) {
503
+ if ((held === null || held === void 0 ? void 0 : held[defaultRowId]) === defaultKey) {
504
+ return held[this.rowId];
505
+ }
506
+ }
507
+ return undefined;
508
+ }
470
509
  handleStreamDeletes(deletedRows) {
471
510
  deletedRows === null || deletedRows === void 0 ? void 0 : deletedRows.forEach((deleteData) => {
472
- if (this.hasDefaultRowId || this.isRequestServer) {
473
- this.transactionData.remove.push({ [this.rowId]: deleteData[this.rowId] });
474
- }
475
- else {
476
- const rowToBeDeleted = Object.values(Object.fromEntries(this.rowData)).find((obj) => obj[this.defaultRowIdByResourceType] === deleteData[this.defaultRowIdByResourceType]);
477
- this.transactionData.remove.push({ [this.rowId]: rowToBeDeleted[this.rowId] });
511
+ const rowId = this.resolveRowId(deleteData);
512
+ if (rowId === undefined) {
513
+ logger.warn('Attempted to delete row without a resolvable rowId', deleteData);
514
+ return;
478
515
  }
479
- this.rowData.delete(deleteData[this.rowId]);
516
+ this.transactionData.remove.push({ [this.rowId]: rowId });
517
+ this.rowData.delete(rowId);
480
518
  });
481
519
  }
482
520
  handleStreamUpdates(updatedRows) {
483
521
  updatedRows === null || updatedRows === void 0 ? void 0 : updatedRows.forEach((updateData) => {
484
- let updatedRow;
485
- if (this.hasDefaultRowId || this.isRequestServer) {
486
- const rowToBeUpdated = this.rowData.get(updateData[this.rowId]);
487
- updatedRow = Object.assign(Object.assign({}, rowToBeUpdated), updateData);
488
- this.transactionData.update.push(updatedRow);
489
- }
490
- else {
491
- const rowToBeUpdated = Object.values(Object.fromEntries(this.rowData)).find((obj) => obj[this.defaultRowIdByResourceType] === updateData[this.defaultRowIdByResourceType]);
492
- updatedRow = Object.assign(Object.assign({}, rowToBeUpdated), updateData);
493
- this.transactionData.update.push(updatedRow);
522
+ const rowId = this.resolveRowId(updateData);
523
+ const rowToBeUpdated = rowId === undefined ? undefined : this.rowData.get(rowId);
524
+ if (!rowToBeUpdated) {
525
+ // Neither the grid nor consumers hold the row, so there is nothing to update; the row is
526
+ // reported as `add` if and when a block delivers it.
527
+ logger.debug('Ignoring update for a row that is not held', updateData);
528
+ return;
494
529
  }
495
- this.rowData.set(updateData[this.rowId], updatedRow);
530
+ // A partial MODIFY resolved through the default id carries the custom id as undefined;
531
+ // keep the resolved one.
532
+ const updatedRow = Object.assign(Object.assign(Object.assign({}, rowToBeUpdated), updateData), { [this.rowId]: rowId });
533
+ this.transactionData.update.push(updatedRow);
534
+ this.rowData.set(rowId, updatedRow);
496
535
  });
497
536
  }
498
537
  applyAllTransactions() {
@@ -525,6 +564,119 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
525
564
  changes: mappedTransaction,
526
565
  });
527
566
  }
567
+ /**
568
+ * Reports rows a block-based row model (server-side, infinite) has just handed to the grid.
569
+ * Rows not held before are reported as `add`; rows already held (a block re-read after a
570
+ * refresh, or a req/rep re-read after a commit or poll) are reported as `update` only when
571
+ * their content changed, so a plain re-read is silent.
572
+ * @remarks `rowData` holds the rows delivered since the last cache reset: the resource reports
573
+ * rows a re-read no longer returned (`reportWithdrawnRows`), and `clearRowData` empties it when
574
+ * the grid drops its cache. Block eviction is not mirrored - see `DeliveredBlockLedger` - so
575
+ * this is a superset of what the grid holds.
576
+ * @internal
577
+ */
578
+ reportDeliveredRows(rows) {
579
+ const transaction = this.resetTransaction();
580
+ rows.forEach((row) => {
581
+ const rowId = row === null || row === void 0 ? void 0 : row[this.rowId];
582
+ if (rowId === undefined || rowId === null) {
583
+ this.warnMissingRowIdOnce(row);
584
+ return;
585
+ }
586
+ const known = this.rowData.get(rowId);
587
+ if (!known) {
588
+ transaction.add.push(row);
589
+ }
590
+ else if (this.hasRowContentChanged(known, row)) {
591
+ transaction.update.push(row);
592
+ }
593
+ this.rowData.set(rowId, row);
594
+ });
595
+ this.emitTransaction(transaction);
596
+ }
597
+ /**
598
+ * Reports rows a block re-read no longer returned: they left the grid (deleted on the server,
599
+ * or moved into a block that reports them again). Rows already reported removed - a DATASERVER
600
+ * push, say - are skipped.
601
+ * @internal
602
+ */
603
+ reportWithdrawnRows(rowIds) {
604
+ const transaction = this.resetTransaction();
605
+ rowIds.forEach((rowId) => {
606
+ if (!this.rowData.has(rowId)) {
607
+ return;
608
+ }
609
+ this.rowData.delete(rowId);
610
+ transaction.remove.push({ [this.rowId]: rowId });
611
+ });
612
+ this.emitTransaction(transaction);
613
+ }
614
+ /**
615
+ * Starts a fresh transaction and returns it, so a caller can keep working on the object it
616
+ * built even if a consumer's `datasource-data-changed` handler resets the element meanwhile.
617
+ * @internal
618
+ */
619
+ resetTransaction() {
620
+ const transaction = { add: [], remove: [], update: [] };
621
+ this.transactionData = transaction;
622
+ return transaction;
623
+ }
624
+ /**
625
+ * Whether two versions of a row differ in content. Identity and a key-order-insensitive
626
+ * field comparison come first, so a re-read of unchanged rows costs no serialization; nested
627
+ * values fall back to the serializer.
628
+ * @internal
629
+ */
630
+ hasRowContentChanged(known, row) {
631
+ if (known === row) {
632
+ return false;
633
+ }
634
+ if (!known || !row || typeof known !== 'object' || typeof row !== 'object') {
635
+ return true;
636
+ }
637
+ const knownKeys = Object.keys(known);
638
+ const rowKeys = Object.keys(row);
639
+ if (knownKeys.length !== rowKeys.length) {
640
+ return true;
641
+ }
642
+ for (const key of rowKeys) {
643
+ const before = known[key];
644
+ const after = row[key];
645
+ if (before === after) {
646
+ continue;
647
+ }
648
+ if (before !== null &&
649
+ after !== null &&
650
+ typeof before === 'object' &&
651
+ typeof after === 'object' &&
652
+ this.serializer.serialize(before) === this.serializer.serialize(after)) {
653
+ continue;
654
+ }
655
+ return true;
656
+ }
657
+ return false;
658
+ }
659
+ /** Emits `datasource-data-changed` for `transaction` when it is non-empty. @internal */
660
+ emitTransaction(transaction) {
661
+ if (!transaction) {
662
+ return;
663
+ }
664
+ const { add, update, remove } = transaction;
665
+ if (!(add === null || add === void 0 ? void 0 : add.length) && !(update === null || update === void 0 ? void 0 : update.length) && !(remove === null || remove === void 0 ? void 0 : remove.length)) {
666
+ return;
667
+ }
668
+ this.$emit(datasourceEventNames.dataChanged, {
669
+ changes: transaction,
670
+ });
671
+ }
672
+ /** @internal */
673
+ warnMissingRowIdOnce(row) {
674
+ if (this.missingRowIdWarned) {
675
+ return;
676
+ }
677
+ this.missingRowIdWarned = true;
678
+ logger.warn(`Rows delivered by ${this.resourceName} carry no '${this.rowId}' value and are not reported in datasource-data-changed; set row-id to the field that identifies them.`, row);
679
+ }
528
680
  /**
529
681
  * Builds the index map used for server-side sorting from the resource's metadata.
530
682
  * @remarks Real DATASERVER indexes keep their names; when the resource also (or only) reports
@@ -707,3 +859,6 @@ export class GridProBaseDatasource extends GenesisGridDatasourceElement {
707
859
  return additionalPanels;
708
860
  }
709
861
  }
862
+ __decorate([
863
+ JSONSerializer
864
+ ], GridProBaseDatasource.prototype, "serializer", void 0);
@@ -0,0 +1,28 @@
1
+ import { DatasourceDefaults, dataServerResultFilter, } from '@genesislcap/foundation-comms';
2
+ /**
3
+ * Filters a DATASERVER batch into inserts/updates/deletes, keeping the default row id
4
+ * (`ROW_REF`) on every row alongside the configured one.
5
+ * @remarks `dataServerResultFilter` resolves the configured `rowId` out of `DETAILS` and then
6
+ * drops `DETAILS` entirely. Under a custom `row-id` that loses the only id a pushed MODIFY or
7
+ * DELETE carries: Genesis sends those as `DETAILS: { OPERATION, ROW_REF }` plus the changed
8
+ * fields, so the custom id is absent and the row arrives with no resolvable identity at all.
9
+ * Hoisting `ROW_REF` before the filter runs keeps it on both the rows a block delivers and the
10
+ * rows a push carries, which is what lets `resolveRowId` match one to the other.
11
+ *
12
+ * Columns come from the resource's field metadata, not from row keys, so the extra field does
13
+ * not add a column.
14
+ * @internal
15
+ */
16
+ export function filterDataserverResult(result, rowId) {
17
+ var _a;
18
+ const defaultRowId = DatasourceDefaults.DATASERVER_ROW_ID;
19
+ if (rowId === defaultRowId) {
20
+ return dataServerResultFilter(result, rowId);
21
+ }
22
+ const withRowRef = Object.assign(Object.assign({}, result), { ROW: (_a = result.ROW) === null || _a === void 0 ? void 0 : _a.map((row) => {
23
+ var _a, _b;
24
+ const rowRef = (_b = (_a = row === null || row === void 0 ? void 0 : row.DETAILS) === null || _a === void 0 ? void 0 : _a[defaultRowId]) !== null && _b !== void 0 ? _b : row === null || row === void 0 ? void 0 : row[defaultRowId];
25
+ return rowRef === undefined || rowRef === null ? row : Object.assign(Object.assign({}, row), { [defaultRowId]: rowRef });
26
+ }) });
27
+ return dataServerResultFilter(withRowRef, rowId);
28
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Tracks which row ids each block has handed to AG Grid, so the host can learn when a re-read of
3
+ * a block no longer returns a row (it left the grid).
4
+ * @remarks Plain class shared by the infinite and SSRM resources - AG Grid does not report that
5
+ * event.
6
+ *
7
+ * Block eviction is deliberately **not** mirrored here. AG Grid evicts by last access
8
+ * (`InfiniteCache.purgeBlocksIfNeeded` sorts on `lastAccessed` and never evicts a displayed or
9
+ * focused block; the SSRM `LazyCache` purges by distance from the viewport), and a datasource is
10
+ * told about neither the accesses nor the evictions. Any eviction model built from delivery order
11
+ * alone diverges from the grid's as soon as the user scrolls back, which would drop a visible
12
+ * row from the held set and silence the pushes that follow. So the held set means "rows delivered
13
+ * since the last cache reset": a superset of what the grid holds, which keeps every push for a
14
+ * possibly-visible row flowing and costs only a re-delivered row being reported as unchanged
15
+ * rather than as an `add`.
16
+ * @internal
17
+ */
18
+ export class DeliveredBlockLedger {
19
+ constructor(rowId) {
20
+ this.rowId = rowId;
21
+ /** Row ids per block start row. */
22
+ this.blocks = new Map();
23
+ }
24
+ /** Records the rows handed to the grid for the block at `startRow`. */
25
+ record(startRow, rows) {
26
+ const ids = new Set();
27
+ rows.forEach((row) => {
28
+ const id = row === null || row === void 0 ? void 0 : row[this.rowId];
29
+ if (id !== undefined && id !== null) {
30
+ ids.add(id);
31
+ }
32
+ });
33
+ const previous = this.blocks.get(startRow);
34
+ this.blocks.set(startRow, ids);
35
+ const withdrawnRowIds = previous
36
+ ? [...previous].filter((id) => !ids.has(id) && !this.isHeld(id))
37
+ : [];
38
+ return { withdrawnRowIds };
39
+ }
40
+ /** Forgets every block, for when the grid drops its whole cache (filter/sort change, destroy). */
41
+ clear() {
42
+ this.blocks.clear();
43
+ }
44
+ isHeld(id) {
45
+ for (const ids of this.blocks.values()) {
46
+ if (ids.has(id)) {
47
+ return true;
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+ }
@@ -2,6 +2,7 @@ import { __awaiter, __decorate } from "tslib";
2
2
  import { Connect, ConnectEvents, Datasource, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
3
3
  import { LifecycleMixin } from '@genesislcap/foundation-utils';
4
4
  import { customElement, DOM, observable } from '@microsoft/fast-element';
5
+ import { debounceTime, skip } from 'rxjs/operators';
5
6
  import { gridProGenesisDatasourceEventNames } from '../grid-pro-genesis-datasource';
6
7
  import { datasourceEventNames, } from '../grid-pro-genesis-datasource/datasource-events.types';
7
8
  import { logger } from '../utils';
@@ -9,6 +10,8 @@ import { GridProBaseDatasource } from './base.datasource';
9
10
  import { GenesisInfiniteDatasource } from './infinite.resource';
10
11
  import { getFilterByFieldType, getServerSideFilterParamsByFieldType, } from './server-side.grid-definitions';
11
12
  const criteriaDelimiter = ';';
13
+ /** How long a burst of `setFilter()` calls is collapsed for, matching the client-side model. */
14
+ const FILTER_DEBOUNCE_MS = 600;
12
15
  /**
13
16
  * A Genesis Datasource element for AG Grid's Infinite Row Model.
14
17
  * @remarks
@@ -40,12 +43,15 @@ const criteriaDelimiter = ';';
40
43
  * @fires datasource-initialize - Fired to hand off infinite row model grid options. detail: `InitializeEventDetail`
41
44
  * @fires datasource-init - Fired when the infinite grid model should initialize data
42
45
  * @fires datasource-schema-updated - Fired when column metadata or defs are updated. detail: `SchemaUpdatedEventDetail`
46
+ * @fires datasource-data-changed - Fired when rows are delivered to the grid, a DATASERVER push changes them, or a re-read no longer returns them; `add` are rows the grid did not hold before, `update` are held rows whose content changed, `remove` carries the row id. detail: `DataChangedEventDetail`
43
47
  * @fires set-infinite-datasource - Fired to attach or clear the infinite row model datasource. detail: `SetInfiniteDatasourceEventDetail`
44
48
  * @fires refresh-infinite-cache - Fired to request an infinite row model refresh; `purge` drops the block cache and re-reads. detail: `RefreshInfiniteCacheEventDetail`
45
49
  * @fires add-grid-css-class - Fired to add a CSS class on the grid host (hover sort indicators). detail: `GridCssClassEventDetail`
46
50
  * @fires remove-grid-css-class - Fired to remove that CSS class from the grid host. detail: `GridCssClassEventDetail`
47
51
  * @fires cache-filter-config - Fired to persist filter configuration for the grid
48
- * @fires datasource-data-cleared - Fired when infinite row data is cleared. detail: `DataClearedEventDetail`
52
+ * @fires datasource-data-cleared - Fired when the grid drops every row it held (reload, filter or sort change, destroy); `includeSchema` says whether the columns went too. detail: `DataClearedEventDetail`
53
+ * @fires datasource-loading-finished - Fired when the first block comes back with rows
54
+ * @fires datasource-no-data-available - Fired when the first block comes back empty
49
55
  * @fires datasource-filters-restored - Fired when persisted filters are reapplied
50
56
  */
51
57
  let GridProInfiniteDatasource = class GridProInfiniteDatasource extends LifecycleMixin(GridProBaseDatasource) {
@@ -61,6 +67,10 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
61
67
  this.setDisconnected(true);
62
68
  return;
63
69
  }
70
+ if (!this.isInfiniteSupportedForResource()) {
71
+ this.clearRowData();
72
+ return;
73
+ }
64
74
  this.$emit(gridProGenesisDatasourceEventNames.dataInit);
65
75
  this.setDisconnected(false);
66
76
  this.indexes = this.getResourceIndexes(this.datasource.availableIndexes, this.datasource.availableSortableFields);
@@ -78,15 +88,14 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
78
88
  // VIEW_NUMBER 0 means "pagination disabled": the server continues from the cursor it
79
89
  // holds on the last row it delivered, rather than re-seeking by offset.
80
90
  getMoreRowsFunc: (sourceRef) => this.connect.getMoreRows(sourceRef, 0),
81
- // A pushed INSERT/DELETE changes which rows exist or their order, so the rows are
82
- // re-read (purge); a pushed MODIFY is already patched into the cache, so the grid only
83
- // needs to re-render its blocks.
84
- onRowsInvalidatedFunc: () => this.$emit(datasourceEventNames.refreshInfiniteCache, {
85
- purge: true,
86
- }),
87
- onRowsUpdatedFunc: () => this.$emit(datasourceEventNames.refreshInfiniteCache, {
88
- purge: false,
89
- }),
91
+ onRowsDeliveredFunc: this.reportDeliveredRows.bind(this),
92
+ onRowsWithdrawnFunc: this.reportWithdrawnRows.bind(this),
93
+ // A filter or sort change drops every block: forget the rows and tell consumers.
94
+ onRowCacheResetFunc: () => this.clearRowData(false),
95
+ onRowsInvalidatedFunc: this.handleLiveRowsInvalidated.bind(this),
96
+ onRowsUpdatedFunc: this.handleLiveRowsUpdated.bind(this),
97
+ onNoDataAvailableFunc: () => this.$emit(datasourceEventNames.noDataAvailable),
98
+ onDataAvailableFunc: () => this.$emit(datasourceEventNames.loadingFinished),
90
99
  errorHandlerFunc: this.handleErrors.bind(this),
91
100
  resourceName: this.resourceName,
92
101
  resourceParams: this.isRequestServer
@@ -96,8 +105,12 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
96
105
  resourceColDefs: this.datasource.originalFieldDef,
97
106
  maxRows: +this.maxRows,
98
107
  rowId: this.rowId,
99
- baseCriteria: this.criteria,
108
+ // The merged criteria, so a criteria added through setFilter() is read with the rows
109
+ // rather than dropped: the logon builder replaces CRITERIA_MATCH with what it is given.
110
+ baseCriteria: this.buildCriteria(),
100
111
  isRequestServer: this.isRequestServer,
112
+ defaultOrderBy: this.orderBy,
113
+ defaultReverse: this.reverse,
101
114
  });
102
115
  // Emit event to set infinite datasource
103
116
  this.$emit(datasourceEventNames.setInfiniteDatasource, {
@@ -140,6 +153,7 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
140
153
  return;
141
154
  this.init();
142
155
  this.subscribeToConnection();
156
+ this.subscribeToFilterChanges();
143
157
  });
144
158
  }
145
159
  disconnectedCallback() {
@@ -150,8 +164,34 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
150
164
  return;
151
165
  yield this.destroy();
152
166
  this.unsubscribeFromConnection();
167
+ this.unsubscribeFromFilterChanges();
153
168
  }));
154
169
  }
170
+ /**
171
+ * Reloads when `setFilter()` or `removeFilter()` changes the criteria.
172
+ * @remarks The criteria a block is read with is captured when the resource is built, so a
173
+ * filter added later reaches nothing until the rows are re-read. The `criteria` attribute
174
+ * already reloads through `criteriaChanged`; this gives the programmatic API the same
175
+ * behaviour instead of silently doing nothing.
176
+ * @internal
177
+ */
178
+ subscribeToFilterChanges() {
179
+ this.unsubscribeFromFilterChanges();
180
+ // Same shape as the client-side datasource: `update` is a BehaviorSubject, so skip(1) drops
181
+ // the value it replays on subscribe (the state init() is already reading), and the debounce
182
+ // collapses a burst of setFilter calls into one reload.
183
+ this.filterSub = this.update.pipe(skip(1), debounceTime(FILTER_DEBOUNCE_MS)).subscribe(() => {
184
+ if (!this.infiniteDatasource)
185
+ return;
186
+ this.reloadResourceData();
187
+ });
188
+ }
189
+ /** @internal */
190
+ unsubscribeFromFilterChanges() {
191
+ var _a;
192
+ (_a = this.filterSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
193
+ this.filterSub = undefined;
194
+ }
155
195
  deepClone() {
156
196
  const copy = super.deepClone();
157
197
  copy.deferredGridOptions = structuredClone(this.deferredGridOptions);
@@ -169,7 +209,10 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
169
209
  // The paging DATA_LOGON is opened through connect.stream, not this.datasource, so destroying
170
210
  // the comms datasource below does not close it. Dispose the IDatasource explicitly or every
171
211
  // criteria change (e.g. a bound search box) leaks a live subscription and its block timers.
172
- (_b = (_a = this.infiniteDatasource) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
212
+ // detach(), not destroy(): AG Grid keeps calling this bean until the replacement is attached
213
+ // a round trip later, and the purge below re-requests blocks immediately. destroy() alone
214
+ // would leave it reading with the old criteria and reporting those rows as delivered.
215
+ (_b = (_a = this.infiniteDatasource) === null || _a === void 0 ? void 0 : _a.detach) === null || _b === void 0 ? void 0 : _b.call(_a);
173
216
  this.infiniteDatasource = undefined;
174
217
  this.datasource.destroy();
175
218
  // Emit event to cache current filter model before clearing data
@@ -225,6 +268,69 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
225
268
  clearReadyListener() {
226
269
  this.removeEventListener(datasourceEventNames.ready, this.onDatasourceReady);
227
270
  }
271
+ /**
272
+ * Whether the infinite row model can drive this resource.
273
+ * @remarks It pages a req/rep by sending CRITERIA_MATCH, ORDER_BY and OFFSET in DETAILS, which
274
+ * a request reply only accepts when it is declared `criteriaOnlyRequest`
275
+ * (`CRITERIA_ONLY_REQUEST` in its metadata). Without it the server expects its inputs in
276
+ * `REQUEST` instead and rejects every block, which on this row model surfaces as a grid that
277
+ * simply never fills. Report it once, up front, as the server-side model does.
278
+ * @returns `true` when the infinite model can be used, `false` after reporting a
279
+ * `resource-type` error.
280
+ * @internal
281
+ */
282
+ isInfiniteSupportedForResource() {
283
+ if (!this.isRequestServer || this.datasource.criteriaOnlyRequest) {
284
+ return true;
285
+ }
286
+ this.handleErrors(`Infinite row model is not supported for the REQUEST_SERVER resource '${this.resourceName}' because it does not support criteria-only requests. Set 'criteriaOnlyRequest = true' on the request reply definition, or use a client-side datasource for this resource.`, 'resource-type');
287
+ return false;
288
+ }
289
+ /**
290
+ * A pushed INSERT/DELETE changes which rows exist or their order, so the change is reported
291
+ * and then the rows are re-read (purge).
292
+ * @internal
293
+ */
294
+ handleLiveRowsInvalidated(change) {
295
+ this.reportLiveChange(change);
296
+ this.$emit(datasourceEventNames.refreshInfiniteCache, {
297
+ purge: true,
298
+ });
299
+ }
300
+ /**
301
+ * A pushed MODIFY is already patched into the paging cache, so the change is reported and the
302
+ * grid only needs to re-render its blocks.
303
+ * @internal
304
+ */
305
+ handleLiveRowsUpdated(change) {
306
+ this.reportLiveChange(change);
307
+ this.$emit(datasourceEventNames.refreshInfiniteCache, {
308
+ purge: false,
309
+ });
310
+ }
311
+ /**
312
+ * Turns a DATASERVER push into the same transaction the client-side datasource reports:
313
+ * partial MODIFY rows are merged into the held row, and a remove carries just the row id.
314
+ * @remarks Unlike the client-side model, a pushed row need not be held here - it may sit
315
+ * beyond the loaded blocks, in which case the grid will never show it.
316
+ *
317
+ * So a pushed INSERT is deliberately not reported: nothing here says where the new row sorts,
318
+ * and reporting an `add` for a row that lands at position 40,000 of an unscrolled grid would
319
+ * contradict the event's contract and leave the row held forever, since no block re-read ever
320
+ * delivers (or withdraws) it. Every INSERT is followed by a purge and re-read, which reports
321
+ * the row as `add` if and when it actually reaches the grid.
322
+ *
323
+ * A MODIFY for a row that is not held is skipped for the same reason; a DELETE is reported by
324
+ * the id it carries, and one that cannot be resolved to an id is skipped with a warning rather
325
+ * than matched against the wrong row.
326
+ * @internal
327
+ */
328
+ reportLiveChange(change) {
329
+ const transaction = this.resetTransaction();
330
+ this.handleStreamUpdates(change.updates);
331
+ this.handleStreamDeletes(change.deletes);
332
+ this.emitTransaction(transaction);
333
+ }
228
334
  destroy() {
229
335
  return __awaiter(this, void 0, void 0, function* () {
230
336
  var _a, _b;
@@ -237,7 +343,9 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
237
343
  this.datasource.destroy();
238
344
  this.clearRowData();
239
345
  if (this.infiniteDatasource) {
240
- (_b = (_a = this.infiniteDatasource).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
346
+ // The host is done with this bean for good, so detach rather than destroy - see
347
+ // reloadResourceData.
348
+ (_b = (_a = this.infiniteDatasource).detach) === null || _b === void 0 ? void 0 : _b.call(_a);
241
349
  this.infiniteDatasource = undefined;
242
350
  }
243
351
  // Emit events instead of direct grid access
@@ -252,15 +360,18 @@ let GridProInfiniteDatasource = class GridProInfiniteDatasource extends Lifecycl
252
360
  yield this.init();
253
361
  });
254
362
  }
363
+ /**
364
+ * Forgets every held row and tells consumers, as the client-side datasource does: the grid is
365
+ * dropping its rows (a reload, a filter or sort change, or teardown). `withColumnDefs` also
366
+ * clears the column definitions.
367
+ * @internal
368
+ */
255
369
  clearRowData(withColumnDefs = true) {
256
370
  this.rowData = new Map();
257
- if (withColumnDefs) {
258
- // Emit event to clear column definitions
259
- this.$emit(datasourceEventNames.dataCleared, {
260
- includeSchema: true,
261
- });
262
- }
263
- this.transactionData = { remove: [] };
371
+ this.resetTransaction();
372
+ this.$emit(datasourceEventNames.dataCleared, {
373
+ includeSchema: withColumnDefs,
374
+ });
264
375
  }
265
376
  /**
266
377
  * Honours `pollTriggerEvents` without polling: a commit ACK for one of the listed events