@genesislcap/grid-pro 14.482.1-alpha-c9fc44ab6.0 → 14.482.1

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.
@@ -10,19 +10,6 @@ import { BaseServerSideDatasource } from './server-side.resource-base';
10
10
  export class DataserverServerSideDatasource extends BaseServerSideDatasource {
11
11
  constructor(options) {
12
12
  super(options);
13
- /**
14
- * Blocks awaiting stream messages. Settled blocks remove themselves; destroy() cancels
15
- * (fails) the rest so AG Grid's concurrent-request slots are always released.
16
- * @internal
17
- */
18
- this.pendingBlocks = new Set();
19
- /**
20
- * Highest absolute row index already handed to AG Grid this session (non-pagination).
21
- * Live DELETEs below this watermark must not remove cache entries - see
22
- * handleCurrentStreamLoad.
23
- * @internal
24
- */
25
- this.deliveredRowCount = 0;
26
13
  this.createDataserverStreamFunc = options.createDataserverStreamFunc;
27
14
  }
28
15
  refreshDatasource(params) {
@@ -31,32 +18,18 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
31
18
  });
32
19
  return __awaiter(this, void 0, void 0, function* () {
33
20
  var _a;
34
- this.cancelAllPendingBlocks();
35
21
  (_a = this.dataserverStreamSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
36
22
  this.dataserverStreamSubscription = undefined;
37
23
  this.dataserverStream = undefined;
38
- this.deliveredRowCount = 0;
39
24
  yield _super.refreshDatasource.call(this, params);
40
25
  });
41
26
  }
