@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,6 +1,8 @@
1
1
  import { __awaiter } from "tslib";
2
- import { dataServerResultFilter, MessageType, } from '@genesislcap/foundation-comms';
2
+ import { MessageType, } from '@genesislcap/foundation-comms';
3
3
  import { logger } from '../utils';
4
+ import { filterDataserverResult } from './dataserver-result.filter';
5
+ import { DeliveredBlockLedger } from './delivered-block.ledger';
4
6
  import { convertFilterModelToCriteria } from './filter.utils';
5
7
  /**
6
8
  * Converts an AG Grid sort model to the Genesis ORDER_BY form the resource expects.
@@ -39,12 +41,71 @@ export function convertSortModelToOrderBy(sortModel, resourceIndexes, isRequestS
39
41
  logger.warn(`Column '${colId}' is not part of any named INDEX. Server-side sorting is not available for this column.`);
40
42
  return { orderBy: null, reverse: false };
41
43
  }
44
+ /**
45
+ * Turns whatever a rejected resource read produced into a message worth showing.
46
+ * @remarks Genesis rejects with a message object (`{ ERROR: [{ TEXT }], MESSAGE_TYPE }`), not an
47
+ * `Error`, so stringifying it naively yields "[object Object]" in the grid's error dialog.
48
+ * @internal
49
+ */
50
+ export function describeResourceError(error) {
51
+ var _a, _b, _c;
52
+ if (error instanceof Error) {
53
+ return error.message;
54
+ }
55
+ if (typeof error === 'string') {
56
+ return error;
57
+ }
58
+ const details = error;
59
+ for (const candidate of [details, details === null || details === void 0 ? void 0 : details.receivedMessage]) {
60
+ const texts = (_a = candidate === null || candidate === void 0 ? void 0 : candidate.ERROR) === null || _a === void 0 ? void 0 : _a.map((entry) => entry === null || entry === void 0 ? void 0 : entry.TEXT).filter(Boolean);
61
+ if (texts === null || texts === void 0 ? void 0 : texts.length) {
62
+ return texts.join('; ');
63
+ }
64
+ }
65
+ if (typeof (details === null || details === void 0 ? void 0 : details.message) === 'string' && details.message) {
66
+ return details.message;
67
+ }
68
+ const messageType = (_b = details === null || details === void 0 ? void 0 : details.MESSAGE_TYPE) !== null && _b !== void 0 ? _b : (_c = details === null || details === void 0 ? void 0 : details.receivedMessage) === null || _c === void 0 ? void 0 : _c.MESSAGE_TYPE;
69
+ if (messageType) {
70
+ return messageType;
71
+ }
72
+ try {
73
+ return JSON.stringify(error);
74
+ }
75
+ catch (_d) {
76
+ return String(error);
77
+ }
78
+ }
42
79
  /**
43
80
  * AG Grid Infinite Row Model datasource implementation.
44
81
  * This class implements the IDatasource interface required by AG Grid's Infinite Row Model.
45
82
  */
