@genesislcap/grid-pro 15.24.2 → 15.25.0-FUI-2611.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.
- package/dist/custom-elements.json +549 -44
- package/dist/dts/datasource/base.datasource.d.ts +70 -7
- package/dist/dts/datasource/base.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/base.types.d.ts +27 -0
- package/dist/dts/datasource/base.types.d.ts.map +1 -1
- package/dist/dts/datasource/delivered-block.ledger.d.ts +32 -0
- package/dist/dts/datasource/delivered-block.ledger.d.ts.map +1 -0
- package/dist/dts/datasource/infinite.datasource.d.ts +33 -1
- package/dist/dts/datasource/infinite.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/infinite.resource.d.ts +75 -6
- package/dist/dts/datasource/infinite.resource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.datasource.d.ts +14 -4
- package/dist/dts/datasource/server-side.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts +24 -0
- package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-dataserver.d.ts +26 -0
- package/dist/dts/datasource/server-side.resource-dataserver.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-reqrep.d.ts.map +1 -1
- package/dist/dts/grid-pro-beta.d.ts +6 -0
- package/dist/dts/grid-pro-beta.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts +4 -0
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts.map +1 -1
- package/dist/dts/grid-pro.d.ts.map +1 -1
- package/dist/dts/react.d.ts +1 -1
- package/dist/esm/datasource/base.datasource.js +177 -23
- package/dist/esm/datasource/delivered-block.ledger.js +59 -0
- package/dist/esm/datasource/infinite.datasource.js +61 -17
- package/dist/esm/datasource/infinite.resource.js +81 -10
- package/dist/esm/datasource/server-side.datasource.js +73 -25
- package/dist/esm/datasource/server-side.resource-base.js +43 -0
- package/dist/esm/datasource/server-side.resource-dataserver.js +77 -17
- package/dist/esm/datasource/server-side.resource-reqrep.js +12 -0
- package/dist/esm/grid-pro-beta.js +20 -2
- package/dist/esm/grid-pro.js +4 -1
- package/dist/grid-pro.api.json +35 -35
- package/dist/grid-pro.d.ts +228 -17
- package/dist/react.cjs +6 -6
- package/dist/react.mjs +6 -6
- package/package.json +13 -13
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { dataServerResultFilter, MessageType, } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { logger } from '../utils';
|
|
4
|
+
import { DeliveredBlockLedger } from './delivered-block.ledger';
|
|
4
5
|
import { convertFilterModelToCriteria } from './filter.utils';
|
|
5
6
|
/**
|
|
6
7
|
* Converts an AG Grid sort model to the Genesis ORDER_BY form the resource expects.
|
|
@@ -45,6 +46,17 @@ export function convertSortModelToOrderBy(sortModel, resourceIndexes, isRequestS
|
|
|
45
46
|
*/
|
|
46
47
|
export class GenesisInfiniteDatasource {
|
|
47
48
|
constructor(options) {
|
|
49
|
+
/**
|
|
50
|
+
* Bumped whenever the rows this datasource was reading for are dropped (a teardown, or a
|
|
51
|
+
* filter/sort change). A REQUEST_SERVER reply that resolves for an earlier generation was read
|
|
52
|
+
* for a cache the grid no longer has.
|
|
53
|
+
* @remarks Deliberately a generation rather than a "destroyed" flag: AG Grid calls `destroy()`
|
|
54
|
+
* on the datasource bean it then keeps using (`InfiniteRowModel.start()` re-applies the
|
|
55
|
+
* `datasource` grid option, and `setDatasource` destroys the previous one first), so a sticky
|
|
56
|
+
* flag would disable the instance the grid is still driving.
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
this.loadGeneration = 0;
|
|
48
60
|
/**
|
|
49
61
|
* Session-cumulative row cache, keyed by row id. A Map preserves insertion order, so the
|
|
50
62
|
* cache can be sliced by block range; Genesis splits batches across messages and later
|
|
@@ -69,7 +81,12 @@ export class GenesisInfiniteDatasource {
|
|
|
69
81
|
this.getMoreRowsFunc = options.getMoreRowsFunc;
|
|
70
82
|
this.onRowsInvalidatedFunc = options.onRowsInvalidatedFunc;
|
|
71
83
|
this.onRowsUpdatedFunc = options.onRowsUpdatedFunc;
|
|
84
|
+
this.onRowsDeliveredFunc = options.onRowsDeliveredFunc;
|
|
85
|
+
this.onRowsWithdrawnFunc = options.onRowsWithdrawnFunc;
|
|
86
|
+
this.onRowsEvictedFunc = options.onRowsEvictedFunc;
|
|
87
|
+
this.onRowCacheResetFunc = options.onRowCacheResetFunc;
|
|
72
88
|
this.errorHandlerFunc = options.errorHandlerFunc;
|
|
89
|
+
this.ledger = new DeliveredBlockLedger(options.rowId, options.maxBlocksInCache);
|
|
73
90
|
this.resourceName = options.resourceName;
|
|
74
91
|
this.resourceParams = Object.assign({}, options.resourceParams);
|
|
75
92
|
this.resourceIndexes = options.resourceIndexes;
|
|
@@ -164,6 +181,7 @@ export class GenesisInfiniteDatasource {
|
|
|
164
181
|
const combinedCriteria = [this.baseCriteria, filterCriteria].filter(Boolean).join(' && ');
|
|
165
182
|
// Build sort config
|
|
166
183
|
const sortConfig = convertSortModelToOrderBy(params.sortModel, this.resourceIndexes, this.isRequestServer);
|
|
184
|
+
this.noteRequestKey(combinedCriteria, sortConfig);
|
|
167
185
|
// DATASERVER pages through a persistent subscription; REQUEST_SERVER is stateless and
|
|
168
186
|
// pages by OFFSET on a fresh request each time.
|
|
169
187
|
if (!this.isRequestServer) {
|
|
@@ -172,7 +190,19 @@ export class GenesisInfiniteDatasource {
|
|
|
172
190
|
}
|
|
173
191
|
const requestParams = this.buildRequestServerParams(params.startRow, combinedCriteria, sortConfig);
|
|
174
192
|
logger.debug('Infinite datasource request params', requestParams);
|
|
193
|
+
// Taken after noteRequestKey, so this is the generation the request belongs to: only a
|
|
194
|
+
// reset that lands while it is in flight invalidates the reply.
|
|
195
|
+
const generation = this.loadGeneration;
|
|
175
196
|
const result = yield this.createSnapshotFunc(requestParams);
|
|
197
|
+
// The rows this block was read for have since been dropped (a filter or sort change): AG
|
|
198
|
+
// Grid has purged the block, and the host must not learn of rows the grid will never hold.
|
|
199
|
+
// The block is still failed rather than left hanging - AG Grid caps concurrent requests
|
|
200
|
+
// and frees a slot only on success or fail, so a silent return stops it ever asking again.
|
|
201
|
+
if (generation !== this.loadGeneration) {
|
|
202
|
+
logger.debug('Infinite REQUEST_SERVER reply discarded: the block cache was reset');
|
|
203
|
+
params.failCallback();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
176
206
|
if (!result) {
|
|
177
207
|
params.failCallback();
|
|
178
208
|
return;
|
|
@@ -181,6 +211,7 @@ export class GenesisInfiniteDatasource {
|
|
|
181
211
|
const rowData = result.REPLY;
|
|
182
212
|
const lastRow = this.getLastRow(result, params.startRow, rowData.length, Boolean(combinedCriteria));
|
|
183
213
|
params.successCallback(rowData, lastRow);
|
|
214
|
+
this.deliverRows(params.startRow, rowData);
|
|
184
215
|
return;
|
|
185
216
|
}
|
|
186
217
|
// Fallback - empty result
|
|
@@ -202,20 +233,33 @@ export class GenesisInfiniteDatasource {
|
|
|
202
233
|
* outstanding at once, and each settles as soon as the cache covers its range.
|
|
203
234
|
* @internal
|
|
204
235
|
*/
|
|
236
|
+
/**
|
|
237
|
+
* Notes the CRITERIA_MATCH/ORDER_BY a block is being read with. A change means AG Grid has
|
|
238
|
+
* purged its cache and restarted from block 0: on DATASERVER the subscription is re-opened
|
|
239
|
+
* (those are DATA_LOGON parameters), and on both transports the host is told the rows it held
|
|
240
|
+
* are gone, so the rows read under the new filter or sort are reported as new.
|
|
241
|
+
* @internal
|
|
242
|
+
*/
|
|
243
|
+
noteRequestKey(criteria, sortConfig) {
|
|
244
|
+
var _a;
|
|
245
|
+
const key = JSON.stringify(Object.assign({ criteria }, sortConfig));
|
|
246
|
+
if (this.subscriptionKey !== undefined && this.subscriptionKey !== key) {
|
|
247
|
+
// Whatever is in flight was read for the old filter or sort.
|
|
248
|
+
this.loadGeneration += 1;
|
|
249
|
+
if (!this.isRequestServer) {
|
|
250
|
+
this.teardownDataserverSubscription();
|
|
251
|
+
}
|
|
252
|
+
this.ledger.clear();
|
|
253
|
+
(_a = this.onRowCacheResetFunc) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
254
|
+
}
|
|
255
|
+
this.subscriptionKey = key;
|
|
256
|
+
}
|
|
205
257
|
getDataserverRows(params, criteria, sortConfig) {
|
|
206
258
|
if (!this.createDataserverStreamFunc || !this.getMoreRowsFunc) {
|
|
207
259
|
logger.error('Infinite DATASERVER paging requires a stream and a getMoreRows function');
|
|
208
260
|
params.failCallback();
|
|
209
261
|
return;
|
|
210
262
|
}
|
|
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
263
|
const startRow = Number.isFinite(Number(params.startRow)) ? Number(params.startRow) : 0;
|
|
220
264
|
const endRow = Number.isFinite(Number(params.endRow))
|
|
221
265
|
? Number(params.endRow)
|
|
@@ -380,7 +424,7 @@ export class GenesisInfiniteDatasource {
|
|
|
380
424
|
deletes: (_k = (_j = result.deletes) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0,
|
|
381
425
|
});
|
|
382
426
|
this.reset();
|
|
383
|
-
(_l = this.onRowsInvalidatedFunc) === null || _l === void 0 ? void 0 : _l.call(this);
|
|
427
|
+
(_l = this.onRowsInvalidatedFunc) === null || _l === void 0 ? void 0 : _l.call(this, result);
|
|
384
428
|
return;
|
|
385
429
|
}
|
|
386
430
|
if (modified) {
|
|
@@ -388,7 +432,7 @@ export class GenesisInfiniteDatasource {
|
|
|
388
432
|
logger.debug('Infinite DATASERVER live update patched into the cache', {
|
|
389
433
|
updates: (_o = (_m = result.updates) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : 0,
|
|
390
434
|
});
|
|
391
|
-
(_p = this.onRowsUpdatedFunc) === null || _p === void 0 ? void 0 : _p.call(this);
|
|
435
|
+
(_p = this.onRowsUpdatedFunc) === null || _p === void 0 ? void 0 : _p.call(this, result);
|
|
392
436
|
}
|
|
393
437
|
}
|
|
394
438
|
/**
|
|
@@ -506,6 +550,25 @@ export class GenesisInfiniteDatasource {
|
|
|
506
550
|
lastRow,
|
|
507
551
|
});
|
|
508
552
|
block.success(rows, lastRow);
|
|
553
|
+
this.deliverRows(block.startRow, rows);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Reports rows handed to the grid for a block, then the rows a re-read of that block no longer
|
|
557
|
+
* carries and the rows whose block the grid evicted.
|
|
558
|
+
* @internal
|
|
559
|
+
*/
|
|
560
|
+
deliverRows(startRow, rows) {
|
|
561
|
+
var _a, _b, _c;
|
|
562
|
+
const { withdrawnRowIds, evictedRowIds } = this.ledger.record(startRow, rows);
|
|
563
|
+
if (rows.length > 0) {
|
|
564
|
+
(_a = this.onRowsDeliveredFunc) === null || _a === void 0 ? void 0 : _a.call(this, rows);
|
|
565
|
+
}
|
|
566
|
+
if (withdrawnRowIds.length > 0) {
|
|
567
|
+
(_b = this.onRowsWithdrawnFunc) === null || _b === void 0 ? void 0 : _b.call(this, withdrawnRowIds);
|
|
568
|
+
}
|
|
569
|
+
if (evictedRowIds.length > 0) {
|
|
570
|
+
(_c = this.onRowsEvictedFunc) === null || _c === void 0 ? void 0 : _c.call(this, evictedRowIds);
|
|
571
|
+
}
|
|
509
572
|
}
|
|
510
573
|
/**
|
|
511
574
|
* Warns when the view filled up before the data ran out.
|
|
@@ -566,8 +629,16 @@ export class GenesisInfiniteDatasource {
|
|
|
566
629
|
this.moreRows = undefined;
|
|
567
630
|
this.moreRowsInFlight = false;
|
|
568
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Drops the subscription and the cached rows.
|
|
634
|
+
* @remarks AG Grid calls this on the bean it then keeps using (`InfiniteRowModel.start()`
|
|
635
|
+
* re-applies the `datasource` grid option, and `setDatasource` destroys the previous one
|
|
636
|
+
* first), and it can land while a block request is in flight. So it must leave the datasource
|
|
637
|
+
* able to serve that reply and the next `getRows`: it drops the subscription, not the instance.
|
|
638
|
+
*/
|
|
569
639
|
destroy() {
|
|
570
640
|
this.teardownDataserverSubscription();
|
|
641
|
+
this.ledger.clear();
|
|
571
642
|
logger.debug('GenesisInfiniteDatasource destroyed');
|
|
572
643
|
}
|
|
573
644
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __awaiter, __decorate } from "tslib";
|
|
2
2
|
import { dataServerResultFilter, Datasource, MessageType, normaliseCriteria, toFieldMetadata, } from '@genesislcap/foundation-comms';
|
|
3
|
-
import {
|
|
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';
|
|
@@ -30,7 +30,7 @@ const withoutColumnDefs = null;
|
|
|
30
30
|
* @fires base-datasource-connected - Fired when error state is cleared after connection succeeds
|
|
31
31
|
* @fires datasource-loading-finished - Fired when pending stream transactions are flushed with no row changes
|
|
32
32
|
* @fires datasource-no-data-available - Fired when loading finishes with an empty row set
|
|
33
|
-
* @fires datasource-data-changed - Fired when
|
|
33
|
+
* @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
34
|
* @fires cache-filter-config - Fired to persist filter configuration for the grid
|
|
35
35
|
* @fires refresh-server-side - Fired to request a server-side refresh. detail: `RefreshServerSideEventDetail`
|
|
36
36
|
* @fires set-server-side-datasource - Fired to attach or clear the server-side row model datasource
|
|
@@ -41,7 +41,7 @@ const withoutColumnDefs = null;
|
|
|
41
41
|
* @fires datasource-init - Fired when the server-side grid model should initialize data
|
|
42
42
|
* @fires datasource-schema-updated - Fired when column metadata or defs are updated. detail: `SchemaUpdatedEventDetail`
|
|
43
43
|
* @fires datasource-filters-restored - Fired when persisted filters are reapplied
|
|
44
|
-
* @fires datasource-data-cleared - Fired when
|
|
44
|
+
* @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
45
|
* @fires apply-server-side-transaction - Fired to apply a server-side row transaction
|
|
46
46
|
* @fires datasource-ready - Fired when the host grid is ready to start loading data
|
|
47
47
|
*/
|
|
@@ -91,6 +91,10 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
91
91
|
errorHandlerFunc: this.handleErrors.bind(this),
|
|
92
92
|
onNoDataAvailableFunc: () => this.$emit(datasourceEventNames.noDataAvailable),
|
|
93
93
|
onDataAvailableFunc: () => this.$emit(datasourceEventNames.loadingFinished),
|
|
94
|
+
onRowsDeliveredFunc: this.reportDeliveredRows.bind(this),
|
|
95
|
+
onRowsWithdrawnFunc: this.reportWithdrawnRows.bind(this),
|
|
96
|
+
onRowsEvictedFunc: this.forgetRows.bind(this),
|
|
97
|
+
maxBlocksInCache: this.gridMaxBlocksInCache,
|
|
94
98
|
resourceName: this.resourceName,
|
|
95
99
|
resourceParams: this.datasource.requestOnlyParams,
|
|
96
100
|
resourceIndexes: this.indexes,
|
|
@@ -107,6 +111,11 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
107
111
|
errorHandlerFunc: this.handleErrors.bind(this),
|
|
108
112
|
onNoDataAvailableFunc: () => this.$emit(datasourceEventNames.noDataAvailable),
|
|
109
113
|
onDataAvailableFunc: () => this.$emit(datasourceEventNames.loadingFinished),
|
|
114
|
+
onRowsDeliveredFunc: this.reportDeliveredRows.bind(this),
|
|
115
|
+
onRowsWithdrawnFunc: this.reportWithdrawnRows.bind(this),
|
|
116
|
+
onRowsEvictedFunc: this.forgetRows.bind(this),
|
|
117
|
+
maxBlocksInCache: this.gridMaxBlocksInCache,
|
|
118
|
+
gridReceivesLiveUpdates: this.liveUpdates,
|
|
110
119
|
resourceName: this.resourceName,
|
|
111
120
|
resourceParams: this.datasource.dataserverOnlyParams,
|
|
112
121
|
resourceIndexes: this.indexes,
|
|
@@ -145,6 +154,16 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
145
154
|
*/
|
|
146
155
|
this.hasCapturedFirstPollResult = false;
|
|
147
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The `maxBlocksInCache` the consumer gave the grid, if any, so block eviction can be mirrored
|
|
159
|
+
* in the held-row bookkeeping. The server-side defaults set none, so AG Grid keeps every block.
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
get gridMaxBlocksInCache() {
|
|
163
|
+
var _a;
|
|
164
|
+
const value = Number((_a = this.deferredGridOptions) === null || _a === void 0 ? void 0 : _a.maxBlocksInCache);
|
|
165
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
166
|
+
}
|
|
148
167
|
resourceNameChanged(oldValue, newValue) {
|
|
149
168
|
if (!oldValue || oldValue === newValue)
|
|
150
169
|
return;
|
|
@@ -195,9 +214,12 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
195
214
|
*/
|
|
196
215
|
reloadResourceData(params) {
|
|
197
216
|
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
-
var _a;
|
|
217
|
+
var _a, _b, _c;
|
|
199
218
|
this.datasource.destroy();
|
|
200
|
-
|
|
219
|
+
// A block request still in flight was read for the cache being dropped here; its reply must
|
|
220
|
+
// not be reported as rows the grid holds.
|
|
221
|
+
(_b = (_a = this.ssrmDatasource) === null || _a === void 0 ? void 0 : _a.invalidatePendingLoads) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
222
|
+
(_c = this.liveUpdatesStream) === null || _c === void 0 ? void 0 : _c.unsubscribe();
|
|
201
223
|
this.liveUpdatesStream = undefined;
|
|
202
224
|
// Emit event to cache current filter model before clearing data
|
|
203
225
|
this.$emit(datasourceEventNames.cacheFilterConfig);
|
|
@@ -329,20 +351,22 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
329
351
|
yield this.init();
|
|
330
352
|
});
|
|
331
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Forgets every held row and tells consumers, as the client-side datasource does: the grid is
|
|
356
|
+
* dropping its rows (a reload, a filter or sort change, or teardown). `withColumnDefs` also
|
|
357
|
+
* clears the column definitions.
|
|
358
|
+
* @internal
|
|
359
|
+
*/
|
|
332
360
|
clearRowData(withColumnDefs = true) {
|
|
333
361
|
this.rowData = new Map();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
this.transactionData = { remove: [] };
|
|
362
|
+
this.resetTransaction();
|
|
363
|
+
this.$emit(datasourceEventNames.dataCleared, {
|
|
364
|
+
includeSchema: Boolean(withColumnDefs),
|
|
365
|
+
});
|
|
341
366
|
// Emit event to apply server side transaction for removing all data
|
|
342
367
|
this.$emit(datasourceEventNames.applyServerSideTransaction, {
|
|
343
|
-
transaction:
|
|
368
|
+
transaction: { remove: [] },
|
|
344
369
|
});
|
|
345
|
-
this.transactionData = undefined;
|
|
346
370
|
}
|
|
347
371
|
/**
|
|
348
372
|
* Checks whether the resolved resource can be served through the Server-Side Row Model.
|
|
@@ -471,7 +495,7 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
471
495
|
const rowId = row[this.rowId];
|
|
472
496
|
if (this.previousPollResult.has(rowId)) {
|
|
473
497
|
const previousRow = this.previousPollResult.get(rowId);
|
|
474
|
-
if (this.
|
|
498
|
+
if (this.hasRowContentChanged(previousRow, row)) {
|
|
475
499
|
rowsToUpdate.push(row);
|
|
476
500
|
}
|
|
477
501
|
}
|
|
@@ -492,6 +516,17 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
492
516
|
removed: rowsToDelete.length,
|
|
493
517
|
totalInPoll: currentPollMap.size,
|
|
494
518
|
});
|
|
519
|
+
// The poll has already classified the rows, so report them as they are (the same shape
|
|
520
|
+
// the client-side model emits: a remove carries just the row id) and keep the held rows
|
|
521
|
+
// in step so a later block re-read is classified correctly.
|
|
522
|
+
this.transactionData = {
|
|
523
|
+
add: rowsToInsert,
|
|
524
|
+
update: rowsToUpdate,
|
|
525
|
+
remove: rowsToDelete.map((row) => ({ [this.rowId]: row[this.rowId] })),
|
|
526
|
+
};
|
|
527
|
+
[...rowsToInsert, ...rowsToUpdate].forEach((row) => this.rowData.set(row[this.rowId], row));
|
|
528
|
+
rowsToDelete.forEach((row) => this.rowData.delete(row[this.rowId]));
|
|
529
|
+
this.emitTransaction(this.transactionData);
|
|
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
|
-
|
|
552
|
+
const transaction = this.resetTransaction();
|
|
518
553
|
this.handleStreamInserts(inserts);
|
|
519
554
|
this.handleStreamUpdates(updates);
|
|
520
555
|
this.handleStreamDeletes(deletes);
|
|
521
|
-
|
|
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:
|
|
526
|
-
addIndex: this.reverse ? 0 :
|
|
575
|
+
add: transaction.add,
|
|
576
|
+
addIndex: this.reverse ? 0 : transaction.addIndex,
|
|
527
577
|
},
|
|
528
578
|
});
|
|
529
579
|
}
|
|
530
|
-
|
|
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:
|
|
535
|
-
remove:
|
|
585
|
+
update: gridUpdates,
|
|
586
|
+
remove: transaction.remove,
|
|
536
587
|
},
|
|
537
588
|
});
|
|
538
589
|
}
|
|
@@ -611,9 +662,6 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
611
662
|
throw new Error('loadMore() method is not supported for server-side datasource');
|
|
612
663
|
}
|
|
613
664
|
};
|
|
614
|
-
__decorate([
|
|
615
|
-
JSONSerializer
|
|
616
|
-
], GridProServerSideDatasource.prototype, "serializer", void 0);
|
|
617
665
|
__decorate([
|
|
618
666
|
Datasource
|
|
619
667
|
], GridProServerSideDatasource.prototype, "pollingDatasource", void 0);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __awaiter, __decorate } from "tslib";
|
|
2
2
|
import { Auth, Connect } from '@genesislcap/foundation-comms';
|
|
3
3
|
import { UUID } from '@genesislcap/foundation-utils';
|
|
4
|
+
import { DeliveredBlockLedger } from './delivered-block.ledger';
|
|
4
5
|
import { convertDateFilterToCriteria, convertNumberFilterToCriteria, convertSetFilterToCriteria, convertTextFilterToCriteria, } from './filter.utils';
|
|
5
6
|
/**
|
|
6
7
|
* Base class for server-side resource datasources used in Grid Pro SSRM implementations.
|
|
@@ -11,6 +12,12 @@ import { convertDateFilterToCriteria, convertNumberFilterToCriteria, convertSetF
|
|
|
11
12
|
export class BaseServerSideDatasource {
|
|
12
13
|
constructor(options) {
|
|
13
14
|
this.rowData = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Bumped whenever the rows the grid holds are dropped (destroy, refresh, reload). A req/rep
|
|
17
|
+
* reply that resolves for an earlier generation belongs to a cache the grid no longer has.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
this.loadGeneration = 0;
|
|
14
21
|
/**
|
|
15
22
|
* The number of rows in the grid. THIS DO NOT CONSIDER CRITERIA_MATCH scenarios.
|
|
16
23
|
* @internal
|
|
@@ -28,6 +35,9 @@ export class BaseServerSideDatasource {
|
|
|
28
35
|
this.errorHandlerFunc = options.errorHandlerFunc;
|
|
29
36
|
this.onNoDataAvailableFunc = options.onNoDataAvailableFunc;
|
|
30
37
|
this.onDataAvailableFunc = options.onDataAvailableFunc;
|
|
38
|
+
this.onRowsDeliveredFunc = options.onRowsDeliveredFunc;
|
|
39
|
+
this.onRowsWithdrawnFunc = options.onRowsWithdrawnFunc;
|
|
40
|
+
this.onRowsEvictedFunc = options.onRowsEvictedFunc;
|
|
31
41
|
this.resourceName = options.resourceName;
|
|
32
42
|
this.resourceParams = options.resourceParams;
|
|
33
43
|
this.originalCriteriaMatch = this.getResourceParam('CRITERIA_MATCH');
|
|
@@ -36,6 +46,7 @@ export class BaseServerSideDatasource {
|
|
|
36
46
|
this.maxRows = options.maxRows;
|
|
37
47
|
this.maxView = options.maxView;
|
|
38
48
|
this.rowId = options.rowId;
|
|
49
|
+
this.ledger = new DeliveredBlockLedger(options.rowId, options.maxBlocksInCache);
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
52
|
* Gets a resource parameter, handling both REQUEST_SERVER (DETAILS structure) and DATASERVER (flat structure)
|
|
@@ -116,6 +127,7 @@ export class BaseServerSideDatasource {
|
|
|
116
127
|
});
|
|
117
128
|
}
|
|
118
129
|
destroy() {
|
|
130
|
+
this.invalidatePendingLoads();
|
|
119
131
|
this.rowData = new Map();
|
|
120
132
|
this.clientRowsCount = 0;
|
|
121
133
|
this.calculatedRowsCount = 0;
|
|
@@ -125,6 +137,15 @@ export class BaseServerSideDatasource {
|
|
|
125
137
|
this.sourceRef = undefined;
|
|
126
138
|
this.lastSuccessRowData = undefined;
|
|
127
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Marks every block request still in flight as stale: the grid is dropping the cache they were
|
|
142
|
+
* read for, so their replies must not be handed to the host as held rows.
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
invalidatePendingLoads() {
|
|
146
|
+
this.loadGeneration += 1;
|
|
147
|
+
this.ledger.clear();
|
|
148
|
+
}
|
|
128
149
|
refreshDatasource(params) {
|
|
129
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
130
151
|
this.destroy();
|
|
@@ -287,6 +308,9 @@ export class BaseServerSideDatasource {
|
|
|
287
308
|
completeEmptyResult(params) {
|
|
288
309
|
var _a, _b;
|
|
289
310
|
if ((_b = (_a = this.lastSuccessRowData) === null || _a === void 0 ? void 0 : _a.rowData) === null || _b === void 0 ? void 0 : _b.length) {
|
|
311
|
+
// Re-serving the previous block's snapshot keeps the SSRM request from hanging; it is not a
|
|
312
|
+
// delivery of new rows, so it is not reported - the snapshot may predate a poll that has
|
|
313
|
+
// since reported those rows changed or removed.
|
|
290
314
|
params.success(this.lastSuccessRowData);
|
|
291
315
|
return;
|
|
292
316
|
}
|
|
@@ -295,6 +319,25 @@ export class BaseServerSideDatasource {
|
|
|
295
319
|
params.success(successRowData);
|
|
296
320
|
this.notifyNoDataAvailableIfEmpty(params, successRowData);
|
|
297
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Reports rows handed to the grid for the block at `startRow`, then the rows a re-read of that
|
|
324
|
+
* block no longer carries and the rows whose block the grid evicted.
|
|
325
|
+
* @internal
|
|
326
|
+
*/
|
|
327
|
+
deliverRows(startRow, rows) {
|
|
328
|
+
var _a, _b, _c;
|
|
329
|
+
const delivered = rows !== null && rows !== void 0 ? rows : [];
|
|
330
|
+
const { withdrawnRowIds, evictedRowIds } = this.ledger.record(startRow, delivered);
|
|
331
|
+
if (delivered.length > 0) {
|
|
332
|
+
(_a = this.onRowsDeliveredFunc) === null || _a === void 0 ? void 0 : _a.call(this, delivered);
|
|
333
|
+
}
|
|
334
|
+
if (withdrawnRowIds.length > 0) {
|
|
335
|
+
(_b = this.onRowsWithdrawnFunc) === null || _b === void 0 ? void 0 : _b.call(this, withdrawnRowIds);
|
|
336
|
+
}
|
|
337
|
+
if (evictedRowIds.length > 0) {
|
|
338
|
+
(_c = this.onRowsEvictedFunc) === null || _c === void 0 ? void 0 : _c.call(this, evictedRowIds);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
298
341
|
/** Invokes `onNoDataAvailableFunc` when the first SSRM block has no rows. @internal */
|
|
299
342
|
notifyNoDataAvailableIfEmpty(params, successRowData) {
|
|
300
343
|
var _a;
|
|
@@ -9,6 +9,7 @@ import { BaseServerSideDatasource } from './server-side.resource-base';
|
|
|
9
9
|
*/
|
|
10
10
|
export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
11
11
|
constructor(options) {
|
|
12
|
+
var _a;
|
|
12
13
|
super(options);
|
|
13
14
|
/**
|
|
14
15
|
* Blocks awaiting stream messages. Settled blocks remove themselves; destroy() cancels
|
|
@@ -23,7 +24,24 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
23
24
|
* @internal
|
|
24
25
|
*/
|
|
25
26
|
this.deliveredRowCount = 0;
|
|
27
|
+
/**
|
|
28
|
+
* Ids of rows the server deleted that stay in the cache because the grid was not told
|
|
29
|
+
* (`live-updates` off) and they sit below the delivered watermark - see
|
|
30
|
+
* handleCurrentStreamLoad. They are not reported to the host as rows the grid holds, and
|
|
31
|
+
* their presence marks the cache out of step, so a re-read purges rather than serves them.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
this.retainedDeletedRowIds = new Set();
|
|
35
|
+
/**
|
|
36
|
+
* Set when the cache holds a change the grid's rows do not reflect (a tombstoned DELETE). The
|
|
37
|
+
* blocks AG Grid holds are still consistent with what it was given, but a re-read of one of
|
|
38
|
+
* them must not be served from the cache: it would hand back stale content, so the datasource
|
|
39
|
+
* refreshes instead and the grid re-reads everything from the server.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
this.cacheOutOfStep = false;
|
|
26
43
|
this.createDataserverStreamFunc = options.createDataserverStreamFunc;
|
|
44
|
+
this.gridReceivesLiveUpdates = (_a = options.gridReceivesLiveUpdates) !== null && _a !== void 0 ? _a : false;
|
|
27
45
|
}
|
|
28
46
|
refreshDatasource(params) {
|
|
29
47
|
const _super = Object.create(null, {
|
|
@@ -36,6 +54,8 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
36
54
|
this.dataserverStreamSubscription = undefined;
|
|
37
55
|
this.dataserverStream = undefined;
|
|
38
56
|
this.deliveredRowCount = 0;
|
|
57
|
+
this.retainedDeletedRowIds.clear();
|
|
58
|
+
this.cacheOutOfStep = false;
|
|
39
59
|
yield _super.refreshDatasource.call(this, params);
|
|
40
60
|
});
|
|
41
61
|
}
|
|
@@ -69,6 +89,16 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
69
89
|
// when it already covers this block, or the stream ended, no further stream messages
|
|
70
90
|
// will arrive for this request - resolve it synchronously from the cache.
|
|
71
91
|
const blockAlreadyCovered = this.currentSequenceId >= 1 && (this.rowData.size >= blockEndRow || !this.moreRows);
|
|
92
|
+
// A re-read is the moment the grid asks for current rows. When the cache holds a change the
|
|
93
|
+
// grid never saw (a DELETE it was not told about), serving the slice would hand back the
|
|
94
|
+
// deleted row, so refresh instead: the grid drops every block and re-reads from the server.
|
|
95
|
+
// fail() releases the request slot, as for a superseded filter or sort change above.
|
|
96
|
+
if (blockAlreadyCovered && this.cacheOutOfStep) {
|
|
97
|
+
logger.debug(`SSRM block ${blockStartRow}-${blockEndRow} for ${this.resourceName} re-read while the cache is out of step with the grid; refreshing`);
|
|
98
|
+
yield this.refreshDatasource(params);
|
|
99
|
+
params.fail();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
72
102
|
if (this.currentSequenceId >= 1 &&
|
|
73
103
|
!blockAlreadyCovered &&
|
|
74
104
|
(this.moreRows || params.request.startRow >= Number(this.maxRows))) {
|
|
@@ -109,6 +139,7 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
109
139
|
successRowData.rowCount = this.getCorrectRowCount(params);
|
|
110
140
|
this.lastSuccessRowData = successRowData;
|
|
111
141
|
params.success(successRowData);
|
|
142
|
+
this.deliverRows(blockStartRow, successRowData.rowData.filter((row) => !this.retainedDeletedRowIds.has(row === null || row === void 0 ? void 0 : row[this.rowId])));
|
|
112
143
|
this.notifyNoDataAvailableIfEmpty(params, successRowData);
|
|
113
144
|
};
|
|
114
145
|
const failBlock = (timer) => {
|
|
@@ -290,6 +321,8 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
290
321
|
(_a = this.dataserverStreamSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
291
322
|
this.dataserverStreamSubscription = undefined;
|
|
292
323
|
this.deliveredRowCount = 0;
|
|
324
|
+
this.retainedDeletedRowIds.clear();
|
|
325
|
+
this.cacheOutOfStep = false;
|
|
293
326
|
yield _super.destroy.call(this);
|
|
294
327
|
});
|
|
295
328
|
}
|
|
@@ -299,13 +332,31 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
299
332
|
return;
|
|
300
333
|
// Infinite scroll accumulates a session-cumulative cache, as GSF splits batches across
|
|
301
334
|
// messages and later batches append. Map preserves insertion order, so the cache can be
|
|
302
|
-
// sliced by block range
|
|
303
|
-
|
|
335
|
+
// sliced by block range - which only works while the cache and the grid agree on where
|
|
336
|
+
// every delivered row sits, so pushed changes below the delivered watermark are mirrored the
|
|
337
|
+
// way the grid applies them, or deferred to a refresh when the grid is not told.
|
|
338
|
+
let rows = new Map(this.rowData);
|
|
339
|
+
// Rows arriving while a block is pending are the page that block asked for and append in
|
|
340
|
+
// view order. With no block waiting they are a push; the host inserts those at the top of
|
|
341
|
+
// a live grid (its transaction uses addIndex 0), so the cache puts them there too. (A push
|
|
342
|
+
// that lands during a page load is indistinguishable from the page and appends.)
|
|
343
|
+
const pushed = this.pendingBlocks.size === 0 && this.deliveredRowCount > 0;
|
|
344
|
+
const prepended = new Map();
|
|
304
345
|
(_a = result.inserts) === null || _a === void 0 ? void 0 : _a.forEach((insertData) => {
|
|
305
|
-
|
|
346
|
+
const key = insertData[this.rowId];
|
|
347
|
+
this.retainedDeletedRowIds.delete(key);
|
|
348
|
+
if (pushed && this.gridReceivesLiveUpdates && !rows.has(key)) {
|
|
349
|
+
prepended.set(key, insertData);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
rows.set(key, insertData);
|
|
306
353
|
});
|
|
354
|
+
if (prepended.size > 0) {
|
|
355
|
+
rows = new Map([...prepended, ...rows]);
|
|
356
|
+
this.deliveredRowCount += prepended.size;
|
|
357
|
+
}
|
|
307
358
|
// Live QUERY_UPDATE messages can arrive while a block is pending: MODIFY rows may be
|
|
308
|
-
// partial, so merge into the cached row instead of clobbering it
|
|
359
|
+
// partial, so merge into the cached row instead of clobbering it.
|
|
309
360
|
(_b = result.updates) === null || _b === void 0 ? void 0 : _b.forEach((updateData) => {
|
|
310
361
|
const key = updateData === null || updateData === void 0 ? void 0 : updateData[this.rowId];
|
|
311
362
|
if (key === undefined) {
|
|
@@ -316,28 +367,37 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
|
|
|
316
367
|
});
|
|
317
368
|
(_c = result.deletes) === null || _c === void 0 ? void 0 : _c.forEach((deleteData) => {
|
|
318
369
|
const key = deleteData === null || deleteData === void 0 ? void 0 : deleteData[this.rowId];
|
|
319
|
-
if (key === undefined) {
|
|
370
|
+
if (key === undefined || !rows.has(key)) {
|
|
320
371
|
return;
|
|
321
372
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
break;
|
|
332
|
-
}
|
|
333
|
-
index += 1;
|
|
373
|
+
const index = this.cacheIndexOf(rows, key);
|
|
374
|
+
if (index < this.deliveredRowCount) {
|
|
375
|
+
if (!this.gridReceivesLiveUpdates) {
|
|
376
|
+
// The grid still shows the row at this index. Dropping it would shift every later
|
|
377
|
+
// block's slice against the grid, so keep its slot and let the next re-read refresh.
|
|
378
|
+
this.retainedDeletedRowIds.add(key);
|
|
379
|
+
this.cacheOutOfStep = true;
|
|
380
|
+
return;
|
|
334
381
|
}
|
|
382
|
+
// The host's remove transaction re-indexes the grid past this row; follow it.
|
|
383
|
+
this.deliveredRowCount -= 1;
|
|
335
384
|
}
|
|
336
385
|
rows.delete(key);
|
|
337
386
|
});
|
|
338
387
|
this.clientRowsCount += (_e = (_d = result.inserts) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0;
|
|
339
388
|
this.rowData = rows;
|
|
340
389
|
}
|
|
390
|
+
/** Position of a key in the cache's insertion order. @internal */
|
|
391
|
+
cacheIndexOf(rows, key) {
|
|
392
|
+
let index = 0;
|
|
393
|
+
for (const mapKey of rows.keys()) {
|
|
394
|
+
if (mapKey === key) {
|
|
395
|
+
return index;
|
|
396
|
+
}
|
|
397
|
+
index += 1;
|
|
398
|
+
}
|
|
399
|
+
return -1;
|
|
400
|
+
}
|
|
341
401
|
}
|
|
342
402
|
/**
|
|
343
403
|
* Max time a non-paginated SSRM block may stay pending before the safety valve in
|