42
27
  getRows(params) {
43
28
  return __awaiter(this, void 0, void 0, function* () {
44
29
  var _a, _b;
45
- // setupFiltering()/setupSorting() return true when they triggered a refresh: the
46
- // datasource was re-initialized and AG Grid's SSRM cache purged, so getRows() is
47
- // re-invoked with fresh params and this invocation is superseded. It must still resolve
48
- // via params.fail() - AG Grid caps concurrent requests and only frees a slot on
49
- // success()/fail(); a silent return leaks the slot until the grid stops issuing
50
- // getRows() entirely. fail() is safe here: the purge already destroyed the cache this
51
- // request belonged to.
52
- if (yield this.setupFiltering(params)) {
53
- params.fail();
54
- return;
55
- }
56
- if (yield this.setupSorting(params)) {
57
- params.fail();
58
- return;
59
- }
30
+ // Use separated filtering and sorting setup
31
+ yield this.setupFiltering(params);
32
+ yield this.setupSorting(params);
60
33
  if (this.pagination && !this.isNewPageSize && this.currentSequenceId > 0) {
61
34
  const requestFilter = JSON.stringify((_a = params.request.filterModel) !== null && _a !== void 0 ? _a : {});
62
35
  const currentFilter = JSON.stringify((_b = this.currentFilterModel) !== null && _b !== void 0 ? _b : {});
@@ -68,20 +41,7 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
68
41
  if (!this.dataserverStream) {
69
42
  this.dataserverStream = yield this.createDataserverStreamFunc(this.resourceParams);
70
43
  }
71
- const blockStartRow = Number.isFinite(Number(params.request.startRow))
72
- ? Number(params.request.startRow)
73
- : 0;
74
- const blockEndRow = Number.isFinite(Number(params.request.endRow))
75
- ? Number(params.request.endRow)
76
- : blockStartRow + Number(this.maxRows);
77
- // Infinite scroll keeps a session-cumulative row cache (see handleCurrentStreamLoad);
78
- // when it already covers this block, or the stream ended, no further stream messages
79
- // will arrive for this request - resolve it synchronously from the cache.
80
- const blockAlreadyCovered = !this.pagination &&
81
- this.currentSequenceId >= 1 &&
82
- (this.rowData.size >= blockEndRow || !this.moreRows);
83
44
  if (this.currentSequenceId >= 1 &&
84
- !blockAlreadyCovered &&
85
45
  (this.moreRows || params.request.startRow >= Number(this.maxRows))) {
86
46
  if (this.pagination) {
87
47
  const startRow = Number.isFinite(Number(params.request.startRow))
@@ -98,91 +58,16 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
98
58
  this.connect.getMoreRows(this.sourceRef, viewNumber);
99
59
  }
100
60
  }
101
- // One shared stream subscription for the session; each getRows() registers a pending
102
- // block instead of subscribing again (.unsubscribe() would send DATA_LOGOFF and kill
103
- // the whole server-side session).
104
61
  let applyResult = true;
105
- const clearSafetyTimer = (timer) => {
106
- if (timer !== undefined) {
107
- clearTimeout(timer);
108
- }
109
- };
110
- const pendingBlock = { handle: () => { }, cancel: () => { } };
111
- const settleBlock = () => {
112
- this.pendingBlocks.delete(pendingBlock);
113
- };
114
- const resolveBlock = (timer) => {
115
- if (!applyResult) {
116
- return;
117
- }
118
- applyResult = false;
119
- clearSafetyTimer(timer);
120
- settleBlock();
121
- if (!this.pagination) {
122
- this.deliveredRowCount = Math.max(this.deliveredRowCount, blockEndRow);
123
- }
124
- const allRows = Array.from(this.rowData.values());
125
- const successRowData = {
126
- // Non-pagination: deliver only this block's slice of the cache - AG Grid SSRM
127
- // expects exactly the rows for its startRow..endRow range.
128
- rowData: this.pagination ? allRows : allRows.slice(blockStartRow, blockEndRow),
129
- };
130
- successRowData.rowCount = this.getCorrectRowCount(params);
131
- this.lastSuccessRowData = successRowData;
132
- params.success(successRowData);
133
- this.notifyNoDataAvailableIfEmpty(params, successRowData);
134
- };
135
- const failBlock = (timer) => {
136
- if (!applyResult) {
137
- return;
138
- }
139
- applyResult = false;
140
- clearSafetyTimer(timer);
141
- settleBlock();
142
- params.fail();
143
- };
144
- if (blockAlreadyCovered) {
145
- resolveBlock();
146
- return;
147
- }
148
- // Safety valve: never leave a block pending forever if the stream goes quiet - resolve
149
- // short with the accumulated rows so AG Grid ends the dataset there.
150
- const safetyTimer = setTimeout(() => {
151
- if (applyResult) {
152
- logger.warn(`SSRM block ${blockStartRow}-${blockEndRow} for ${this.resourceName} timed out waiting for a full batch; resolving with ${this.rowData.size} accumulated row(s).`);
153
- resolveBlock(safetyTimer);
154
- }
155
- }, DataserverServerSideDatasource.BLOCK_RESOLVE_TIMEOUT_MS);
156
- pendingBlock.cancel = () => failBlock(safetyTimer);
157
- pendingBlock.handle = (dataserverResult) => {
62
+ this.dataserverStreamSubscription = this.dataserverStream.subscribe((dataserverResult) => {
158
63
  var _a;
159
- const messageType = dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.MESSAGE_TYPE;
160
- if (messageType &&
161
- (messageType === MessageType.LOGOFF_ACK || messageType === MessageType.MSG_NACK)) {
162
- if (this.errorHandlerFunc) {
163
- const errorMessage = messageType === MessageType.LOGOFF_ACK
164
- ? `Connection lost to ${this.resourceName}`
165
- : `Authentication failed for ${this.resourceName}`;
166
- this.errorHandlerFunc(errorMessage, 'connection');
167
- }
168
- failBlock(safetyTimer);
169
- return;
170
- }
171
- if (this.pagination) {
172
- const incomingRowCount = (_a = dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.ROW) === null || _a === void 0 ? void 0 : _a.length;
173
- const shouldApply = applyResult || (incomingRowCount > 0 && this.rowData.size === 0);
174
- if (!shouldApply) {
175
- return;
176
- }
177
- // Paginated mode keeps the original behaviour: each message resolves the request
178
- // (page swap).
64
+ const incomingRowCount = (_a = dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.ROW) === null || _a === void 0 ? void 0 : _a.length;
65
+ const shouldApply = applyResult || (incomingRowCount > 0 && this.rowData.size === 0);
66
+ if (shouldApply) {
179
67
  DOM.queueUpdate(() => __awaiter(this, void 0, void 0, function* () {
180
68
  var _a, _b;
181
- if (!applyResult && !(incomingRowCount > 0 && this.rowData.size === 0)) {
182
- return;
183
- }
184
69
  // TODO: this is a bit of hack, due GSF not returning a different ROWS_COUNT when there is a CRITERIA_MATCH
185
- if (this.resourceParams.CRITERIA_MATCH && !this.criteriaRowCountResolved) {
70
+ if (this.resourceParams.CRITERIA_MATCH && this.pagination) {
186
71
  const updatedInfo = yield this.connect.snapshot(this.resourceName, {
187
72
  MAX_ROWS: this.maxView,
188
73
  MAX_VIEW: this.maxView,
@@ -194,96 +79,18 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
194
79
  this.clientRowsCount = 0;
195
80
  }
196
81
  else {
197
- // ROWS_COUNT ignores CRITERIA_MATCH and ROW.length is capped at page size;
198
- // Math.max may over-report (self-corrects when the stream ends) but never
199
- // under-reports, which would make AG Grid stop requesting blocks.
200
- const rowsCount = (_a = updatedInfo.ROWS_COUNT) !== null && _a !== void 0 ? _a : 0;
201
- const rowLength = (_b = updatedInfo.ROW) === null || _b === void 0 ? void 0 : _b.length;
202
- this.serverRowsCount =
203
- rowLength !== undefined ? Math.max(rowsCount, rowLength) : rowsCount;
204
- this.criteriaRowCountResolved = true;
82
+ this.serverRowsCount = this.resourceParams.CRITERIA_MATCH
83
+ ? ((_b = (_a = updatedInfo.ROW) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : updatedInfo.ROWS_COUNT)
84
+ : updatedInfo.ROWS_COUNT;
205
85
  }
206
86
  }
207
- clearSafetyTimer(safetyTimer);
208
87
  this.applyServerSideData(params, dataserverResult);
209
88
  applyResult = false;
210
- settleBlock();
211
89
  }));
212
- return;
213
- }
214
- if (!applyResult) {
215
- return;
216
- }
217
- // Infinite scroll accumulation runs once in dispatchStreamMessage(); here we only
218
- // decide whether this block is ready to resolve.
219
- if (this.currentSequenceId === 0 && !(dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.ROW) && !this.moreRows) {
220
- // First response carries no rows - the criteria matches nothing; resolve as
221
- // empty instead of waiting for the timeout.
222
- applyResult = false;
223
- clearSafetyTimer(safetyTimer);
224
- settleBlock();
225
- this.completeEmptyResult(params);
226
- return;
227
- }
228
- if (this.rowData.size >= blockEndRow || !this.moreRows) {
229
- DOM.queueUpdate(() => resolveBlock(safetyTimer));
230
90
  }
231
- };
232
- this.pendingBlocks.add(pendingBlock);
233
- this.ensureStreamSubscription();
91
+ });
234
92
  });
235
93
  }
236
- /** @internal */
237
- cancelAllPendingBlocks() {
238
- for (const block of this.pendingBlocks) {
239
- block.cancel();
240
- }
241
- this.pendingBlocks.clear();
242
- }
243
- /**
244
- * Lazily attaches the single stream subscription for this datasource session.
245
- * @internal
246
- */
247
- ensureStreamSubscription() {
248
- if (this.dataserverStreamSubscription) {
249
- return;
250
- }
251
- this.dataserverStreamSubscription = this.dataserverStream.subscribe((dataserverResult) => this.dispatchStreamMessage(dataserverResult));
252
- }
253
- /**
254
- * Fan-out: accumulate shared infinite-scroll state once, then notify each pending block.
255
- * @internal
256
- */
257
- dispatchStreamMessage(dataserverResult) {
258
- var _a;
259
- const messageType = dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.MESSAGE_TYPE;
260
- if (messageType &&
261
- (messageType === MessageType.LOGOFF_ACK || messageType === MessageType.MSG_NACK)) {
262
- for (const block of this.pendingBlocks) {
263
- block.handle(dataserverResult);
264
- }
265
- return;
266
- }
267
- if (!this.pagination) {
268
- if ((dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.MORE_ROWS) !== undefined) {
269
- this.moreRows = dataserverResult.MORE_ROWS;
270
- }
271
- if (dataserverResult === null || dataserverResult === void 0 ? void 0 : dataserverResult.ROW) {
272
- const nextMessage = dataServerResultFilter(dataserverResult);
273
- this.handleCurrentStreamLoad(nextMessage);
274
- this.currentSequenceId = dataserverResult.SEQUENCE_ID;
275
- if (this.currentSequenceId === 1) {
276
- this.sourceRef = dataserverResult.SOURCE_REF;
277
- if (this.serverRowsCount === 0) {
278
- this.serverRowsCount = (_a = dataserverResult.ROWS_COUNT) !== null && _a !== void 0 ? _a : this.rowData.size;
279
- }
280
- }
281
- }
282
- }
283
- for (const block of this.pendingBlocks) {
284
- block.handle(dataserverResult);
285
- }
286
- }
287
94
  applyServerSideData(params, result) {
288
95
  var _a;
289
96
  const messageType = result.MESSAGE_TYPE;
@@ -327,9 +134,10 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
327
134
  if (this.serverRowsCount === 0) {
328
135
  this.serverRowsCount = (_a = result.ROWS_COUNT) !== null && _a !== void 0 ? _a : this.rowData.size;
329
136
  }
330
- // Deliberately not overriding serverRowsCount with ROW.length under CRITERIA_MATCH:
331
- // ROW.length is just the first batch's size and clobbered the corrected count from
332
- // the snapshot() workaround in getRows().
137
+ if (this.getResourceParam('CRITERIA_MATCH') && result.ROW) {
138
+ this.serverRowsCount = result.ROW.length;
139
+ this.clientRowsCount = result.ROW.length;
140
+ }
333
141
  }
334
142
  const successRowData = {
335
143
  rowData: Array.from(this.rowData.values()),
@@ -355,34 +163,31 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
355
163
  this.resourceParams.ORDER_BY = null;
356
164
  this.resourceParams.REVERSE = null;
357
165
  yield this.refreshDatasource(params);
358
- return true;
166
+ return;
359
167
  }
360
168
  else if (toBeAppliedSortModel.length > 0) {
361
169
  const coldIdBeingSorted = toBeAppliedSortModel[0].colId; // Not allowing multiple sorts by user
362
170
  const sortTypeBeingApplied = toBeAppliedSortModel[0].sort;
363
171
  const orderByAndToBeSortedColIds = this.getOrderByAndToBeSortedColIds(this.resourceIndexes, coldIdBeingSorted);
364
172
  if (!orderByAndToBeSortedColIds) {
173
+ this.calculatedRowsCount = 0;
365
174
  const availableIndexes = getAvailableIndexes(this.resourceIndexes);
366
175
  const availableIndexFields = getAvailableIndexFields(this.resourceIndexes);
367
- logger.warn(`The FIELD/column (${coldIdBeingSorted}) being sorted is not part of an INDEX, required for the [orderBy] operation - ignoring this sort and loading unsorted data. See https://learn.genesis.global/docs/database/data-types/index-entities/`);
176
+ logger.warn(`The FIELD/column (${coldIdBeingSorted}) being sorted is not part of an INDEX, required for the [orderBy] operation. See https://learn.genesis.global/docs/database/data-types/index-entities/`);
368
177
  logger.debug('Available indexes:', availableIndexes);
369
178
  logger.debug('Columns that can be sorted with the available indexes:', availableIndexFields);
370
- // Ignore the un-sortable sort instead of failing - failing killed the entire
371
- // first load when a restored workspace carried a sort on a non-indexed column.
372
- // Remember the model so identical requests don't re-warn, and carry on unsorted.
373
- this.currentSortModel = toBeAppliedSortModel;
374
- return false;
179
+ params.fail();
180
+ return;
375
181
  }
376
182
  else if (JSON.stringify(toBeAppliedSortModel) !== JSON.stringify(this.currentSortModel)) {
377
183
  this.currentSortModel = toBeAppliedSortModel;
378
184
  this.resourceParams.ORDER_BY = orderByAndToBeSortedColIds.orderBy;
379
185
  this.resourceParams.REVERSE = sortTypeBeingApplied === 'desc' ? true : false;
380
186
  yield this.refreshDatasource(params);
381
- return true;
187
+ return;
382
188
  }
383
189
  }
384
190
  }
385
- return false;
386
191
  });
387
192
  }
388
193
  destroy() {
@@ -391,63 +196,21 @@ export class DataserverServerSideDatasource extends BaseServerSideDatasource {
391
196
  });
392
197
  return __awaiter(this, void 0, void 0, function* () {
393
198
  var _a;
394
- this.cancelAllPendingBlocks();
395
199
  this.dataserverStream = undefined;
396
200
  (_a = this.dataserverStreamSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
397
201
  this.dataserverStreamSubscription = undefined;
398
- this.deliveredRowCount = 0;
399
202
  yield _super.destroy.call(this);
400
203
  });
401
204
  }
402
205
  handleCurrentStreamLoad(result) {
403
- var _a, _b, _c, _d, _e;
206
+ var _a, _b;
404
207
  if (!result)
405
208
  return;
406
- // Pagination replaces the buffer per message (page swap); infinite scroll accumulates a
407
- // session-cumulative cache, as GSF splits batches across messages and later batches
408
- // append. Map preserves insertion order, so the cache can be sliced by block range.
409
- const rows = this.pagination ? new Map() : new Map(this.rowData);
209
+ const rows = new Map();
410
210
  (_a = result.inserts) === null || _a === void 0 ? void 0 : _a.forEach((insertData) => {
411
211
  rows.set(insertData[this.rowId], insertData);
412
212
  });
413
- // Live QUERY_UPDATE messages can arrive while a block is pending: MODIFY rows may be
414
- // partial, so merge into the cached row instead of clobbering it; DELETE rows drop out.
415
- (_b = result.updates) === null || _b === void 0 ? void 0 : _b.forEach((updateData) => {
416
- const key = updateData === null || updateData === void 0 ? void 0 : updateData[this.rowId];
417
- if (key === undefined) {
418
- return;
419
- }
420
- const existing = rows.get(key);
421
- rows.set(key, existing ? Object.assign(Object.assign({}, existing), updateData) : updateData);
422
- });
423
- (_c = result.deletes) === null || _c === void 0 ? void 0 : _c.forEach((deleteData) => {
424
- const key = deleteData === null || deleteData === void 0 ? void 0 : deleteData[this.rowId];
425
- if (key === undefined) {
426
- return;
427
- }
428
- // Rows already handed to AG Grid sit at fixed absolute indices; removing them from
429
- // the cumulative cache would shift slice() offsets for later blocks.
430
- if (!this.pagination && this.deliveredRowCount > 0) {
431
- let index = 0;
432
- for (const mapKey of rows.keys()) {
433
- if (mapKey === key) {
434
- if (index < this.deliveredRowCount) {
435
- return;
436
- }
437
- break;
438
- }
439
- index += 1;
440
- }
441
- }
442
- rows.delete(key);
443
- });
444
- this.clientRowsCount += (_e = (_d = result.inserts) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0;
213
+ this.clientRowsCount += (_b = result.inserts.length) !== null && _b !== void 0 ? _b : 0;
445
214
  this.rowData = rows;
446
215
  }
447
216
  }
448
- /**
449
- * Max time a non-paginated SSRM block may stay pending before the safety valve in
450
- * getRows() resolves it with the rows accumulated so far.
451
- * @internal
452
- */
453
- DataserverServerSideDatasource.BLOCK_RESOLVE_TIMEOUT_MS = 10000;
@@ -255,7 +255,7 @@ export class ReqRepServerSideDatasource extends BaseServerSideDatasource {
255
255
  this.currentSortModel = null;
256
256
  this.resourceParams.DETAILS.ORDER_BY = undefined;
257
257
  yield this.refreshDatasource(params);
258
- return true;
258
+ return;
259
259
  }
260
260
  else if (toBeAppliedSortModel.length > 0) {
261
261
  const coldIdBeingSorted = toBeAppliedSortModel[0].colId; // Not allowing multiple sorts by user
@@ -266,11 +266,10 @@ export class ReqRepServerSideDatasource extends BaseServerSideDatasource {
266
266
  this.resourceParams.DETAILS.ORDER_BY =
267
267
  coldIdBeingSorted + (sortTypeBeingApplied === 'desc' ? ' DESC' : ' ASC'); // Use the column directly
268
268
  yield this.refreshDatasource(params);
269
- return true;
269
+ return;
270
270
  }
271
271
  }
272
272
  }