46
83
  export class GenesisInfiniteDatasource {
47
84
  constructor(options) {
85
+ var _a;
86
+ /** The `reverse` attribute, applied alongside `defaultOrderBy` on DATASERVER. @internal */
87
+ this.defaultReverse = false;
88
+ /**
89
+ * Bumped whenever the rows this datasource was reading for are dropped (a teardown, or a
90
+ * filter/sort change). A REQUEST_SERVER reply that resolves for an earlier generation was read
91
+ * for a cache the grid no longer has.
92
+ * @remarks Deliberately a generation rather than a "destroyed" flag: AG Grid calls `destroy()`
93
+ * on the datasource bean it then keeps using (`InfiniteRowModel.start()` re-applies the
94
+ * `datasource` grid option, and `setDatasource` destroys the previous one first), so a sticky
95
+ * flag would disable the instance the grid is still driving.
96
+ * @internal
97
+ */
98
+ this.loadGeneration = 0;
99
+ /**
100
+ * Set once the host has dropped this instance for a replacement (a criteria change, a resource
101
+ * change, teardown). AG Grid keeps calling the bean it was given until the new one is attached,
102
+ * and its purge re-requests blocks immediately, so without this the replaced instance keeps
103
+ * reading with its captured criteria and reporting those rows to the host as delivered.
104
+ * @remarks Distinct from `destroy()`, which AG Grid itself calls on a bean it then keeps using;
105
+ * only the host calls `detach()`, and only when it will never use this instance again.
106
+ * @internal
107
+ */
108
+ this.detached = false;
48
109
  /**
49
110
  * Session-cumulative row cache, keyed by row id. A Map preserves insertion order, so the
50
111
  * cache can be sliced by block range; Genesis splits batches across messages and later
@@ -69,7 +130,11 @@ export class GenesisInfiniteDatasource {
69
130
  this.getMoreRowsFunc = options.getMoreRowsFunc;
70
131
  this.onRowsInvalidatedFunc = options.onRowsInvalidatedFunc;
71
132
  this.onRowsUpdatedFunc = options.onRowsUpdatedFunc;
133
+ this.onRowsDeliveredFunc = options.onRowsDeliveredFunc;
134
+ this.onRowsWithdrawnFunc = options.onRowsWithdrawnFunc;
135
+ this.onRowCacheResetFunc = options.onRowCacheResetFunc;
72
136
  this.errorHandlerFunc = options.errorHandlerFunc;
137
+ this.ledger = new DeliveredBlockLedger(options.rowId);
73
138
  this.resourceName = options.resourceName;
74
139
  this.resourceParams = Object.assign({}, options.resourceParams);
75
140
  this.resourceIndexes = options.resourceIndexes;
@@ -78,6 +143,10 @@ export class GenesisInfiniteDatasource {
78
143
  this.rowId = options.rowId;
79
144
  this.baseCriteria = options.baseCriteria || '';
80
145
  this.isRequestServer = options.isRequestServer;
146
+ this.defaultOrderBy = options.defaultOrderBy || undefined;
147
+ this.defaultReverse = (_a = options.defaultReverse) !== null && _a !== void 0 ? _a : false;
148
+ this.onNoDataAvailableFunc = options.onNoDataAvailableFunc;
149
+ this.onDataAvailableFunc = options.onDataAvailableFunc;
81
150
  }
82
151
  /**
83
152
  * Builds request parameters for REQUEST_SERVER resources.
@@ -153,6 +222,15 @@ export class GenesisInfiniteDatasource {
153
222
  */
154
223
  getRows(params) {
155
224
  return __awaiter(this, void 0, void 0, function* () {
225
+ var _a, _b;
226
+ // The host has replaced this instance; the grid is only still asking because the replacement
227
+ // has not been attached yet. Reading would use this instance's captured criteria and report
228
+ // rows the grid will drop. fail() releases AG Grid's request slot for the new datasource.
229
+ if (this.detached) {
230
+ logger.debug('Infinite getRows ignored: the datasource was detached by its host');
231
+ params.failCallback();
232
+ return;
233
+ }
156
234
  logger.debug('Infinite getRows called', {
157
235
  startRow: params.startRow,
158
236
  endRow: params.endRow,
@@ -163,7 +241,8 @@ export class GenesisInfiniteDatasource {
163
241
  const filterCriteria = convertFilterModelToCriteria(params.filterModel, this.resourceColDefs);
164
242
  const combinedCriteria = [this.baseCriteria, filterCriteria].filter(Boolean).join(' && ');
165
243
  // Build sort config
166
- const sortConfig = convertSortModelToOrderBy(params.sortModel, this.resourceIndexes, this.isRequestServer);
244
+ const sortConfig = this.resolveSortConfig(params.sortModel);
245
+ this.noteRequestKey(combinedCriteria, sortConfig);
167
246
  // DATASERVER pages through a persistent subscription; REQUEST_SERVER is stateless and
168
247
  // pages by OFFSET on a fresh request each time.
169
248
  if (!this.isRequestServer) {
@@ -172,25 +251,51 @@ export class GenesisInfiniteDatasource {
172
251
  }
173
252
  const requestParams = this.buildRequestServerParams(params.startRow, combinedCriteria, sortConfig);
174
253
  logger.debug('Infinite datasource request params', requestParams);
254
+ // Taken after noteRequestKey, so this is the generation the request belongs to: only a
255
+ // reset that lands while it is in flight invalidates the reply.
256
+ const generation = this.loadGeneration;
175
257
  const result = yield this.createSnapshotFunc(requestParams);
258
+ // The rows this block was read for have since been dropped (a filter or sort change): AG
259
+ // Grid has purged the block, and the host must not learn of rows the grid will never hold.
260
+ // The block is still failed rather than left hanging - AG Grid caps concurrent requests
261
+ // and frees a slot only on success or fail, so a silent return stops it ever asking again.
262
+ if (generation !== this.loadGeneration) {
263
+ logger.debug('Infinite REQUEST_SERVER reply discarded: the block cache was reset');
264
+ params.failCallback();
265
+ return;
266
+ }
176
267
  if (!result) {
177
268
  params.failCallback();
178
269
  return;
179
270
  }
271
+ // A rejected request answers with a message, not a REPLY. Settling it as an empty block
272
+ // would tell the grid the dataset simply ends here, so the user sees a short grid with no
273
+ // indication that anything failed.
274
+ const messageType = result.MESSAGE_TYPE;
275
+ if (messageType === MessageType.LOGOFF_ACK || messageType === MessageType.MSG_NACK) {
276
+ const message = messageType === MessageType.LOGOFF_ACK
277
+ ? `Connection lost to ${this.resourceName}`
278
+ : `Request rejected by ${this.resourceName}`;
279
+ logger.error(`Infinite REQUEST_SERVER block failed: ${message}`, result);
280
+ (_a = this.errorHandlerFunc) === null || _a === void 0 ? void 0 : _a.call(this, message, 'connection');
281
+ params.failCallback();
282
+ return;
283
+ }
180
284
  if ('REPLY' in result && Array.isArray(result.REPLY)) {
181
285
  const rowData = result.REPLY;
182
286
  const lastRow = this.getLastRow(result, params.startRow, rowData.length, Boolean(combinedCriteria));
183
287
  params.successCallback(rowData, lastRow);
288
+ this.deliverRows(params.startRow, rowData);
289
+ this.notifyDataAvailability(params.startRow, rowData.length);
184
290
  return;
185
291
  }
186
292
  // Fallback - empty result
187
293
  params.successCallback([], params.startRow);
294
+ this.notifyDataAvailability(params.startRow, 0);
188
295
  }
189
296
  catch (error) {
190
297
  logger.error('Error in infinite getRows:', error);
191
- if (this.errorHandlerFunc) {
192
- this.errorHandlerFunc(error instanceof Error ? error.message : String(error), 'unknown');
193
- }
298
+ (_b = this.errorHandlerFunc) === null || _b === void 0 ? void 0 : _b.call(this, describeResourceError(error), 'unknown');
194
299
  params.failCallback();
195
300
  }
196
301
  });
@@ -202,20 +307,57 @@ export class GenesisInfiniteDatasource {
202
307
  * outstanding at once, and each settles as soon as the cache covers its range.
203
308
  * @internal
204
309
  */
310
+ /**
311
+ * Notes the CRITERIA_MATCH/ORDER_BY a block is being read with. A change means AG Grid has
312
+ * purged its cache and restarted from block 0: on DATASERVER the subscription is re-opened
313
+ * (those are DATA_LOGON parameters), and on both transports the host is told the rows it held
314
+ * are gone, so the rows read under the new filter or sort are reported as new.
315
+ * @internal
316
+ */
317
+ /**
318
+ * The sort a block is read with: the grid's own sort model when the user has sorted, and the
319
+ * `order-by` / `reverse` attributes otherwise.
320
+ * @remarks Without the fallback those attributes reached the comms datasource but were then
321
+ * dropped, because the logon builder removes ORDER_BY and REVERSE whenever the grid is
322
+ * unsorted. Sorting from the grid still wins, as it is the more specific instruction.
323
+ * @internal
324
+ */
325
+ resolveSortConfig(sortModel) {
326
+ const fromGrid = convertSortModelToOrderBy(sortModel, this.resourceIndexes, this.isRequestServer);
327
+ if (fromGrid.orderBy || !this.defaultOrderBy) {
328
+ return fromGrid;
329
+ }
330
+ return { orderBy: this.defaultOrderBy, reverse: this.defaultReverse };
331
+ }
332
+ noteRequestKey(criteria, sortConfig) {
333
+ var _a;
334
+ const key = JSON.stringify(Object.assign({ criteria }, sortConfig));
335
+ // Tracked separately from `subscriptionKey`, which a teardown clears: `reset()` tears the
336
+ // subscription down on every structural push, and a filter change landing between that and
337
+ // the next block read would otherwise look like the very first request - leaving the host
338
+ // holding every row read under the old filter.
339
+ if (this.rowCacheKey !== undefined && this.rowCacheKey !== key) {
340
+ // Whatever is in flight was read for the old filter or sort.
341
+ this.loadGeneration += 1;
342
+ this.ledger.clear();
343
+ (_a = this.onRowCacheResetFunc) === null || _a === void 0 ? void 0 : _a.call(this);
344
+ }
345
+ this.rowCacheKey = key;
346
+ // CRITERIA_MATCH and ORDER_BY are DATA_LOGON parameters, so a change needs a fresh
347
+ // subscription. A teardown has already left `subscriptionKey` undefined.
348
+ if (!this.isRequestServer &&
349
+ this.subscriptionKey !== undefined &&
350
+ this.subscriptionKey !== key) {
351
+ this.teardownDataserverSubscription();
352
+ }
353
+ this.subscriptionKey = key;
354
+ }
205
355
  getDataserverRows(params, criteria, sortConfig) {
206
356
  if (!this.createDataserverStreamFunc || !this.getMoreRowsFunc) {
207
357
  logger.error('Infinite DATASERVER paging requires a stream and a getMoreRows function');
208
358
  params.failCallback();
209
359
  return;
210
360
  }
211
- // CRITERIA_MATCH and ORDER_BY are DATA_LOGON parameters, so a filter or sort change needs a
212
- // fresh subscription and a fresh cache. AG Grid purges its own cache on those changes and
213
- // restarts from block 0, so nothing is lost.
214
- const key = JSON.stringify(Object.assign({ criteria }, sortConfig));
215
- if (this.subscriptionKey !== undefined && this.subscriptionKey !== key) {
216
- this.teardownDataserverSubscription();
217
- }
218
- this.subscriptionKey = key;
219
361
  const startRow = Number.isFinite(Number(params.startRow)) ? Number(params.startRow) : 0;
220
362
  const endRow = Number.isFinite(Number(params.endRow))
221
363
  ? Number(params.endRow)
@@ -318,7 +460,7 @@ export class GenesisInfiniteDatasource {
318
460
  return;
319
461
  }
320
462
  this.sourceRef = (_b = message === null || message === void 0 ? void 0 : message.SOURCE_REF) !== null && _b !== void 0 ? _b : this.sourceRef;
321
- const filtered = (message === null || message === void 0 ? void 0 : message.ROW) ? dataServerResultFilter(message, this.rowId) : undefined;
463
+ const filtered = (message === null || message === void 0 ? void 0 : message.ROW) ? filterDataserverResult(message, this.rowId) : undefined;
322
464
  // A DATASERVER subscription is live by definition: batches answering the logon or a
323
465
  // MORE_ROWS carry the rows we asked for, and anything else is a change pushed by the server.
324
466
  // Replies only ever deliver view rows (INSERT operations), so a batch carrying a MODIFY or
@@ -380,7 +522,7 @@ export class GenesisInfiniteDatasource {
380
522
  deletes: (_k = (_j = result.deletes) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0,
381
523
  });
382
524
  this.reset();
383
- (_l = this.onRowsInvalidatedFunc) === null || _l === void 0 ? void 0 : _l.call(this);
525
+ (_l = this.onRowsInvalidatedFunc) === null || _l === void 0 ? void 0 : _l.call(this, result);
384
526
  return;
385
527
  }
386
528
  if (modified) {
@@ -388,7 +530,7 @@ export class GenesisInfiniteDatasource {
388
530
  logger.debug('Infinite DATASERVER live update patched into the cache', {
389
531
  updates: (_o = (_m = result.updates) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : 0,
390
532
  });
391
- (_p = this.onRowsUpdatedFunc) === null || _p === void 0 ? void 0 : _p.call(this);
533
+ (_p = this.onRowsUpdatedFunc) === null || _p === void 0 ? void 0 : _p.call(this, result);
392
534
  }
393
535
  }
394
536
  /**
@@ -506,6 +648,46 @@ export class GenesisInfiniteDatasource {
506
648
  lastRow,
507
649
  });
508
650
  block.success(rows, lastRow);
651
+ this.deliverRows(block.startRow, rows);
652
+ this.notifyDataAvailability(block.startRow, rows.length);
653
+ }
654
+ /**
655
+ * Raises the no-data / data-available hooks for the first block, as the server-side resource
656
+ * does. Later blocks say nothing: the question is whether the resource has any rows at all,
657
+ * and the answer cannot change once the first block has been served.
658
+ * @internal
659
+ */
660
+ notifyDataAvailability(startRow, rowCount) {
661
+ var _a, _b;
662
+ if (startRow !== 0 || this.detached) {
663
+ return;
664
+ }
665
+ if (rowCount === 0) {
666
+ (_a = this.onNoDataAvailableFunc) === null || _a === void 0 ? void 0 : _a.call(this);
667
+ }
668
+ else {
669
+ (_b = this.onDataAvailableFunc) === null || _b === void 0 ? void 0 : _b.call(this);
670
+ }
671
+ }
672
+ /**
673
+ * Reports rows handed to the grid for a block, then the rows a re-read of that block no longer
674
+ * carries and the rows whose block the grid evicted.
675
+ * @internal
676
+ */
677
+ deliverRows(startRow, rows) {
678
+ var _a, _b;
679
+ // A reply that resolves after the host dropped this instance belongs to a cache the grid no
680
+ // longer has; reporting it would tell consumers about rows the grid will never show.
681
+ if (this.detached) {
682
+ return;
683
+ }
684
+ const { withdrawnRowIds } = this.ledger.record(startRow, rows);
685
+ if (rows.length > 0) {
686
+ (_a = this.onRowsDeliveredFunc) === null || _a === void 0 ? void 0 : _a.call(this, rows);
687
+ }
688
+ if (withdrawnRowIds.length > 0) {
689
+ (_b = this.onRowsWithdrawnFunc) === null || _b === void 0 ? void 0 : _b.call(this, withdrawnRowIds);
690
+ }
509
691
  }
510
692
  /**
511
693
  * Warns when the view filled up before the data ran out.
@@ -566,10 +748,33 @@ export class GenesisInfiniteDatasource {
566
748
  this.moreRows = undefined;
567
749
  this.moreRowsInFlight = false;
568
750
  }
751
+ /**
752
+ * Drops the subscription and the cached rows.
753
+ * @remarks AG Grid calls this on the bean it then keeps using (`InfiniteRowModel.start()`
754
+ * re-applies the `datasource` grid option, and `setDatasource` destroys the previous one
755
+ * first), and it can land while a block request is in flight. So it must leave the datasource
756
+ * able to serve that reply and the next `getRows`: it drops the subscription, not the instance.
757
+ */
569
758
  destroy() {
570
759
  this.teardownDataserverSubscription();
760
+ this.ledger.clear();
571
761
  logger.debug('GenesisInfiniteDatasource destroyed');
572
762
  }
763
+ /**
764
+ * Drops this instance for good: the host is replacing it, so it must stop serving the grid and
765
+ * stop reporting rows, even though AG Grid still holds it as its `datasource` until the
766
+ * replacement is attached.
767
+ * @remarks Deliberately separate from `destroy()`, which AG Grid calls on a bean it then keeps
768
+ * using (`InfiniteRowModel.start()` re-applies the `datasource` grid option, and `setDatasource`
769
+ * destroys the previous one first).
770
+ */
771
+ detach() {
772
+ this.detached = true;
773
+ this.loadGeneration += 1;
774
+ this.rowCacheKey = undefined;
775
+ this.destroy();
776
+ logger.debug('GenesisInfiniteDatasource detached by its host');
777
+ }
573
778
  }
574
779
  /**
575
780
  * Max time a DATASERVER block may stay pending before the safety valve resolves it with the
@@ -1,11 +1,12 @@
1
1
  import { __awaiter, __decorate } from "tslib";
2
- import { dataServerResultFilter, Datasource, MessageType, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
3
- import { JSONSerializer, LifecycleMixin } from '@genesislcap/foundation-utils';
2
+ import { Datasource, MessageType, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
3
+ import { LifecycleMixin } from '@genesislcap/foundation-utils';
4
4
  import { attr, customElement, DOM, observable } from '@microsoft/fast-element';
5
5
  import { gridProGenesisDatasourceEventNames } from '../grid-pro-genesis-datasource';
6
6
  import { datasourceEventNames, } from '../grid-pro-genesis-datasource/datasource-events.types';
7
7
  import { logger } from '../utils';
8
8
  import { GridProBaseDatasource } from './base.datasource';
9
+ import { filterDataserverResult } from './dataserver-result.filter';
9
10
  import { getFilterByFieldType, getServerSideFilterParamsByFieldType, } from './server-side.grid-definitions';
10
11
  import { DataserverServerSideDatasource } from './server-side.resource-dataserver';
11
12
  import { ReqRepServerSideDatasource } from './server-side.resource-reqrep';
@@ -30,7 +31,7 @@ const withoutColumnDefs = null;
30
31
  * @fires base-datasource-connected - Fired when error state is cleared after connection succeeds
31
32
  * @fires datasource-loading-finished - Fired when pending stream transactions are flushed with no row changes
32
33
  * @fires datasource-no-data-available - Fired when loading finishes with an empty row set
33
- * @fires datasource-data-changed - Fired when row data changes from applied stream transactions. detail: `DataChangedEventDetail`
34
+ * @fires datasource-data-changed - Fired when rows are delivered to the grid, a live stream pushes a change, a poll detects one, or a re-read no longer returns a row; `add` are rows the grid did not hold before, `update` are held rows whose content changed, `remove` carries the row id. detail: `DataChangedEventDetail`
34
35
  * @fires cache-filter-config - Fired to persist filter configuration for the grid
35
36
  * @fires refresh-server-side - Fired to request a server-side refresh. detail: `RefreshServerSideEventDetail`
36
37
  * @fires set-server-side-datasource - Fired to attach or clear the server-side row model datasource
@@ -41,7 +42,7 @@ const withoutColumnDefs = null;
41
42
  * @fires datasource-init - Fired when the server-side grid model should initialize data
42
43
  * @fires datasource-schema-updated - Fired when column metadata or defs are updated. detail: `SchemaUpdatedEventDetail`
43
44
  * @fires datasource-filters-restored - Fired when persisted filters are reapplied
44
- * @fires datasource-data-cleared - Fired when server-side row data is cleared. detail: `DataClearedEventDetail`
45
+ * @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`
45
46
  * @fires apply-server-side-transaction - Fired to apply a server-side row transaction
46
47
  * @fires datasource-ready - Fired when the host grid is ready to start loading data
47
48
  */
@@ -91,6 +92,8 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
91
92
  errorHandlerFunc: this.handleErrors.bind(this),
92
93
  onNoDataAvailableFunc: () => this.$emit(datasourceEventNames.noDataAvailable),
93
94
  onDataAvailableFunc: () => this.$emit(datasourceEventNames.loadingFinished),
95
+ onRowsDeliveredFunc: this.reportDeliveredRows.bind(this),
96
+ onRowsWithdrawnFunc: this.reportWithdrawnRows.bind(this),
94
97
  resourceName: this.resourceName,
95
98
  resourceParams: this.datasource.requestOnlyParams,
96
99
  resourceIndexes: this.indexes,
@@ -107,6 +110,8 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
107
110
  errorHandlerFunc: this.handleErrors.bind(this),
108
111
  onNoDataAvailableFunc: () => this.$emit(datasourceEventNames.noDataAvailable),
109
112
  onDataAvailableFunc: () => this.$emit(datasourceEventNames.loadingFinished),
113
+ onRowsDeliveredFunc: this.reportDeliveredRows.bind(this),
114
+ onRowsWithdrawnFunc: this.reportWithdrawnRows.bind(this),
110
115
  resourceName: this.resourceName,
111
116
  resourceParams: this.datasource.dataserverOnlyParams,
112
117
  resourceIndexes: this.indexes,
@@ -195,9 +200,12 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
195
200
  */
196
201
  reloadResourceData(params) {
197
202
  return __awaiter(this, void 0, void 0, function* () {
198
- var _a;
203
+ var _a, _b, _c;
199
204
  this.datasource.destroy();
200
- (_a = this.liveUpdatesStream) === null || _a === void 0 ? void 0 : _a.unsubscribe();
205
+ // A block request still in flight was read for the cache being dropped here; its reply must
206
+ // not be reported as rows the grid holds.
207
+ (_b = (_a = this.ssrmDatasource) === null || _a === void 0 ? void 0 : _a.invalidatePendingLoads) === null || _b === void 0 ? void 0 : _b.call(_a);
208
+ (_c = this.liveUpdatesStream) === null || _c === void 0 ? void 0 : _c.unsubscribe();
201
209
  this.liveUpdatesStream = undefined;
202
210
  // Emit event to cache current filter model before clearing data
203
211
  this.$emit(datasourceEventNames.cacheFilterConfig);
@@ -329,20 +337,22 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
329
337
  yield this.init();
330
338
  });
331
339
  }
340
+ /**
341
+ * Forgets every held row and tells consumers, as the client-side datasource does: the grid is
342
+ * dropping its rows (a reload, a filter or sort change, or teardown). `withColumnDefs` also
343
+ * clears the column definitions.
344
+ * @internal
345
+ */
332
346
  clearRowData(withColumnDefs = true) {
333
347
  this.rowData = new Map();
334
- if (withColumnDefs) {
335
- // Emit event to clear column definitions
336
- this.$emit(datasourceEventNames.dataCleared, {
337
- includeSchema: true,
338
- });
339
- }
340
- this.transactionData = { remove: [] };
348
+ this.resetTransaction();
349
+ this.$emit(datasourceEventNames.dataCleared, {
350
+ includeSchema: Boolean(withColumnDefs),
351
+ });
341
352
  // Emit event to apply server side transaction for removing all data
342
353
  this.$emit(datasourceEventNames.applyServerSideTransaction, {
343
- transaction: this.transactionData,
354
+ transaction: { remove: [] },
344
355
  });
345
- this.transactionData = undefined;
346
356
  }
347
357
  /**
348
358
  * Checks whether the resolved resource can be served through the Server-Side Row Model.
@@ -471,7 +481,7 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
471
481
  const rowId = row[this.rowId];
472
482
  if (this.previousPollResult.has(rowId)) {
473
483
  const previousRow = this.previousPollResult.get(rowId);
474
- if (this.serializer.serialize(previousRow) !== this.serializer.serialize(row)) {
484
+ if (this.hasRowContentChanged(previousRow, row)) {
475
485
  rowsToUpdate.push(row);
476
486
  }
477
487
  }
@@ -492,6 +502,31 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
492
502
  removed: rowsToDelete.length,
493
503
  totalInPoll: currentPollMap.size,
494
504
  });
505
+ // The poll has already classified the rows against the previous poll, so re-classify the
506
+ // inserts against the rows actually held: a row shifted into the polled page from a block
507
+ // above is new to the poll but not to the grid, and reporting it as `add` would duplicate
508
+ // it for consumers.
509
+ const transaction = this.resetTransaction();
510
+ rowsToInsert.forEach((row) => {
511
+ const rowId = row[this.rowId];
512
+ const known = this.rowData.get(rowId);
513
+ if (!known) {
514
+ transaction.add.push(row);
515
+ }
516
+ else if (this.hasRowContentChanged(known, row)) {
517
+ transaction.update.push(row);
518
+ }
519
+ });
520
+ transaction.update.push(...rowsToUpdate);
521
+ transaction.remove.push(...rowsToDelete.map((row) => ({ [this.rowId]: row[this.rowId] })));
522
+ [...rowsToInsert, ...rowsToUpdate].forEach((row) => this.rowData.set(row[this.rowId], row));
523
+ rowsToDelete.forEach((row) => this.rowData.delete(row[this.rowId]));
524
+ this.emitTransaction(transaction);
525
+ // A consumer's handler may have reloaded or destroyed this datasource synchronously; the
526
+ // grid is then being purged and the transaction belongs to rows it no longer holds.
527
+ if (this.transactionData !== transaction) {
528
+ return;
529
+ }
495
530
  this.$emit(datasourceEventNames.applyServerSideTransaction, {
496
531
  transaction: {
497
532
  add: rowsToInsert,
@@ -514,25 +549,41 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
514
549
  this.ssrmDatasource.serverRowsCount += netRowChange;
515
550
  this.ssrmDatasource.clientRowsCount += netRowChange;
516
551
  }
517
- this.transactionData = { add: [], remove: [], update: [] };
552
+ const transaction = this.resetTransaction();
518
553
  this.handleStreamInserts(inserts);
519
554
  this.handleStreamUpdates(updates);
520
555
  this.handleStreamDeletes(deletes);
521
- if (this.transactionData.add.length > 0) {
556
+ // A MODIFY for a row this element has not seen delivered is not reported to consumers (they
557
+ // do not hold it), but it still goes to the grid, which ignores rows outside its store: the
558
+ // stream pre-merge above made it a full row, and the grid may hold it from a block loaded
559
+ // before this element started mirroring deliveries.
560
+ const gridOnlyUpdates = updates.filter((row) => {
561
+ const rowId = row === null || row === void 0 ? void 0 : row[this.rowId];
562
+ return rowId !== undefined && rowId !== null && !this.rowData.has(rowId);
563
+ });
564
+ // Consumers hear the change first; the grid transactions below then catch the grid up.
565
+ this.emitTransaction(transaction);
566
+ // A consumer's handler may have reloaded or destroyed this datasource synchronously; the
567
+ // grid is then being purged and the transaction belongs to rows it no longer holds.
568
+ if (this.transactionData !== transaction) {
569
+ return;
570
+ }
571
+ if (transaction.add.length > 0) {
522
572
  this.$emit(datasourceEventNames.applyServerSideTransaction, {
523
573
  transaction: {
524
574
  route: [],
525
- add: this.transactionData.add,
526
- addIndex: this.reverse ? 0 : this.transactionData.addIndex,
575
+ add: transaction.add,
576
+ addIndex: this.reverse ? 0 : transaction.addIndex,
527
577
  },
528
578
  });
529
579
  }
530
- if (this.transactionData.update.length > 0 || this.transactionData.remove.length > 0) {
580
+ const gridUpdates = [...transaction.update, ...gridOnlyUpdates];
581
+ if (gridUpdates.length > 0 || transaction.remove.length > 0) {
531
582
  this.$emit(datasourceEventNames.applyServerSideTransaction, {
532
583
  transaction: {
533
584
  route: [],
534
- update: this.transactionData.update,
535
- remove: this.transactionData.remove,
585
+ update: gridUpdates,
586
+ remove: transaction.remove,
536
587
  },
537
588
  });
538
589
  }
@@ -563,9 +614,12 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
563
614
  // a complete row — without this, unchanged columns are cleared on every update.
564
615
  const rowData = this.ssrmDatasource.rowData;
565
616
  const mergedRows = normalizedRows.map((row) => {
566
- var _a;
617
+ var _a, _b;
567
618
  const details = (_a = row.DETAILS) !== null && _a !== void 0 ? _a : {};
568
- const rowRef = details[this.rowId];
619
+ // Genesis keys pushes by the default id. Under a custom `row-id` DETAILS carries no
620
+ // custom id at all, so fall back to the held row that carries this ROW_REF.
621
+ const defaultRowId = this.defaultRowIdByResourceType;
622
+ const rowRef = ((_b = details[this.rowId]) !== null && _b !== void 0 ? _b : this.resolveRowId(Object.assign(Object.assign({}, row), { [defaultRowId]: details[defaultRowId] })));
569
623
  const existingRow = rowRef != null ? rowData === null || rowData === void 0 ? void 0 : rowData.get(rowRef) : undefined;
570
624
  if (!existingRow)
571
625
  return row;
@@ -577,7 +631,7 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
577
631
  return Object.assign(Object.assign({}, merged), { DETAILS: details });
578
632
  });
579
633
  const normalizedMessage = Object.assign(Object.assign({}, rawMessage), { ROW: mergedRows });
580
- const nextMessage = dataServerResultFilter(normalizedMessage, this.rowId);
634
+ const nextMessage = filterDataserverResult(normalizedMessage, this.rowId);
581
635
  this.applyLiveServerSideUpdate(nextMessage);
582
636
  });
583
637
  const onError = (error) => {
@@ -611,9 +665,6 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
611
665
  throw new Error('loadMore() method is not supported for server-side datasource');
612
666
  }
613
667
  };
614
- __decorate([
615
- JSONSerializer
616
- ], GridProServerSideDatasource.prototype, "serializer", void 0);
617
668
  __decorate([
618
669
  Datasource
619
670
  ], GridProServerSideDatasource.prototype, "pollingDatasource", void 0);