@genesislcap/grid-pro 15.20.2 → 15.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-elements.json +2161 -163
- package/dist/dts/datasource/base.datasource.d.ts +17 -3
- package/dist/dts/datasource/base.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/base.types.d.ts +1 -1
- package/dist/dts/datasource/base.types.d.ts.map +1 -1
- package/dist/dts/datasource/datasource.types.d.ts +5 -2
- package/dist/dts/datasource/datasource.types.d.ts.map +1 -1
- package/dist/dts/datasource/filter.utils.d.ts +40 -0
- package/dist/dts/datasource/filter.utils.d.ts.map +1 -0
- package/dist/dts/datasource/index.d.ts +2 -0
- package/dist/dts/datasource/index.d.ts.map +1 -1
- package/dist/dts/datasource/infinite.datasource.d.ts +464 -0
- package/dist/dts/datasource/infinite.datasource.d.ts.map +1 -0
- package/dist/dts/datasource/infinite.resource.d.ts +231 -0
- package/dist/dts/datasource/infinite.resource.d.ts.map +1 -0
- package/dist/dts/datasource/server-side.datasource.d.ts +0 -2
- package/dist/dts/datasource/server-side.datasource.d.ts.map +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts +1 -1
- package/dist/dts/datasource/server-side.resource-base.d.ts.map +1 -1
- package/dist/dts/grid-pro-beta.d.ts +37 -2
- package/dist/dts/grid-pro-beta.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts +20 -1
- package/dist/dts/grid-pro-genesis-datasource/datasource-events.types.d.ts.map +1 -1
- package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts +0 -1
- package/dist/dts/grid-pro-genesis-datasource/grid-pro-genesis-datasource.d.ts.map +1 -1
- package/dist/dts/grid-pro.d.ts +6 -0
- package/dist/dts/grid-pro.d.ts.map +1 -1
- package/dist/dts/react.d.ts +25 -1
- package/dist/dts/utils/map.d.ts +2 -2
- package/dist/dts/utils/map.d.ts.map +1 -1
- package/dist/esm/datasource/base.datasource.js +39 -2
- package/dist/esm/datasource/filter.utils.js +241 -0
- package/dist/esm/datasource/index.js +2 -0
- package/dist/esm/datasource/infinite.datasource.js +373 -0
- package/dist/esm/datasource/infinite.resource.js +581 -0
- package/dist/esm/datasource/server-side.datasource.js +0 -38
- package/dist/esm/datasource/server-side.grid-definitions.js +1 -1
- package/dist/esm/datasource/server-side.resource-base.js +7 -105
- package/dist/esm/grid-pro-beta.js +73 -6
- package/dist/esm/grid-pro-genesis-datasource/datasource-events.types.js +3 -0
- package/dist/esm/grid-pro-genesis-datasource/grid-pro-genesis-datasource.js +0 -3
- package/dist/esm/grid-pro.js +12 -1
- package/dist/grid-pro.api.json +1355 -477
- package/dist/grid-pro.d.ts +962 -165
- package/dist/react.cjs +84 -0
- package/dist/react.mjs +83 -0
- package/package.json +13 -13
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { dataServerResultFilter, MessageType, } from '@genesislcap/foundation-comms';
|
|
3
|
+
import { logger } from '../utils';
|
|
4
|
+
import { convertFilterModelToCriteria } from './filter.utils';
|
|
5
|
+
/**
|
|
6
|
+
* Converts an AG Grid sort model to the Genesis ORDER_BY form the resource expects.
|
|
7
|
+
* @remarks The two transports order rows differently, matching what the SSRM datasources send:
|
|
8
|
+
* - REQUEST_SERVER takes the column directly with the direction inline (`NAME DESC`) and no
|
|
9
|
+
* REVERSE flag; the column does not have to belong to an index.
|
|
10
|
+
* - DATASERVER takes the name of an INDEX containing the column, plus REVERSE. A column outside
|
|
11
|
+
* every index cannot be ordered server-side, and neither can an unnamed index.
|
|
12
|
+
* @param sortModel - The AG Grid sort model
|
|
13
|
+
* @param resourceIndexes - Available indexes for sorting (DATASERVER)
|
|
14
|
+
* @param isRequestServer - Whether the resource is a REQUEST_SERVER
|
|
15
|
+
* @returns Object with orderBy and reverse properties
|
|
16
|
+
*/
|
|
17
|
+
export function convertSortModelToOrderBy(sortModel, resourceIndexes, isRequestServer = false) {
|
|
18
|
+
if (!sortModel || sortModel.length === 0) {
|
|
19
|
+
return { orderBy: null, reverse: false };
|
|
20
|
+
}
|
|
21
|
+
const sortColumn = sortModel[0]; // Only support single column sort for infinite model
|
|
22
|
+
const colId = sortColumn.colId;
|
|
23
|
+
const descending = sortColumn.sort === 'desc';
|
|
24
|
+
if (isRequestServer) {
|
|
25
|
+
return { orderBy: `${colId} ${descending ? 'DESC' : 'ASC'}`, reverse: false };
|
|
26
|
+
}
|
|
27
|
+
// Find the index that contains this column. An index with no NAME cannot be named in
|
|
28
|
+
// ORDER_BY, so it is no use for sorting even when it covers the column.
|
|
29
|
+
for (const [indexName, fields] of resourceIndexes.entries()) {
|
|
30
|
+
if (indexName && fields.includes(colId)) {
|
|
31
|
+
return {
|
|
32
|
+
orderBy: indexName,
|
|
33
|
+
reverse: descending,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Column not in any usable index - carry on unsorted rather than sending an ORDER_BY the
|
|
38
|
+
// server will reject, which would empty the grid.
|
|
39
|
+
logger.warn(`Column '${colId}' is not part of any named INDEX. Server-side sorting is not available for this column.`);
|
|
40
|
+
return { orderBy: null, reverse: false };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* AG Grid Infinite Row Model datasource implementation.
|
|
44
|
+
* This class implements the IDatasource interface required by AG Grid's Infinite Row Model.
|
|
45
|
+
*/
|
|
46
|
+
export class GenesisInfiniteDatasource {
|
|
47
|
+
constructor(options) {
|
|
48
|
+
/**
|
|
49
|
+
* Session-cumulative row cache, keyed by row id. A Map preserves insertion order, so the
|
|
50
|
+
* cache can be sliced by block range; Genesis splits batches across messages and later
|
|
51
|
+
* batches append.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
this.rowData = new Map();
|
|
55
|
+
/** Blocks waiting for the cache to reach their range. @internal */
|
|
56
|
+
this.pendingBlocks = new Set();
|
|
57
|
+
/** Highest absolute row index already handed to AG Grid. @internal */
|
|
58
|
+
this.deliveredRowCount = 0;
|
|
59
|
+
/** False until the first message arrives on the subscription. @internal */
|
|
60
|
+
this.streamStarted = false;
|
|
61
|
+
/** Whether a MORE_ROWS request is awaiting its batch. @internal */
|
|
62
|
+
this.moreRowsInFlight = false;
|
|
63
|
+
/** Whether the current subscription was opened with a CRITERIA_MATCH. @internal */
|
|
64
|
+
this.hasCriteria = false;
|
|
65
|
+
/** So the MAX_VIEW truncation warning is logged once per subscription. @internal */
|
|
66
|
+
this.viewTruncationWarned = false;
|
|
67
|
+
this.createSnapshotFunc = options.createSnapshotFunc;
|
|
68
|
+
this.createDataserverStreamFunc = options.createDataserverStreamFunc;
|
|
69
|
+
this.getMoreRowsFunc = options.getMoreRowsFunc;
|
|
70
|
+
this.onRowsInvalidatedFunc = options.onRowsInvalidatedFunc;
|
|
71
|
+
this.onRowsUpdatedFunc = options.onRowsUpdatedFunc;
|
|
72
|
+
this.errorHandlerFunc = options.errorHandlerFunc;
|
|
73
|
+
this.resourceName = options.resourceName;
|
|
74
|
+
this.resourceParams = Object.assign({}, options.resourceParams);
|
|
75
|
+
this.resourceIndexes = options.resourceIndexes;
|
|
76
|
+
this.resourceColDefs = options.resourceColDefs;
|
|
77
|
+
this.maxRows = options.maxRows;
|
|
78
|
+
this.rowId = options.rowId;
|
|
79
|
+
this.baseCriteria = options.baseCriteria || '';
|
|
80
|
+
this.isRequestServer = options.isRequestServer;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds request parameters for REQUEST_SERVER resources.
|
|
84
|
+
* Uses OFFSET-based pagination.
|
|
85
|
+
*/
|
|
86
|
+
buildRequestServerParams(startRow, criteria, sortConfig) {
|
|
87
|
+
const requestParams = Object.assign({}, this.resourceParams);
|
|
88
|
+
// The captured resourceParams object is reused across getRows calls, so its nested DETAILS
|
|
89
|
+
// must not be mutated: a CRITERIA_MATCH/ORDER_BY written into it would keep being sent after
|
|
90
|
+
// the user clears the filter or sort. Copy it, and remove stale keys rather than leaving them.
|
|
91
|
+
requestParams.DETAILS = Object.assign({}, requestParams.DETAILS);
|
|
92
|
+
requestParams.DETAILS.MAX_ROWS = this.maxRows;
|
|
93
|
+
requestParams.DETAILS.OFFSET = startRow;
|
|
94
|
+
if (criteria) {
|
|
95
|
+
requestParams.DETAILS.CRITERIA_MATCH = criteria;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
delete requestParams.DETAILS.CRITERIA_MATCH;
|
|
99
|
+
}
|
|
100
|
+
// ORDER_BY already carries the direction for REQUEST_SERVER, so no REVERSE is sent.
|
|
101
|
+
// Fall back to the row id when the user has not sorted: OFFSET paging is only correct over a
|
|
102
|
+
// stable order, and an unsorted req/rep returns rows in an unspecified order that can differ
|
|
103
|
+
// between requests, so consecutive blocks overlap or skip rows. The row id (RECORD_ID by
|
|
104
|
+
// default) is unique and always sortable, giving every page a deterministic window.
|
|
105
|
+
requestParams.DETAILS.ORDER_BY = sortConfig.orderBy || `${this.rowId} ASC`;
|
|
106
|
+
return requestParams;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Builds the DATA_LOGON parameters for the DATASERVER subscription.
|
|
110
|
+
* @remarks No VIEW_NUMBER is sent: any non-zero value switches the server into pagination
|
|
111
|
+
* mode, which is not safe to combine with CRITERIA_MATCH (see dataserverStream). MAX_VIEW is
|
|
112
|
+
* passed through from the resource params and still caps how many rows one view will hold.
|
|
113
|
+
*/
|
|
114
|
+
buildDataserverLogonParams(criteria, sortConfig) {
|
|
115
|
+
const params = Object.assign({}, this.resourceParams);
|
|
116
|
+
params.MAX_ROWS = this.maxRows;
|
|
117
|
+
delete params.VIEW_NUMBER;
|
|
118
|
+
if (criteria) {
|
|
119
|
+
params.CRITERIA_MATCH = criteria;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
delete params.CRITERIA_MATCH;
|
|
123
|
+
}
|
|
124
|
+
if (sortConfig.orderBy) {
|
|
125
|
+
params.ORDER_BY = sortConfig.orderBy;
|
|
126
|
+
params.REVERSE = sortConfig.reverse;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
delete params.ORDER_BY;
|
|
130
|
+
delete params.REVERSE;
|
|
131
|
+
}
|
|
132
|
+
return params;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Determines the last row index from the server response.
|
|
136
|
+
* @remarks ROWS_COUNT is only trusted when no criteria is in effect - the same rule the
|
|
137
|
+
* DATASERVER path applies to serverRowsCount - because a server-reported total may ignore
|
|
138
|
+
* CRITERIA_MATCH, which would give a filtered grid a scrollbar over rows that never arrive.
|
|
139
|
+
* Under a filter the count stays open-ended until MORE_ROWS reports the end.
|
|
140
|
+
*/
|
|
141
|
+
getLastRow(result, startRow, rowDataLength, hasCriteria) {
|
|
142
|
+
if ('MORE_ROWS' in result && result.MORE_ROWS === false) {
|
|
143
|
+
return startRow + rowDataLength;
|
|
144
|
+
}
|
|
145
|
+
if (!hasCriteria && 'ROWS_COUNT' in result && typeof result.ROWS_COUNT === 'number') {
|
|
146
|
+
return result.ROWS_COUNT;
|
|
147
|
+
}
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Called by AG Grid when more rows are needed.
|
|
152
|
+
* Implements the IDatasource.getRows method.
|
|
153
|
+
*/
|
|
154
|
+
getRows(params) {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
logger.debug('Infinite getRows called', {
|
|
157
|
+
startRow: params.startRow,
|
|
158
|
+
endRow: params.endRow,
|
|
159
|
+
isRequestServer: this.isRequestServer,
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
// Build filter criteria
|
|
163
|
+
const filterCriteria = convertFilterModelToCriteria(params.filterModel, this.resourceColDefs);
|
|
164
|
+
const combinedCriteria = [this.baseCriteria, filterCriteria].filter(Boolean).join(' && ');
|
|
165
|
+
// Build sort config
|
|
166
|
+
const sortConfig = convertSortModelToOrderBy(params.sortModel, this.resourceIndexes, this.isRequestServer);
|
|
167
|
+
// DATASERVER pages through a persistent subscription; REQUEST_SERVER is stateless and
|
|
168
|
+
// pages by OFFSET on a fresh request each time.
|
|
169
|
+
if (!this.isRequestServer) {
|
|
170
|
+
this.getDataserverRows(params, combinedCriteria, sortConfig);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const requestParams = this.buildRequestServerParams(params.startRow, combinedCriteria, sortConfig);
|
|
174
|
+
logger.debug('Infinite datasource request params', requestParams);
|
|
175
|
+
const result = yield this.createSnapshotFunc(requestParams);
|
|
176
|
+
if (!result) {
|
|
177
|
+
params.failCallback();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if ('REPLY' in result && Array.isArray(result.REPLY)) {
|
|
181
|
+
const rowData = result.REPLY;
|
|
182
|
+
const lastRow = this.getLastRow(result, params.startRow, rowData.length, Boolean(combinedCriteria));
|
|
183
|
+
params.successCallback(rowData, lastRow);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Fallback - empty result
|
|
187
|
+
params.successCallback([], params.startRow);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
logger.error('Error in infinite getRows:', error);
|
|
191
|
+
if (this.errorHandlerFunc) {
|
|
192
|
+
this.errorHandlerFunc(error instanceof Error ? error.message : String(error), 'unknown');
|
|
193
|
+
}
|
|
194
|
+
params.failCallback();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Loads one AG Grid block from a DATASERVER resource.
|
|
200
|
+
* @remarks Blocks are served out of the session-cumulative cache. When the cache does not yet
|
|
201
|
+
* reach the block, MORE_ROWS pulls the next batch and the block waits; several blocks can be
|
|
202
|
+
* outstanding at once, and each settles as soon as the cache covers its range.
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
getDataserverRows(params, criteria, sortConfig) {
|
|
206
|
+
if (!this.createDataserverStreamFunc || !this.getMoreRowsFunc) {
|
|
207
|
+
logger.error('Infinite DATASERVER paging requires a stream and a getMoreRows function');
|
|
208
|
+
params.failCallback();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
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
|
+
const startRow = Number.isFinite(Number(params.startRow)) ? Number(params.startRow) : 0;
|
|
220
|
+
const endRow = Number.isFinite(Number(params.endRow))
|
|
221
|
+
? Number(params.endRow)
|
|
222
|
+
: startRow + this.maxRows;
|
|
223
|
+
const block = {
|
|
224
|
+
startRow,
|
|
225
|
+
endRow,
|
|
226
|
+
settled: false,
|
|
227
|
+
success: params.successCallback,
|
|
228
|
+
fail: params.failCallback,
|
|
229
|
+
};
|
|
230
|
+
// The cache may already cover this block - one batch can span several blocks, and blocks are
|
|
231
|
+
// re-requested after a purge - and once the stream has ended nothing more will arrive.
|
|
232
|
+
if (this.streamStarted && (this.rowData.size >= endRow || this.moreRows === false)) {
|
|
233
|
+
this.settleBlock(block, true);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Safety valve: never leave a block pending forever if the stream goes quiet.
|
|
237
|
+
block.timer = this.createBlockTimer(block);
|
|
238
|
+
this.pendingBlocks.add(block);
|
|
239
|
+
if (!this.dataserverStream) {
|
|
240
|
+
const logonParams = this.buildDataserverLogonParams(criteria, sortConfig);
|
|
241
|
+
this.hasCriteria = Boolean(logonParams.CRITERIA_MATCH);
|
|
242
|
+
this.dataserverStream = this.createDataserverStreamFunc(logonParams);
|
|
243
|
+
this.dataserverStreamSubscription = this.dataserverStream.subscribe((message) => this.handleDataserverMessage(message));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.pumpMoreRows();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Keeps pulling batches while blocks are still waiting.
|
|
250
|
+
* @remarks Sequential paging cannot seek, so reaching a block the cache does not cover means
|
|
251
|
+
* walking to it one batch at a time. One request is kept in flight; each arriving batch drives
|
|
252
|
+
* the next, until the blocks are covered or the stream ends.
|
|
253
|
+
* @internal
|
|
254
|
+
*/
|
|
255
|
+
pumpMoreRows() {
|
|
256
|
+
if (this.moreRowsInFlight || this.moreRows === false || this.pendingBlocks.size === 0) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// No subscription, or none that has identified itself yet: a MORE_ROWS against a stale
|
|
260
|
+
// SOURCE_REF is answered with MSG_NACK 404 "Dataserver subscription does not exist", which
|
|
261
|
+
// then fails every waiting block.
|
|
262
|
+
if (!this.dataserverStream || !this.dataserverStreamSubscription || !this.sourceRef) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
this.moreRowsInFlight = true;
|
|
266
|
+
void this.requestMoreRows();
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Asks the server for the next batch of rows on the current subscription.
|
|
270
|
+
* @internal
|
|
271
|
+
*/
|
|
272
|
+
requestMoreRows() {
|
|
273
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
274
|
+
// The subscription can be torn down between scheduling and running this.
|
|
275
|
+
if (!this.dataserverStreamSubscription || !this.sourceRef) {
|
|
276
|
+
this.moreRowsInFlight = false;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
yield this.getMoreRowsFunc(this.sourceRef);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
this.moreRowsInFlight = false;
|
|
284
|
+
// MORE_ROWS only exists as an interaction on a live socket subscription; over HTTP
|
|
285
|
+
// (feature.forceHttp) the router has no such endpoint and answers 404. Treat the rows
|
|
286
|
+
// already delivered as the whole view - waiting blocks settle with what arrived and the
|
|
287
|
+
// grid stops asking - rather than failing every block and erroring again on each scroll.
|
|
288
|
+
if ((error === null || error === void 0 ? void 0 : error.status) === GenesisInfiniteDatasource.HTTP_NOT_FOUND) {
|
|
289
|
+
logger.warn(`Infinite DATASERVER paging is not available on this connection (MORE_ROWS answered 404, expected over HTTP/feature.forceHttp). ${this.resourceName} is truncated to the ${this.rowData.size} row(s) already delivered; use a websocket connection, or a REQUEST_SERVER resource, to page further.`);
|
|
290
|
+
this.moreRows = false;
|
|
291
|
+
// The MAX_VIEW warning would misdiagnose this truncation as a view cap.
|
|
292
|
+
this.viewTruncationWarned = true;
|
|
293
|
+
this.settleCoveredBlocks();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
logger.error('Infinite DATASERVER MORE_ROWS failed:', error);
|
|
297
|
+
this.failAllPendingBlocks();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Accumulates a stream batch into the cumulative cache and settles whatever it covers.
|
|
303
|
+
* @internal
|
|
304
|
+
*/
|
|
305
|
+
handleDataserverMessage(message) {
|
|
306
|
+
var _a, _b, _c, _d, _e, _f;
|
|
307
|
+
const messageType = message === null || message === void 0 ? void 0 : message.MESSAGE_TYPE;
|
|
308
|
+
if (messageType === MessageType.LOGOFF_ACK || messageType === MessageType.MSG_NACK) {
|
|
309
|
+
// Report it (same messages as the SSRM dataserver resource) - without this a logged-off
|
|
310
|
+
// session just shows an empty grid with no banner.
|
|
311
|
+
(_a = this.errorHandlerFunc) === null || _a === void 0 ? void 0 : _a.call(this, messageType === MessageType.LOGOFF_ACK
|
|
312
|
+
? `Connection lost to ${this.resourceName}`
|
|
313
|
+
: `Authentication failed for ${this.resourceName}`, 'connection');
|
|
314
|
+
// The subscription is dead on the server, so drop it - keeping it would make every later
|
|
315
|
+
// scroll send MORE_ROWS against the stale SOURCE_REF, NACK again and fail its blocks
|
|
316
|
+
// silently. The teardown fails the waiting blocks and lets the next block re-log on.
|
|
317
|
+
this.teardownDataserverSubscription();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
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;
|
|
322
|
+
// A DATASERVER subscription is live by definition: batches answering the logon or a
|
|
323
|
+
// MORE_ROWS carry the rows we asked for, and anything else is a change pushed by the server.
|
|
324
|
+
// Replies only ever deliver view rows (INSERT operations), so a batch carrying a MODIFY or
|
|
325
|
+
// DELETE is a push no matter when it arrives - even while a request is in flight. Treating
|
|
326
|
+
// one as the reply would merge it into the cache at the wrong position and clear
|
|
327
|
+
// moreRowsInFlight while the real reply is still outstanding, letting a second MORE_ROWS
|
|
328
|
+
// race the first. (A pushed batch of only new INSERTs arriving while rows are due is
|
|
329
|
+
// indistinguishable from the reply; the reset issued for the next structural push
|
|
330
|
+
// reconciles that residual case.)
|
|
331
|
+
const containsPushedChanges = ((_d = (_c = filtered === null || filtered === void 0 ? void 0 : filtered.updates) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 || ((_f = (_e = filtered === null || filtered === void 0 ? void 0 : filtered.deletes) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0;
|
|
332
|
+
if (containsPushedChanges) {
|
|
333
|
+
// What happens to the pending blocks depends on the push:
|
|
334
|
+
// - MODIFY-only: the cache is patched in place and the awaited reply is still due, so
|
|
335
|
+
// moreRowsInFlight stays up and the blocks are left to it.
|
|
336
|
+
// - structural (any DELETE): notifyLiveChange resets the subscription, which fails the
|
|
337
|
+
// pending blocks and clears moreRowsInFlight; the purge that follows re-requests them
|
|
338
|
+
// on a fresh logon, and the in-flight reply lands on the unsubscribed stream.
|
|
339
|
+
this.streamStarted = true;
|
|
340
|
+
this.notifyLiveChange(filtered);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const solicited = this.moreRowsInFlight || this.pendingBlocks.size > 0 || !this.streamStarted;
|
|
344
|
+
this.moreRowsInFlight = false;
|
|
345
|
+
if ((message === null || message === void 0 ? void 0 : message.MORE_ROWS) !== undefined) {
|
|
346
|
+
this.moreRows = message.MORE_ROWS;
|
|
347
|
+
}
|
|
348
|
+
if (filtered) {
|
|
349
|
+
if (message.ROWS_COUNT !== undefined && this.serverRowsCount === undefined) {
|
|
350
|
+
this.serverRowsCount = message.ROWS_COUNT;
|
|
351
|
+
}
|
|
352
|
+
if (solicited) {
|
|
353
|
+
this.accumulate(filtered);
|
|
354
|
+
// Rows arriving is progress: give the blocks still waiting a fresh deadline so a long
|
|
355
|
+
// walk to a distant block is not cut short by the safety valve.
|
|
356
|
+
this.refreshPendingDeadlines();
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
this.streamStarted = true;
|
|
360
|
+
this.notifyLiveChange(filtered);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this.streamStarted = true;
|
|
365
|
+
this.settleCoveredBlocks();
|
|
366
|
+
this.pumpMoreRows();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Reports a server-pushed change to the host so the grid can catch up.
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
372
|
+
notifyLiveChange(result) {
|
|
373
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
374
|
+
const structural = ((_b = (_a = result.inserts) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 || ((_d = (_c = result.deletes) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0;
|
|
375
|
+
const modified = ((_f = (_e = result.updates) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0;
|
|
376
|
+
if (structural) {
|
|
377
|
+
// No point patching the cache first: it is about to be dropped and re-read.
|
|
378
|
+
logger.debug('Infinite DATASERVER live change invalidates the cache', {
|
|
379
|
+
inserts: (_h = (_g = result.inserts) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0,
|
|
380
|
+
deletes: (_k = (_j = result.deletes) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0,
|
|
381
|
+
});
|
|
382
|
+
this.reset();
|
|
383
|
+
(_l = this.onRowsInvalidatedFunc) === null || _l === void 0 ? void 0 : _l.call(this);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (modified) {
|
|
387
|
+
this.accumulate(result);
|
|
388
|
+
logger.debug('Infinite DATASERVER live update patched into the cache', {
|
|
389
|
+
updates: (_o = (_m = result.updates) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : 0,
|
|
390
|
+
});
|
|
391
|
+
(_p = this.onRowsUpdatedFunc) === null || _p === void 0 ? void 0 : _p.call(this);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Restarts the safety valve for every block still waiting.
|
|
396
|
+
* @internal
|
|
397
|
+
*/
|
|
398
|
+
refreshPendingDeadlines() {
|
|
399
|
+
for (const block of this.pendingBlocks) {
|
|
400
|
+
clearTimeout(block.timer);
|
|
401
|
+
block.timer = this.createBlockTimer(block);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** @internal */
|
|
405
|
+
createBlockTimer(block) {
|
|
406
|
+
return setTimeout(() => {
|
|
407
|
+
logger.warn(`Infinite DATASERVER block ${block.startRow}-${block.endRow} for ${this.resourceName} timed out waiting for rows; resolving with ${this.rowData.size} accumulated row(s).`);
|
|
408
|
+
this.settleBlock(block, true);
|
|
409
|
+
}, GenesisInfiniteDatasource.BLOCK_RESOLVE_TIMEOUT_MS);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Merges a batch into the cumulative cache.
|
|
413
|
+
* @internal
|
|
414
|
+
*/
|
|
415
|
+
accumulate(result) {
|
|
416
|
+
var _a, _b, _c;
|
|
417
|
+
const rows = new Map(this.rowData);
|
|
418
|
+
(_a = result.inserts) === null || _a === void 0 ? void 0 : _a.forEach((row) => {
|
|
419
|
+
const key = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
420
|
+
if (key === undefined) {
|
|
421
|
+
// Without the guard a misconfigured rowId collapses every insert onto one undefined
|
|
422
|
+
// key, and blocks only ever resolve through the safety valve.
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
rows.set(key, row);
|
|
426
|
+
});
|
|
427
|
+
// Live MODIFY rows may be partial, so merge into the cached row instead of clobbering it.
|
|
428
|
+
(_b = result.updates) === null || _b === void 0 ? void 0 : _b.forEach((row) => {
|
|
429
|
+
const key = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
430
|
+
if (key === undefined) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const existing = rows.get(key);
|
|
434
|
+
rows.set(key, existing ? Object.assign(Object.assign({}, existing), row) : row);
|
|
435
|
+
});
|
|
436
|
+
(_c = result.deletes) === null || _c === void 0 ? void 0 : _c.forEach((row) => {
|
|
437
|
+
const key = row === null || row === void 0 ? void 0 : row[this.rowId];
|
|
438
|
+
if (key === undefined) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
// Rows already handed to AG Grid sit at fixed absolute indices; removing one from the
|
|
442
|
+
// cache would shift the slice offsets of every later block.
|
|
443
|
+
const index = this.cacheIndexOf(rows, key);
|
|
444
|
+
if (index === -1 || (this.deliveredRowCount > 0 && index < this.deliveredRowCount)) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
rows.delete(key);
|
|
448
|
+
});
|
|
449
|
+
this.rowData = rows;
|
|
450
|
+
}
|
|
451
|
+
/** Position of a key in the cache, or -1. @internal */
|
|
452
|
+
cacheIndexOf(rows, key) {
|
|
453
|
+
let index = 0;
|
|
454
|
+
for (const mapKey of rows.keys()) {
|
|
455
|
+
if (mapKey === key) {
|
|
456
|
+
return index;
|
|
457
|
+
}
|
|
458
|
+
index += 1;
|
|
459
|
+
}
|
|
460
|
+
return -1;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Settles every pending block the cache now covers.
|
|
464
|
+
* @internal
|
|
465
|
+
*/
|
|
466
|
+
settleCoveredBlocks() {
|
|
467
|
+
// Iterate a copy: settling a block removes it from the set.
|
|
468
|
+
for (const block of [...this.pendingBlocks]) {
|
|
469
|
+
if (this.rowData.size >= block.endRow || this.moreRows === false) {
|
|
470
|
+
this.settleBlock(block, true);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Hands a block its slice of the cache, or fails it.
|
|
476
|
+
* @internal
|
|
477
|
+
*/
|
|
478
|
+
settleBlock(block, success) {
|
|
479
|
+
var _a;
|
|
480
|
+
if (block.settled) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
block.settled = true;
|
|
484
|
+
this.pendingBlocks.delete(block);
|
|
485
|
+
clearTimeout(block.timer);
|
|
486
|
+
if (!success) {
|
|
487
|
+
block.fail();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const rows = [...this.rowData.values()].slice(block.startRow, block.endRow);
|
|
491
|
+
this.deliveredRowCount = Math.max(this.deliveredRowCount, block.startRow + rows.length);
|
|
492
|
+
// Once the stream reports no more rows the cache is the whole dataset, filter included.
|
|
493
|
+
// Before that, an unfiltered total gives the grid an accurate scrollbar, while a filtered
|
|
494
|
+
// view has to stay open-ended - see serverRowsCount.
|
|
495
|
+
const lastRow = this.moreRows === false
|
|
496
|
+
? this.rowData.size
|
|
497
|
+
: this.hasCriteria
|
|
498
|
+
? -1
|
|
499
|
+
: ((_a = this.serverRowsCount) !== null && _a !== void 0 ? _a : -1);
|
|
500
|
+
this.warnIfViewTruncated();
|
|
501
|
+
logger.debug('Infinite DATASERVER block settled', {
|
|
502
|
+
startRow: block.startRow,
|
|
503
|
+
endRow: block.endRow,
|
|
504
|
+
rows: rows.length,
|
|
505
|
+
cached: this.rowData.size,
|
|
506
|
+
lastRow,
|
|
507
|
+
});
|
|
508
|
+
block.success(rows, lastRow);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Warns when the view filled up before the data ran out.
|
|
512
|
+
* @remarks The server stops sending at MAX_VIEW rows and reports MORE_ROWS false, which is
|
|
513
|
+
* indistinguishable from the end of the data - the grid would silently show a truncated view
|
|
514
|
+
* and a row count smaller than the resource's. Raising `max-view` past the row count is the
|
|
515
|
+
* fix, so say so rather than leaving it to be discovered.
|
|
516
|
+
* @internal
|
|
517
|
+
*/
|
|
518
|
+
warnIfViewTruncated() {
|
|
519
|
+
var _a, _b;
|
|
520
|
+
if (this.viewTruncationWarned ||
|
|
521
|
+
this.moreRows !== false ||
|
|
522
|
+
this.hasCriteria ||
|
|
523
|
+
this.serverRowsCount === undefined ||
|
|
524
|
+
this.rowData.size >= this.serverRowsCount) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
this.viewTruncationWarned = true;
|
|
528
|
+
logger.warn(`${this.resourceName} stopped at ${this.rowData.size} of ${this.serverRowsCount} rows: the DATASERVER view is capped by MAX_VIEW (${(_b = (_a = this.resourceParams) === null || _a === void 0 ? void 0 : _a.MAX_VIEW) !== null && _b !== void 0 ? _b : 'server default'}). Raise max-view past the row count to reach the rest.`);
|
|
529
|
+
}
|
|
530
|
+
/** @internal */
|
|
531
|
+
failAllPendingBlocks() {
|
|
532
|
+
// Iterate a copy: settling a block removes it from the set.
|
|
533
|
+
for (const block of [...this.pendingBlocks]) {
|
|
534
|
+
this.settleBlock(block, false);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Drops the accumulated rows and the subscription, so the next block re-reads from the server.
|
|
539
|
+
* @remarks Needed after a write: the cumulative cache is what blocks are served from, so a
|
|
540
|
+
* created row would otherwise appear only at the end of the cache (the server pushes it as an
|
|
541
|
+
* INSERT, not in sort position) and a deleted row that has already been displayed would never
|
|
542
|
+
* disappear, because dropping it from the cache would shift later blocks' slice offsets.
|
|
543
|
+
* @public
|
|
544
|
+
*/
|
|
545
|
+
reset() {
|
|
546
|
+
this.teardownDataserverSubscription();
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Drops the subscription and its cache so the next block re-logs on with fresh parameters.
|
|
550
|
+
* @internal
|
|
551
|
+
*/
|
|
552
|
+
teardownDataserverSubscription() {
|
|
553
|
+
var _a;
|
|
554
|
+
this.failAllPendingBlocks();
|
|
555
|
+
(_a = this.dataserverStreamSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
|
|
556
|
+
this.dataserverStreamSubscription = undefined;
|
|
557
|
+
this.dataserverStream = undefined;
|
|
558
|
+
this.subscriptionKey = undefined;
|
|
559
|
+
this.sourceRef = undefined;
|
|
560
|
+
this.rowData = new Map();
|
|
561
|
+
this.deliveredRowCount = 0;
|
|
562
|
+
this.streamStarted = false;
|
|
563
|
+
this.serverRowsCount = undefined;
|
|
564
|
+
this.hasCriteria = false;
|
|
565
|
+
this.viewTruncationWarned = false;
|
|
566
|
+
this.moreRows = undefined;
|
|
567
|
+
this.moreRowsInFlight = false;
|
|
568
|
+
}
|
|
569
|
+
destroy() {
|
|
570
|
+
this.teardownDataserverSubscription();
|
|
571
|
+
logger.debug('GenesisInfiniteDatasource destroyed');
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Max time a DATASERVER block may stay pending before the safety valve resolves it with the
|
|
576
|
+
* rows accumulated so far.
|
|
577
|
+
* @internal
|
|
578
|
+
*/
|
|
579
|
+
GenesisInfiniteDatasource.BLOCK_RESOLVE_TIMEOUT_MS = 10000;
|
|
580
|
+
/** MORE_ROWS answered 404: the connection has no such endpoint (HTTP mode). @internal */
|
|
581
|
+
GenesisInfiniteDatasource.HTTP_NOT_FOUND = 404;
|
|
@@ -428,41 +428,6 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
428
428
|
this.clearRowData();
|
|
429
429
|
this.$emit(datasourceEventNames.setServerSideDatasource, { datasource: null });
|
|
430
430
|
}
|
|
431
|
-
getResourceIndexes(avaialbleIndexes, availableSortableFields) {
|
|
432
|
-
const resourceIndexesMap = new Map();
|
|
433
|
-
const allSortableFields = new Set();
|
|
434
|
-
// Add indexes from INDEXES metadata
|
|
435
|
-
avaialbleIndexes === null || avaialbleIndexes === void 0 ? void 0 : avaialbleIndexes.forEach((index) => {
|
|
436
|
-
const fields = index.FIELDS.split(' ');
|
|
437
|
-
resourceIndexesMap.set(index.NAME, fields);
|
|
438
|
-
fields.forEach((field) => allSortableFields.add(field));
|
|
439
|
-
});
|
|
440
|
-
// Add fields from SORTABLE_FIELDS metadata if available
|
|
441
|
-
if (availableSortableFields && availableSortableFields.length > 0) {
|
|
442
|
-
availableSortableFields.forEach((field) => allSortableFields.add(field));
|
|
443
|
-
}
|
|
444
|
-
// If we have SORTABLE_FIELDS but no indexes, create a synthetic index entry
|
|
445
|
-
// This ensures compatibility with existing code that expects a Map structure
|
|
446
|
-
if (resourceIndexesMap.size === 0 && allSortableFields.size > 0) {
|
|
447
|
-
resourceIndexesMap.set('SORTABLE_FIELDS', Array.from(allSortableFields));
|
|
448
|
-
}
|
|
449
|
-
else if (availableSortableFields &&
|
|
450
|
-
availableSortableFields.length > 0 &&
|
|
451
|
-
resourceIndexesMap.size > 0) {
|
|
452
|
-
// If we have both indexes and SORTABLE_FIELDS, merge them
|
|
453
|
-
// Add any additional fields from SORTABLE_FIELDS that aren't already in any index
|
|
454
|
-
const fieldsFromIndexes = new Set();
|
|
455
|
-
resourceIndexesMap.forEach((fields) => {
|
|
456
|
-
fields.forEach((field) => fieldsFromIndexes.add(field));
|
|
457
|
-
});
|
|
458
|
-
const additionalFields = availableSortableFields.filter((field) => !fieldsFromIndexes.has(field));
|
|
459
|
-
if (additionalFields.length > 0) {
|
|
460
|
-
// Add additional sortable fields as a separate entry
|
|
461
|
-
resourceIndexesMap.set('SORTABLE_FIELDS', additionalFields);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return resourceIndexesMap;
|
|
465
|
-
}
|
|
466
431
|
createReqRepRequest() {
|
|
467
432
|
return __awaiter(this, arguments, void 0, function* (existingParams = null) {
|
|
468
433
|
const reqRep = yield this.datasource.snapshot(existingParams);
|
|
@@ -694,9 +659,6 @@ let GridProServerSideDatasource = class GridProServerSideDatasource extends Life
|
|
|
694
659
|
return colDefsFromGenesisData;
|
|
695
660
|
});
|
|
696
661
|
}
|
|
697
|
-
applyTransaction(transaction) {
|
|
698
|
-
this.$emit(datasourceEventNames.applyServerSideTransaction, { transaction });
|
|
699
|
-
}
|
|
700
662
|
loadMore() {
|
|
701
663
|
throw new Error('loadMore() method is not supported for server-side datasource');
|
|
702
664
|
}
|