273
- return false;
274
273
  });
275
274
  }
276
275
  refreshDatasource(params) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/grid-pro",
3
3
  "description": "Genesis Foundation AG Grid",
4
- "version": "14.482.1-alpha-c9fc44ab6.0",
4
+ "version": "14.482.1",
5
5
  "sideEffects": false,
6
6
  "license": "SEE LICENSE IN license.txt",
7
7
  "main": "dist/esm/index.js",
@@ -40,20 +40,20 @@
40
40
  }
41
41
  },
42
42
  "devDependencies": {
43
- "@genesislcap/foundation-testing": "14.482.1-alpha-c9fc44ab6.0",
44
- "@genesislcap/genx": "14.482.1-alpha-c9fc44ab6.0",
45
- "@genesislcap/rollup-builder": "14.482.1-alpha-c9fc44ab6.0",
46
- "@genesislcap/ts-builder": "14.482.1-alpha-c9fc44ab6.0",
47
- "@genesislcap/uvu-playwright-builder": "14.482.1-alpha-c9fc44ab6.0",
48
- "@genesislcap/vite-builder": "14.482.1-alpha-c9fc44ab6.0",
49
- "@genesislcap/webpack-builder": "14.482.1-alpha-c9fc44ab6.0"
43
+ "@genesislcap/foundation-testing": "14.482.1",
44
+ "@genesislcap/genx": "14.482.1",
45
+ "@genesislcap/rollup-builder": "14.482.1",
46
+ "@genesislcap/ts-builder": "14.482.1",
47
+ "@genesislcap/uvu-playwright-builder": "14.482.1",
48
+ "@genesislcap/vite-builder": "14.482.1",
49
+ "@genesislcap/webpack-builder": "14.482.1"
50
50
  },
51
51
  "dependencies": {
52
- "@genesislcap/foundation-comms": "14.482.1-alpha-c9fc44ab6.0",
53
- "@genesislcap/foundation-criteria": "14.482.1-alpha-c9fc44ab6.0",
54
- "@genesislcap/foundation-logger": "14.482.1-alpha-c9fc44ab6.0",
55
- "@genesislcap/foundation-ui": "14.482.1-alpha-c9fc44ab6.0",
56
- "@genesislcap/foundation-utils": "14.482.1-alpha-c9fc44ab6.0",
52
+ "@genesislcap/foundation-comms": "14.482.1",
53
+ "@genesislcap/foundation-criteria": "14.482.1",
54
+ "@genesislcap/foundation-logger": "14.482.1",
55
+ "@genesislcap/foundation-ui": "14.482.1",
56
+ "@genesislcap/foundation-utils": "14.482.1",
57
57
  "@microsoft/fast-colors": "5.3.1",
58
58
  "@microsoft/fast-components": "2.30.6",
59
59
  "@microsoft/fast-element": "1.14.0",
@@ -91,5 +91,5 @@
91
91
  "require": "./dist/react.cjs"
92
92
  }
93
93
  },
94
- "gitHead": "f2102d255d1581e519792fb2a4f5ebecc38fbb39"
94
+ "gitHead": "43a902838587d527b6786bbb41a5c3784c638e70"
95
95
  }