@hrforte/react-pivottable 2.0.0 → 2.1.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/README.md +97 -8
- package/dist/PivotDataWorker.js +106 -6
- package/dist/PivotDataWorker.js.map +1 -1
- package/dist/PivotTableUI.js +8 -0
- package/dist/PivotTableUI.js.map +1 -1
- package/dist/__tests__/PivotDataWorker-test.js +170 -0
- package/dist/__tests__/PivotDataWorker-test.js.map +1 -0
- package/dist/pivot-worker.js +30 -20
- package/dist/pivotCompute.js +1 -1
- package/dist/pivotCompute.js.map +1 -1
- package/dist/pivotWorker.js +30 -20
- package/dist/pivotWorker.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
4
|
+
|
|
5
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
|
+
|
|
7
|
+
/* eslint-disable no-magic-numbers */
|
|
8
|
+
/*
|
|
9
|
+
* Tests the main-thread worker client, including the debug logging that the
|
|
10
|
+
* `workerDebug` prop of PivotTableUI switches on.
|
|
11
|
+
*/
|
|
12
|
+
describe(' PivotDataWorker', function () {
|
|
13
|
+
var records = [{ name: 'Nick', gender: 'male' }, { name: 'Jane', gender: 'female' }];
|
|
14
|
+
var config = {
|
|
15
|
+
rows: ['name'],
|
|
16
|
+
cols: [],
|
|
17
|
+
aggregations: [{ key: 'count', aggregatorName: 'Count', vals: [] }]
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
var instances = void 0;
|
|
21
|
+
var logSpy = void 0;
|
|
22
|
+
|
|
23
|
+
var FakeWorker = function () {
|
|
24
|
+
function FakeWorker(url) {
|
|
25
|
+
_classCallCheck(this, FakeWorker);
|
|
26
|
+
|
|
27
|
+
this.url = url;
|
|
28
|
+
this.posted = [];
|
|
29
|
+
this.onmessage = null;
|
|
30
|
+
this.onerror = null;
|
|
31
|
+
instances.push(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_createClass(FakeWorker, [{
|
|
35
|
+
key: 'postMessage',
|
|
36
|
+
value: function postMessage(message) {
|
|
37
|
+
this.posted.push(message);
|
|
38
|
+
}
|
|
39
|
+
}, {
|
|
40
|
+
key: 'terminate',
|
|
41
|
+
value: function terminate() {
|
|
42
|
+
this.terminated = true;
|
|
43
|
+
}
|
|
44
|
+
}]);
|
|
45
|
+
|
|
46
|
+
return FakeWorker;
|
|
47
|
+
}();
|
|
48
|
+
|
|
49
|
+
// console.log('%c[pivottable worker]', 'style', ...) -> strip the format
|
|
50
|
+
// directive and its style argument to assert on the message itself.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
var loggedText = function loggedText() {
|
|
54
|
+
return logSpy.mock.calls.map(function (call) {
|
|
55
|
+
return call.filter(function (arg) {
|
|
56
|
+
return !String(arg).startsWith('%c') && !String(arg).startsWith('color:');
|
|
57
|
+
}).join(' ');
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
var loadClient = function loadClient() {
|
|
62
|
+
jest.resetModules();
|
|
63
|
+
return require('../PivotDataWorker');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
beforeEach(function () {
|
|
67
|
+
instances = [];
|
|
68
|
+
global.Worker = FakeWorker;
|
|
69
|
+
logSpy = jest.spyOn(console, 'log').mockImplementation(function () {});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
afterEach(function () {
|
|
73
|
+
logSpy.mockRestore();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('is silent by default', function () {
|
|
77
|
+
var client = loadClient();
|
|
78
|
+
var promise = client.computePivotInWorker(records, 'data-1', config, jest.fn());
|
|
79
|
+
expect(logSpy).not.toHaveBeenCalled();
|
|
80
|
+
// Resolve the request so nothing stays pending.
|
|
81
|
+
instances[0].onmessage({
|
|
82
|
+
data: { type: 'pivot:result', id: 1, result: { rowKeys: [], colKeys: [] } }
|
|
83
|
+
});
|
|
84
|
+
return promise;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('logs the whole worker pipeline when debug is enabled', function () {
|
|
88
|
+
var client = loadClient();
|
|
89
|
+
var progress = [];
|
|
90
|
+
client.setWorkerDebug(true);
|
|
91
|
+
|
|
92
|
+
var promise = client.computePivotInWorker(records, 'data-1', config, function (processed, total, phase) {
|
|
93
|
+
return progress.push([processed, total, phase]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(loggedText()).toEqual(expect.arrayContaining([expect.stringContaining('debug logging enabled'), expect.stringContaining('worker created: pivot-worker.js'), expect.stringContaining('#1 dispatching to worker'), expect.stringContaining('staging 2 records')]));
|
|
97
|
+
|
|
98
|
+
// Staging + compute messages are sent (staging only once per dataKey).
|
|
99
|
+
var types = instances[0].posted.map(function (message) {
|
|
100
|
+
return message.type;
|
|
101
|
+
});
|
|
102
|
+
expect(types).toEqual(['pivot:debug', 'pivot:setData', 'pivot:compute']);
|
|
103
|
+
var staged = instances[0].posted.find(function (m) {
|
|
104
|
+
return m.type === 'pivot:setData';
|
|
105
|
+
});
|
|
106
|
+
expect(staged.data).toBe(records);
|
|
107
|
+
expect(staged.dataKey).toBe('data-1');
|
|
108
|
+
|
|
109
|
+
var worker = instances[0];
|
|
110
|
+
worker.onmessage({
|
|
111
|
+
data: {
|
|
112
|
+
type: 'pivot:progress',
|
|
113
|
+
id: 1,
|
|
114
|
+
processed: 2,
|
|
115
|
+
total: 2,
|
|
116
|
+
phase: 'materialize'
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
expect(progress[0]).toEqual([2, 2, 'materialize']);
|
|
120
|
+
expect(loggedText()).toEqual(expect.arrayContaining([expect.stringContaining('#1 progress materialize: 2/2 (100%)')]));
|
|
121
|
+
|
|
122
|
+
// Debug logs forwarded from inside the worker thread.
|
|
123
|
+
worker.onmessage({ data: { type: 'pivot:log', message: 'computed in 5ms' } });
|
|
124
|
+
expect(loggedText()).toEqual(expect.arrayContaining([expect.stringContaining('(in worker) computed in 5ms')]));
|
|
125
|
+
|
|
126
|
+
worker.onmessage({
|
|
127
|
+
data: {
|
|
128
|
+
type: 'pivot:result',
|
|
129
|
+
id: 1,
|
|
130
|
+
result: {
|
|
131
|
+
totalRecords: 2,
|
|
132
|
+
rowKeys: [['Nick']],
|
|
133
|
+
colKeys: [],
|
|
134
|
+
aggregations: []
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return promise.then(function (result) {
|
|
139
|
+
expect(result.totalRecords).toBe(2);
|
|
140
|
+
expect(loggedText()).toEqual(expect.arrayContaining([expect.stringContaining('#1 result received in')]));
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('logs the fallback to a synchronous computation', function () {
|
|
145
|
+
var client = loadClient();
|
|
146
|
+
client.setWorkerDebug(true);
|
|
147
|
+
|
|
148
|
+
var promise = client.computePivotWithFallback(records, 'data-2', config, jest.fn());
|
|
149
|
+
|
|
150
|
+
// Simulate a worker that fails to load before producing any result.
|
|
151
|
+
instances[0].onerror(new Error('404'));
|
|
152
|
+
|
|
153
|
+
return promise.then(function (result) {
|
|
154
|
+
expect(result.totalRecords).toBe(2);
|
|
155
|
+
expect(loggedText()).toEqual(expect.arrayContaining([expect.stringContaining('worker error event'), expect.stringContaining('falling back to the main thread'), expect.stringContaining('running synchronous computation'), expect.stringContaining('synchronous computation finished in')]));
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('stops logging when debug is switched off again', function () {
|
|
160
|
+
var client = loadClient();
|
|
161
|
+
client.setWorkerDebug(true);
|
|
162
|
+
logSpy.mockClear();
|
|
163
|
+
client.setWorkerDebug(false);
|
|
164
|
+
expect(loggedText()).toEqual(expect.arrayContaining([expect.stringContaining('debug logging disabled')]));
|
|
165
|
+
logSpy.mockClear();
|
|
166
|
+
client.setWorkerDebug(false);
|
|
167
|
+
expect(logSpy).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=PivotDataWorker-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/PivotDataWorker-test.js"],"names":["describe","records","name","gender","config","rows","cols","aggregations","key","aggregatorName","vals","instances","logSpy","FakeWorker","url","posted","onmessage","onerror","push","message","terminated","loggedText","mock","calls","map","call","filter","String","arg","startsWith","join","loadClient","jest","resetModules","require","beforeEach","global","Worker","spyOn","console","mockImplementation","afterEach","mockRestore","it","client","promise","computePivotInWorker","fn","expect","not","toHaveBeenCalled","data","type","id","result","rowKeys","colKeys","progress","setWorkerDebug","processed","total","phase","toEqual","arrayContaining","stringContaining","types","staged","find","m","toBe","dataKey","worker","totalRecords","then","computePivotWithFallback","Error","mockClear"],"mappings":"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","file":"PivotDataWorker-test.js","sourcesContent":["/* eslint-disable no-magic-numbers */\n/*\n * Tests the main-thread worker client, including the debug logging that the\n * `workerDebug` prop of PivotTableUI switches on.\n */\ndescribe(' PivotDataWorker', function() {\n const records = [\n {name: 'Nick', gender: 'male'},\n {name: 'Jane', gender: 'female'},\n ];\n const config = {\n rows: ['name'],\n cols: [],\n aggregations: [{key: 'count', aggregatorName: 'Count', vals: []}],\n };\n\n let instances;\n let logSpy;\n\n class FakeWorker {\n constructor(url) {\n this.url = url;\n this.posted = [];\n this.onmessage = null;\n this.onerror = null;\n instances.push(this);\n }\n\n postMessage(message) {\n this.posted.push(message);\n }\n\n terminate() {\n this.terminated = true;\n }\n }\n\n // console.log('%c[pivottable worker]', 'style', ...) -> strip the format\n // directive and its style argument to assert on the message itself.\n const loggedText = () =>\n logSpy.mock.calls.map(call =>\n call\n .filter(\n arg =>\n !String(arg).startsWith('%c') && !String(arg).startsWith('color:')\n )\n .join(' ')\n );\n\n const loadClient = () => {\n jest.resetModules();\n return require('../PivotDataWorker');\n };\n\n beforeEach(function() {\n instances = [];\n global.Worker = FakeWorker;\n logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});\n });\n\n afterEach(function() {\n logSpy.mockRestore();\n });\n\n it('is silent by default', function() {\n const client = loadClient();\n const promise = client.computePivotInWorker(\n records,\n 'data-1',\n config,\n jest.fn()\n );\n expect(logSpy).not.toHaveBeenCalled();\n // Resolve the request so nothing stays pending.\n instances[0].onmessage({\n data: {type: 'pivot:result', id: 1, result: {rowKeys: [], colKeys: []}},\n });\n return promise;\n });\n\n it('logs the whole worker pipeline when debug is enabled', function() {\n const client = loadClient();\n const progress = [];\n client.setWorkerDebug(true);\n\n const promise = client.computePivotInWorker(\n records,\n 'data-1',\n config,\n (processed, total, phase) => progress.push([processed, total, phase])\n );\n\n expect(loggedText()).toEqual(\n expect.arrayContaining([\n expect.stringContaining('debug logging enabled'),\n expect.stringContaining('worker created: pivot-worker.js'),\n expect.stringContaining('#1 dispatching to worker'),\n expect.stringContaining('staging 2 records'),\n ])\n );\n\n // Staging + compute messages are sent (staging only once per dataKey).\n const types = instances[0].posted.map(message => message.type);\n expect(types).toEqual(['pivot:debug', 'pivot:setData', 'pivot:compute']);\n const staged = instances[0].posted.find(m => m.type === 'pivot:setData');\n expect(staged.data).toBe(records);\n expect(staged.dataKey).toBe('data-1');\n\n const worker = instances[0];\n worker.onmessage({\n data: {\n type: 'pivot:progress',\n id: 1,\n processed: 2,\n total: 2,\n phase: 'materialize',\n },\n });\n expect(progress[0]).toEqual([2, 2, 'materialize']);\n expect(loggedText()).toEqual(\n expect.arrayContaining([\n expect.stringContaining('#1 progress materialize: 2/2 (100%)'),\n ])\n );\n\n // Debug logs forwarded from inside the worker thread.\n worker.onmessage({data: {type: 'pivot:log', message: 'computed in 5ms'}});\n expect(loggedText()).toEqual(\n expect.arrayContaining([\n expect.stringContaining('(in worker) computed in 5ms'),\n ])\n );\n\n worker.onmessage({\n data: {\n type: 'pivot:result',\n id: 1,\n result: {\n totalRecords: 2,\n rowKeys: [['Nick']],\n colKeys: [],\n aggregations: [],\n },\n },\n });\n return promise.then(result => {\n expect(result.totalRecords).toBe(2);\n expect(loggedText()).toEqual(\n expect.arrayContaining([\n expect.stringContaining('#1 result received in'),\n ])\n );\n });\n });\n\n it('logs the fallback to a synchronous computation', function() {\n const client = loadClient();\n client.setWorkerDebug(true);\n\n const promise = client.computePivotWithFallback(\n records,\n 'data-2',\n config,\n jest.fn()\n );\n\n // Simulate a worker that fails to load before producing any result.\n instances[0].onerror(new Error('404'));\n\n return promise.then(result => {\n expect(result.totalRecords).toBe(2);\n expect(loggedText()).toEqual(\n expect.arrayContaining([\n expect.stringContaining('worker error event'),\n expect.stringContaining('falling back to the main thread'),\n expect.stringContaining('running synchronous computation'),\n expect.stringContaining('synchronous computation finished in'),\n ])\n );\n });\n });\n\n it('stops logging when debug is switched off again', function() {\n const client = loadClient();\n client.setWorkerDebug(true);\n logSpy.mockClear();\n client.setWorkerDebug(false);\n expect(loggedText()).toEqual(\n expect.arrayContaining([\n expect.stringContaining('debug logging disabled'),\n ])\n );\n logSpy.mockClear();\n client.setWorkerDebug(false);\n expect(logSpy).not.toHaveBeenCalled();\n });\n});\n"]}
|
package/dist/pivot-worker.js
CHANGED
|
@@ -301,27 +301,14 @@ module.exports = Function.call.bind(Object.prototype.hasOwnProperty);
|
|
|
301
301
|
"use strict";
|
|
302
302
|
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
|
|
303
303
|
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__pivotCompute__ = __webpack_require__(5);
|
|
304
|
-
/* eslint-disable valid-jsdoc */
|
|
305
|
-
/*
|
|
306
|
-
* Web Worker entry point for off-main-thread pivot computation.
|
|
307
|
-
*
|
|
308
|
-
* Message protocol (plain, structured-cloneable objects):
|
|
309
|
-
* main -> worker {type: 'pivot:setData', id, dataKey, data}
|
|
310
|
-
* main -> worker {type: 'pivot:compute', id, dataKey, config}
|
|
311
|
-
* worker -> main {type: 'pivot:progress', id, processed, total, phase}
|
|
312
|
-
* worker -> main {type: 'pivot:result', id, result}
|
|
313
|
-
* worker -> main {type: 'pivot:error', id, message}
|
|
314
|
-
*
|
|
315
|
-
* The materialized dataset is cached per dataKey, so repeated computations on
|
|
316
|
-
* the same data (e.g. dragging fields around) do not pay the cost of
|
|
317
|
-
* re-scanning the raw records.
|
|
318
|
-
*/
|
|
319
304
|
|
|
320
305
|
|
|
321
306
|
var MAX_CACHED_DATASETS = 4;
|
|
322
307
|
|
|
323
308
|
var datasetCache = new Map();
|
|
324
309
|
|
|
310
|
+
var debugEnabled = false;
|
|
311
|
+
|
|
325
312
|
var cacheDataset = function cacheDataset(dataKey, dataset) {
|
|
326
313
|
datasetCache.delete(dataKey);
|
|
327
314
|
datasetCache.set(dataKey, dataset);
|
|
@@ -336,6 +323,12 @@ var postToMain = function postToMain(message) {
|
|
|
336
323
|
self.postMessage(message);
|
|
337
324
|
};
|
|
338
325
|
|
|
326
|
+
var log = function log(message, detail) {
|
|
327
|
+
if (debugEnabled) {
|
|
328
|
+
postToMain({ type: 'pivot:log', message: message, detail: detail });
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
339
332
|
var toErrorMessage = function toErrorMessage(error) {
|
|
340
333
|
return String(error && error.message || error);
|
|
341
334
|
};
|
|
@@ -343,9 +336,17 @@ var toErrorMessage = function toErrorMessage(error) {
|
|
|
343
336
|
self.addEventListener('message', function (event) {
|
|
344
337
|
var message = event.data || {};
|
|
345
338
|
|
|
339
|
+
if (message.type === 'pivot:debug') {
|
|
340
|
+
debugEnabled = Boolean(message.enabled);
|
|
341
|
+
log('debug logging ' + (debugEnabled ? 'enabled' : 'disabled') + ' in worker');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
346
345
|
if (message.type === 'pivot:setData') {
|
|
346
|
+
var startedAt = Date.now();
|
|
347
|
+
log('#' + message.id + ' setData received: ' + (message.data || []).length + ' records (dataKey=' + message.dataKey + ')');
|
|
347
348
|
try {
|
|
348
|
-
cacheDataset(message.dataKey, Object(__WEBPACK_IMPORTED_MODULE_0__pivotCompute__["b" /* materializeData */])(message.data, {}, function (processed, total) {
|
|
349
|
+
var dataset = cacheDataset(message.dataKey, Object(__WEBPACK_IMPORTED_MODULE_0__pivotCompute__["b" /* materializeData */])(message.data, {}, function (processed, total) {
|
|
349
350
|
postToMain({
|
|
350
351
|
type: 'pivot:progress',
|
|
351
352
|
id: message.id,
|
|
@@ -354,7 +355,9 @@ self.addEventListener('message', function (event) {
|
|
|
354
355
|
phase: 'materialize'
|
|
355
356
|
});
|
|
356
357
|
}));
|
|
358
|
+
log('#' + message.id + ' materialized ' + dataset.totalRecords + ' records in ' + (Date.now() - startedAt + 'ms (attributes: ' + Object.keys(dataset.attrValues).join(', ') + ')'));
|
|
357
359
|
} catch (error) {
|
|
360
|
+
log('#' + message.id + ' setData failed: ' + toErrorMessage(error));
|
|
358
361
|
postToMain({
|
|
359
362
|
type: 'pivot:error',
|
|
360
363
|
id: message.id,
|
|
@@ -365,12 +368,15 @@ self.addEventListener('message', function (event) {
|
|
|
365
368
|
}
|
|
366
369
|
|
|
367
370
|
if (message.type === 'pivot:compute') {
|
|
371
|
+
var _startedAt = Date.now();
|
|
372
|
+
var config = message.config || {};
|
|
368
373
|
try {
|
|
369
|
-
var
|
|
370
|
-
if (!
|
|
374
|
+
var _dataset = datasetCache.get(message.dataKey);
|
|
375
|
+
if (!_dataset) {
|
|
371
376
|
throw new Error('no dataset staged for key ' + message.dataKey);
|
|
372
377
|
}
|
|
373
|
-
|
|
378
|
+
log('#' + message.id + ' compute received (rows=[' + (config.rows || []).join(', ') + '] cols=[' + (config.cols || []).join(', ') + '])');
|
|
379
|
+
var result = Object(__WEBPACK_IMPORTED_MODULE_0__pivotCompute__["a" /* computePivotState */])(_dataset, message.config, function (processed, total, phase) {
|
|
374
380
|
postToMain({
|
|
375
381
|
type: 'pivot:progress',
|
|
376
382
|
id: message.id,
|
|
@@ -379,8 +385,12 @@ self.addEventListener('message', function (event) {
|
|
|
379
385
|
phase: phase
|
|
380
386
|
});
|
|
381
387
|
});
|
|
388
|
+
log('#' + message.id + ' computed in ' + (Date.now() - _startedAt) + 'ms (rowKeys: ' + result.rowKeys.length + ', colKeys: ' + result.colKeys.length + ', aggregations: ' + result.aggregations.map(function (agg) {
|
|
389
|
+
return agg.key;
|
|
390
|
+
}).join(', ') + ')');
|
|
382
391
|
postToMain({ type: 'pivot:result', id: message.id, result: result });
|
|
383
392
|
} catch (error) {
|
|
393
|
+
log('#' + message.id + ' compute failed: ' + toErrorMessage(error));
|
|
384
394
|
postToMain({
|
|
385
395
|
type: 'pivot:error',
|
|
386
396
|
id: message.id,
|
|
@@ -428,7 +438,7 @@ var PROGRESS_BATCH_SIZE = 20000;
|
|
|
428
438
|
|
|
429
439
|
// Datasets smaller than this are computed synchronously on the main thread;
|
|
430
440
|
// larger ones are handed off to the Web Worker.
|
|
431
|
-
var DEFAULT_WORKER_THRESHOLD =
|
|
441
|
+
var DEFAULT_WORKER_THRESHOLD = 200;
|
|
432
442
|
|
|
433
443
|
// Trailing debounce before a worker computation is dispatched, so rapid
|
|
434
444
|
// interactions do not queue one full computation per single change.
|
package/dist/pivotCompute.js
CHANGED
|
@@ -35,7 +35,7 @@ var PROGRESS_BATCH_SIZE = exports.PROGRESS_BATCH_SIZE = 20000;
|
|
|
35
35
|
|
|
36
36
|
// Datasets smaller than this are computed synchronously on the main thread;
|
|
37
37
|
// larger ones are handed off to the Web Worker.
|
|
38
|
-
var DEFAULT_WORKER_THRESHOLD = exports.DEFAULT_WORKER_THRESHOLD =
|
|
38
|
+
var DEFAULT_WORKER_THRESHOLD = exports.DEFAULT_WORKER_THRESHOLD = 200;
|
|
39
39
|
|
|
40
40
|
// Trailing debounce before a worker computation is dispatched, so rapid
|
|
41
41
|
// interactions do not queue one full computation per single change.
|
package/dist/pivotCompute.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pivotCompute.js"],"names":["materializeData","computePivotState","serializePivotData","sortersHaveFunctions","sanitizeSorters","createWorkerConfig","createComputeSignature","canComputeInWorker","PROGRESS_BATCH_SIZE","DEFAULT_WORKER_THRESHOLD","WORKER_DEBOUNCE_MS","data","derivedAttributes","onProgress","attrValues","materializedInput","total","Array","isArray","length","recordsProcessed","PivotData","forEachRecord","record","push","Object","keys","attr","null","value","totalRecords","materialized","config","pivotData","assign","onRecordProcessed","processed","getRowKeys","getColKeys","serializeCell","values","aggregations","forEach","instance","cell","getInstance","agg","key","tree","rowBranch","flatRowKey","serializedRow","flatColKey","serializeTotals","serialized","totals","flatKey","rowKeys","map","slice","colKeys","rowTotals","colTotals","allTotal","sorters","some","clean","sorter","props","rows","cols","aggregatorName","vals","valueFilter","rowOrder","colOrder","rowSorts","colSorts","JSON","stringify","options","enabled","Worker","threshold","aggregators","defaultProps"],"mappings":"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","file":"pivotCompute.js","sourcesContent":["/* eslint-disable valid-jsdoc */\n/*\n * Shared pivot-computation logic.\n *\n * This module contains the pure, DOM-free parts of the pivot computation so\n * that the exact same code paths can run:\n * - on the main thread (classic synchronous rendering path), and\n * - inside a Web Worker (src/pivotWorker.js) for large datasets.\n *\n * It must stay free of React and of any `window`/`document` references.\n */\nimport {PivotData} from './Utilities';\n\n// A progress callback fires every N processed records so the UI can render\n// progress without adding measurable overhead to the per-record loop.\nexport const PROGRESS_BATCH_SIZE = 20000;\n\n// Datasets smaller than this are computed synchronously on the main thread;\n// larger ones are handed off to the Web Worker.\nexport const DEFAULT_WORKER_THRESHOLD = 10000;\n\n// Trailing debounce before a worker computation is dispatched, so rapid\n// interactions do not queue one full computation per single change.\nexport const WORKER_DEBOUNCE_MS = 200;\n\n/*\n * Walks the raw input once and computes the attribute-value inventory plus a\n * \"materialized\" copy of the records (with derived attributes applied).\n * This is an exact extraction of the historical PivotTableUI behavior.\n *\n * onProgress receives (processed, total, phase); total is null when it is\n * unknown upfront (function-based data sources).\n */\nexport function materializeData(data, derivedAttributes, onProgress) {\n const attrValues = {};\n const materializedInput = [];\n const total = Array.isArray(data) ? data.length : null;\n let recordsProcessed = 0;\n PivotData.forEachRecord(data, derivedAttributes || {}, function(record) {\n materializedInput.push(record);\n for (const attr of Object.keys(record)) {\n if (!(attr in attrValues)) {\n attrValues[attr] = {};\n if (recordsProcessed > 0) {\n attrValues[attr].null = recordsProcessed;\n }\n }\n }\n for (const attr in attrValues) {\n const value = attr in record ? record[attr] : 'null';\n if (!(value in attrValues[attr])) {\n attrValues[attr][value] = 0;\n }\n attrValues[attr][value]++;\n }\n recordsProcessed++;\n if (onProgress && recordsProcessed % PROGRESS_BATCH_SIZE === 0) {\n onProgress(recordsProcessed, total, 'materialize');\n }\n });\n return {attrValues, materializedInput, totalRecords: recordsProcessed};\n}\n\n/*\n * Aggregates an already-materialized dataset into a PivotData instance and\n * serializes the result into a plain (structured-cloneable) object that can\n * cross the worker boundary and later hydrate PivotData on the main thread.\n */\nexport function computePivotState(materialized, config, onProgress) {\n const total = materialized.totalRecords;\n const pivotData = new PivotData(\n Object.assign({}, config, {\n data: materialized.materializedInput,\n derivedAttributes: {},\n onRecordProcessed: processed => {\n if (onProgress) {\n onProgress(processed, total, 'aggregate');\n }\n },\n })\n );\n // Force key sorting to happen while we are (possibly) inside the worker.\n pivotData.getRowKeys();\n pivotData.getColKeys();\n return serializePivotData(pivotData, materialized);\n}\n\n/*\n * Serializes a fully-built PivotData instance into plain data. Only the final\n * aggregated value of every cell is kept; `value()` and `format()` behavior\n * are reconstructed on the main thread by the PivotData hydration step.\n */\nexport function serializePivotData(pivotData, materialized) {\n const serializeCell = cell => {\n const values = {};\n pivotData.aggregations.forEach(agg => {\n const instance = cell.getInstance(agg.key);\n if (instance) {\n values[agg.key] = instance.value();\n }\n });\n return {values};\n };\n\n const tree = {};\n Object.keys(pivotData.tree).forEach(flatRowKey => {\n const rowBranch = pivotData.tree[flatRowKey];\n const serializedRow = {};\n Object.keys(rowBranch).forEach(flatColKey => {\n serializedRow[flatColKey] = serializeCell(rowBranch[flatColKey]);\n });\n tree[flatRowKey] = serializedRow;\n });\n\n const serializeTotals = totals => {\n const serialized = {};\n Object.keys(totals).forEach(flatKey => {\n serialized[flatKey] = serializeCell(totals[flatKey]);\n });\n return serialized;\n };\n\n return {\n rowKeys: pivotData.rowKeys.map(key => key.slice()),\n colKeys: pivotData.colKeys.map(key => key.slice()),\n tree,\n rowTotals: serializeTotals(pivotData.rowTotals),\n colTotals: serializeTotals(pivotData.colTotals),\n allTotal: serializeCell(pivotData.allTotal),\n aggregations: pivotData.aggregations.map(agg => Object.assign({}, agg)),\n attrValues: materialized.attrValues,\n materializedInput: materialized.materializedInput,\n totalRecords: materialized.totalRecords,\n };\n}\n\n/*\n * Sorters containing functions cannot be structured-cloned into a worker.\n * Returns true when the sorters prop itself, or any of its entries, is a\n * function. In that case PivotData re-sorts on the main thread after\n * hydration (distinct keys are few, so this stays cheap).\n */\nexport function sortersHaveFunctions(sorters) {\n if (typeof sorters === 'function') {\n return true;\n }\n if (!sorters || typeof sorters !== 'object') {\n return false;\n }\n return Object.keys(sorters).some(attr => typeof sorters[attr] === 'function');\n}\n\n/*\n * Returns a structured-cloneable subset of the sorters prop: data-type names\n * and sample-value arrays survive the worker boundary, functions do not.\n */\nexport function sanitizeSorters(sorters) {\n if (\n !sorters ||\n typeof sorters !== 'object' ||\n typeof sorters === 'function'\n ) {\n return {};\n }\n const clean = {};\n Object.keys(sorters).forEach(attr => {\n const sorter = sorters[attr];\n if (typeof sorter === 'string' || Array.isArray(sorter)) {\n clean[attr] = sorter;\n }\n });\n return clean;\n}\n\n/*\n * Extracts exactly the props PivotData needs to compute results, with\n * everything non-serializable stripped, so the object can be posted to the\n * worker and fed straight into PivotData.\n */\nexport function createWorkerConfig(props) {\n return {\n rows: props.rows,\n cols: props.cols,\n aggregatorName: props.aggregatorName,\n aggregations: props.aggregations,\n vals: props.vals,\n valueFilter: props.valueFilter,\n sorters: sanitizeSorters(props.sorters),\n rowOrder: props.rowOrder,\n colOrder: props.colOrder,\n rowSorts: props.rowSorts,\n colSorts: props.colSorts,\n };\n}\n\n/*\n * Stable string signature of everything that influences a worker computation.\n * Data identity is NOT part of it; callers append their own data version.\n */\nexport function createComputeSignature(props) {\n return JSON.stringify(createWorkerConfig(props));\n}\n\n/*\n * Decides whether the current props can be computed inside the Web Worker.\n * Anything that cannot be structured-cloned (function data sources, custom\n * derivedAttributes, custom aggregator maps) forces the classic path.\n * Function-based sorters are allowed: they are re-applied on the main thread.\n */\nexport function canComputeInWorker(props, options = {}) {\n if (options.enabled === false) {\n return false;\n }\n if (typeof Worker === 'undefined') {\n return false;\n }\n const threshold =\n typeof options.threshold === 'number'\n ? options.threshold\n : DEFAULT_WORKER_THRESHOLD;\n const data = props.data;\n if (!Array.isArray(data) || data.length < threshold) {\n return false;\n }\n if (\n props.derivedAttributes &&\n Object.keys(props.derivedAttributes).length > 0\n ) {\n return false;\n }\n if (props.aggregators !== PivotData.defaultProps.aggregators) {\n return false;\n }\n return true;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/pivotCompute.js"],"names":["materializeData","computePivotState","serializePivotData","sortersHaveFunctions","sanitizeSorters","createWorkerConfig","createComputeSignature","canComputeInWorker","PROGRESS_BATCH_SIZE","DEFAULT_WORKER_THRESHOLD","WORKER_DEBOUNCE_MS","data","derivedAttributes","onProgress","attrValues","materializedInput","total","Array","isArray","length","recordsProcessed","PivotData","forEachRecord","record","push","Object","keys","attr","null","value","totalRecords","materialized","config","pivotData","assign","onRecordProcessed","processed","getRowKeys","getColKeys","serializeCell","values","aggregations","forEach","instance","cell","getInstance","agg","key","tree","rowBranch","flatRowKey","serializedRow","flatColKey","serializeTotals","serialized","totals","flatKey","rowKeys","map","slice","colKeys","rowTotals","colTotals","allTotal","sorters","some","clean","sorter","props","rows","cols","aggregatorName","vals","valueFilter","rowOrder","colOrder","rowSorts","colSorts","JSON","stringify","options","enabled","Worker","threshold","aggregators","defaultProps"],"mappings":"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","file":"pivotCompute.js","sourcesContent":["/* eslint-disable valid-jsdoc */\n/*\n * Shared pivot-computation logic.\n *\n * This module contains the pure, DOM-free parts of the pivot computation so\n * that the exact same code paths can run:\n * - on the main thread (classic synchronous rendering path), and\n * - inside a Web Worker (src/pivotWorker.js) for large datasets.\n *\n * It must stay free of React and of any `window`/`document` references.\n */\nimport {PivotData} from './Utilities';\n\n// A progress callback fires every N processed records so the UI can render\n// progress without adding measurable overhead to the per-record loop.\nexport const PROGRESS_BATCH_SIZE = 20000;\n\n// Datasets smaller than this are computed synchronously on the main thread;\n// larger ones are handed off to the Web Worker.\nexport const DEFAULT_WORKER_THRESHOLD = 200;\n\n// Trailing debounce before a worker computation is dispatched, so rapid\n// interactions do not queue one full computation per single change.\nexport const WORKER_DEBOUNCE_MS = 200;\n\n/*\n * Walks the raw input once and computes the attribute-value inventory plus a\n * \"materialized\" copy of the records (with derived attributes applied).\n * This is an exact extraction of the historical PivotTableUI behavior.\n *\n * onProgress receives (processed, total, phase); total is null when it is\n * unknown upfront (function-based data sources).\n */\nexport function materializeData(data, derivedAttributes, onProgress) {\n const attrValues = {};\n const materializedInput = [];\n const total = Array.isArray(data) ? data.length : null;\n let recordsProcessed = 0;\n PivotData.forEachRecord(data, derivedAttributes || {}, function(record) {\n materializedInput.push(record);\n for (const attr of Object.keys(record)) {\n if (!(attr in attrValues)) {\n attrValues[attr] = {};\n if (recordsProcessed > 0) {\n attrValues[attr].null = recordsProcessed;\n }\n }\n }\n for (const attr in attrValues) {\n const value = attr in record ? record[attr] : 'null';\n if (!(value in attrValues[attr])) {\n attrValues[attr][value] = 0;\n }\n attrValues[attr][value]++;\n }\n recordsProcessed++;\n if (onProgress && recordsProcessed % PROGRESS_BATCH_SIZE === 0) {\n onProgress(recordsProcessed, total, 'materialize');\n }\n });\n return {attrValues, materializedInput, totalRecords: recordsProcessed};\n}\n\n/*\n * Aggregates an already-materialized dataset into a PivotData instance and\n * serializes the result into a plain (structured-cloneable) object that can\n * cross the worker boundary and later hydrate PivotData on the main thread.\n */\nexport function computePivotState(materialized, config, onProgress) {\n const total = materialized.totalRecords;\n const pivotData = new PivotData(\n Object.assign({}, config, {\n data: materialized.materializedInput,\n derivedAttributes: {},\n onRecordProcessed: processed => {\n if (onProgress) {\n onProgress(processed, total, 'aggregate');\n }\n },\n })\n );\n // Force key sorting to happen while we are (possibly) inside the worker.\n pivotData.getRowKeys();\n pivotData.getColKeys();\n return serializePivotData(pivotData, materialized);\n}\n\n/*\n * Serializes a fully-built PivotData instance into plain data. Only the final\n * aggregated value of every cell is kept; `value()` and `format()` behavior\n * are reconstructed on the main thread by the PivotData hydration step.\n */\nexport function serializePivotData(pivotData, materialized) {\n const serializeCell = cell => {\n const values = {};\n pivotData.aggregations.forEach(agg => {\n const instance = cell.getInstance(agg.key);\n if (instance) {\n values[agg.key] = instance.value();\n }\n });\n return {values};\n };\n\n const tree = {};\n Object.keys(pivotData.tree).forEach(flatRowKey => {\n const rowBranch = pivotData.tree[flatRowKey];\n const serializedRow = {};\n Object.keys(rowBranch).forEach(flatColKey => {\n serializedRow[flatColKey] = serializeCell(rowBranch[flatColKey]);\n });\n tree[flatRowKey] = serializedRow;\n });\n\n const serializeTotals = totals => {\n const serialized = {};\n Object.keys(totals).forEach(flatKey => {\n serialized[flatKey] = serializeCell(totals[flatKey]);\n });\n return serialized;\n };\n\n return {\n rowKeys: pivotData.rowKeys.map(key => key.slice()),\n colKeys: pivotData.colKeys.map(key => key.slice()),\n tree,\n rowTotals: serializeTotals(pivotData.rowTotals),\n colTotals: serializeTotals(pivotData.colTotals),\n allTotal: serializeCell(pivotData.allTotal),\n aggregations: pivotData.aggregations.map(agg => Object.assign({}, agg)),\n attrValues: materialized.attrValues,\n materializedInput: materialized.materializedInput,\n totalRecords: materialized.totalRecords,\n };\n}\n\n/*\n * Sorters containing functions cannot be structured-cloned into a worker.\n * Returns true when the sorters prop itself, or any of its entries, is a\n * function. In that case PivotData re-sorts on the main thread after\n * hydration (distinct keys are few, so this stays cheap).\n */\nexport function sortersHaveFunctions(sorters) {\n if (typeof sorters === 'function') {\n return true;\n }\n if (!sorters || typeof sorters !== 'object') {\n return false;\n }\n return Object.keys(sorters).some(attr => typeof sorters[attr] === 'function');\n}\n\n/*\n * Returns a structured-cloneable subset of the sorters prop: data-type names\n * and sample-value arrays survive the worker boundary, functions do not.\n */\nexport function sanitizeSorters(sorters) {\n if (\n !sorters ||\n typeof sorters !== 'object' ||\n typeof sorters === 'function'\n ) {\n return {};\n }\n const clean = {};\n Object.keys(sorters).forEach(attr => {\n const sorter = sorters[attr];\n if (typeof sorter === 'string' || Array.isArray(sorter)) {\n clean[attr] = sorter;\n }\n });\n return clean;\n}\n\n/*\n * Extracts exactly the props PivotData needs to compute results, with\n * everything non-serializable stripped, so the object can be posted to the\n * worker and fed straight into PivotData.\n */\nexport function createWorkerConfig(props) {\n return {\n rows: props.rows,\n cols: props.cols,\n aggregatorName: props.aggregatorName,\n aggregations: props.aggregations,\n vals: props.vals,\n valueFilter: props.valueFilter,\n sorters: sanitizeSorters(props.sorters),\n rowOrder: props.rowOrder,\n colOrder: props.colOrder,\n rowSorts: props.rowSorts,\n colSorts: props.colSorts,\n };\n}\n\n/*\n * Stable string signature of everything that influences a worker computation.\n * Data identity is NOT part of it; callers append their own data version.\n */\nexport function createComputeSignature(props) {\n return JSON.stringify(createWorkerConfig(props));\n}\n\n/*\n * Decides whether the current props can be computed inside the Web Worker.\n * Anything that cannot be structured-cloned (function data sources, custom\n * derivedAttributes, custom aggregator maps) forces the classic path.\n * Function-based sorters are allowed: they are re-applied on the main thread.\n */\nexport function canComputeInWorker(props, options = {}) {\n if (options.enabled === false) {\n return false;\n }\n if (typeof Worker === 'undefined') {\n return false;\n }\n const threshold =\n typeof options.threshold === 'number'\n ? options.threshold\n : DEFAULT_WORKER_THRESHOLD;\n const data = props.data;\n if (!Array.isArray(data) || data.length < threshold) {\n return false;\n }\n if (\n props.derivedAttributes &&\n Object.keys(props.derivedAttributes).length > 0\n ) {\n return false;\n }\n if (props.aggregators !== PivotData.defaultProps.aggregators) {\n return false;\n }\n return true;\n}\n"]}
|
package/dist/pivotWorker.js
CHANGED
|
@@ -2,25 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var _pivotCompute = require('./pivotCompute');
|
|
4
4
|
|
|
5
|
-
var MAX_CACHED_DATASETS = 4;
|
|
6
|
-
/*
|
|
7
|
-
* Web Worker entry point for off-main-thread pivot computation.
|
|
8
|
-
*
|
|
9
|
-
* Message protocol (plain, structured-cloneable objects):
|
|
10
|
-
* main -> worker {type: 'pivot:setData', id, dataKey, data}
|
|
11
|
-
* main -> worker {type: 'pivot:compute', id, dataKey, config}
|
|
12
|
-
* worker -> main {type: 'pivot:progress', id, processed, total, phase}
|
|
13
|
-
* worker -> main {type: 'pivot:result', id, result}
|
|
14
|
-
* worker -> main {type: 'pivot:error', id, message}
|
|
15
|
-
*
|
|
16
|
-
* The materialized dataset is cached per dataKey, so repeated computations on
|
|
17
|
-
* the same data (e.g. dragging fields around) do not pay the cost of
|
|
18
|
-
* re-scanning the raw records.
|
|
19
|
-
*/
|
|
20
|
-
|
|
5
|
+
var MAX_CACHED_DATASETS = 4;
|
|
21
6
|
|
|
22
7
|
var datasetCache = new Map();
|
|
23
8
|
|
|
9
|
+
var debugEnabled = false;
|
|
10
|
+
|
|
24
11
|
var cacheDataset = function cacheDataset(dataKey, dataset) {
|
|
25
12
|
datasetCache.delete(dataKey);
|
|
26
13
|
datasetCache.set(dataKey, dataset);
|
|
@@ -35,6 +22,12 @@ var postToMain = function postToMain(message) {
|
|
|
35
22
|
self.postMessage(message);
|
|
36
23
|
};
|
|
37
24
|
|
|
25
|
+
var log = function log(message, detail) {
|
|
26
|
+
if (debugEnabled) {
|
|
27
|
+
postToMain({ type: 'pivot:log', message: message, detail: detail });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
38
31
|
var toErrorMessage = function toErrorMessage(error) {
|
|
39
32
|
return String(error && error.message || error);
|
|
40
33
|
};
|
|
@@ -42,9 +35,17 @@ var toErrorMessage = function toErrorMessage(error) {
|
|
|
42
35
|
self.addEventListener('message', function (event) {
|
|
43
36
|
var message = event.data || {};
|
|
44
37
|
|
|
38
|
+
if (message.type === 'pivot:debug') {
|
|
39
|
+
debugEnabled = Boolean(message.enabled);
|
|
40
|
+
log('debug logging ' + (debugEnabled ? 'enabled' : 'disabled') + ' in worker');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
45
44
|
if (message.type === 'pivot:setData') {
|
|
45
|
+
var startedAt = Date.now();
|
|
46
|
+
log('#' + message.id + ' setData received: ' + (message.data || []).length + ' records (dataKey=' + message.dataKey + ')');
|
|
46
47
|
try {
|
|
47
|
-
cacheDataset(message.dataKey, (0, _pivotCompute.materializeData)(message.data, {}, function (processed, total) {
|
|
48
|
+
var dataset = cacheDataset(message.dataKey, (0, _pivotCompute.materializeData)(message.data, {}, function (processed, total) {
|
|
48
49
|
postToMain({
|
|
49
50
|
type: 'pivot:progress',
|
|
50
51
|
id: message.id,
|
|
@@ -53,7 +54,9 @@ self.addEventListener('message', function (event) {
|
|
|
53
54
|
phase: 'materialize'
|
|
54
55
|
});
|
|
55
56
|
}));
|
|
57
|
+
log('#' + message.id + ' materialized ' + dataset.totalRecords + ' records in ' + (Date.now() - startedAt + 'ms (attributes: ' + Object.keys(dataset.attrValues).join(', ') + ')'));
|
|
56
58
|
} catch (error) {
|
|
59
|
+
log('#' + message.id + ' setData failed: ' + toErrorMessage(error));
|
|
57
60
|
postToMain({
|
|
58
61
|
type: 'pivot:error',
|
|
59
62
|
id: message.id,
|
|
@@ -64,12 +67,15 @@ self.addEventListener('message', function (event) {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
if (message.type === 'pivot:compute') {
|
|
70
|
+
var _startedAt = Date.now();
|
|
71
|
+
var config = message.config || {};
|
|
67
72
|
try {
|
|
68
|
-
var
|
|
69
|
-
if (!
|
|
73
|
+
var _dataset = datasetCache.get(message.dataKey);
|
|
74
|
+
if (!_dataset) {
|
|
70
75
|
throw new Error('no dataset staged for key ' + message.dataKey);
|
|
71
76
|
}
|
|
72
|
-
|
|
77
|
+
log('#' + message.id + ' compute received (rows=[' + (config.rows || []).join(', ') + '] cols=[' + (config.cols || []).join(', ') + '])');
|
|
78
|
+
var result = (0, _pivotCompute.computePivotState)(_dataset, message.config, function (processed, total, phase) {
|
|
73
79
|
postToMain({
|
|
74
80
|
type: 'pivot:progress',
|
|
75
81
|
id: message.id,
|
|
@@ -78,8 +84,12 @@ self.addEventListener('message', function (event) {
|
|
|
78
84
|
phase: phase
|
|
79
85
|
});
|
|
80
86
|
});
|
|
87
|
+
log('#' + message.id + ' computed in ' + (Date.now() - _startedAt) + 'ms (rowKeys: ' + result.rowKeys.length + ', colKeys: ' + result.colKeys.length + ', aggregations: ' + result.aggregations.map(function (agg) {
|
|
88
|
+
return agg.key;
|
|
89
|
+
}).join(', ') + ')');
|
|
81
90
|
postToMain({ type: 'pivot:result', id: message.id, result: result });
|
|
82
91
|
} catch (error) {
|
|
92
|
+
log('#' + message.id + ' compute failed: ' + toErrorMessage(error));
|
|
83
93
|
postToMain({
|
|
84
94
|
type: 'pivot:error',
|
|
85
95
|
id: message.id,
|
package/dist/pivotWorker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pivotWorker.js"],"names":["MAX_CACHED_DATASETS","datasetCache","Map","cacheDataset","dataKey","dataset","delete","set","size","oldestKey","keys","next","value","postToMain","self","postMessage","message","toErrorMessage","String","error","addEventListener","event","data","
|
|
1
|
+
{"version":3,"sources":["../src/pivotWorker.js"],"names":["MAX_CACHED_DATASETS","datasetCache","Map","debugEnabled","cacheDataset","dataKey","dataset","delete","set","size","oldestKey","keys","next","value","postToMain","self","postMessage","message","log","detail","type","toErrorMessage","String","error","addEventListener","event","data","Boolean","enabled","startedAt","Date","now","id","length","processed","total","phase","totalRecords","Object","attrValues","join","config","get","Error","rows","cols","result","rowKeys","colKeys","aggregations","map","agg","key"],"mappings":"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","file":"pivotWorker.js","sourcesContent":["import {materializeData, computePivotState} from './pivotCompute';\n\nconst MAX_CACHED_DATASETS = 4;\n\nconst datasetCache = new Map();\n\nlet debugEnabled = false;\n\nconst cacheDataset = (dataKey, dataset) => {\n datasetCache.delete(dataKey);\n datasetCache.set(dataKey, dataset);\n while (datasetCache.size > MAX_CACHED_DATASETS) {\n const oldestKey = datasetCache.keys().next().value;\n datasetCache.delete(oldestKey);\n }\n return dataset;\n};\n\nconst postToMain = message => {\n self.postMessage(message);\n};\n\nconst log = (message, detail) => {\n if (debugEnabled) {\n postToMain({type: 'pivot:log', message, detail});\n }\n};\n\nconst toErrorMessage = error => String((error && error.message) || error);\n\nself.addEventListener('message', event => {\n const message = event.data || {};\n\n if (message.type === 'pivot:debug') {\n debugEnabled = Boolean(message.enabled);\n log(`debug logging ${debugEnabled ? 'enabled' : 'disabled'} in worker`);\n return;\n }\n\n if (message.type === 'pivot:setData') {\n const startedAt = Date.now();\n log(\n `#${message.id} setData received: ${\n (message.data || []).length\n } records (dataKey=${message.dataKey})`\n );\n try {\n const dataset = cacheDataset(\n message.dataKey,\n materializeData(message.data, {}, (processed, total) => {\n postToMain({\n type: 'pivot:progress',\n id: message.id,\n processed,\n total,\n phase: 'materialize',\n });\n })\n );\n log(\n `#${message.id} materialized ${dataset.totalRecords} records in ` +\n `${Date.now() - startedAt}ms (attributes: ${Object.keys(\n dataset.attrValues\n ).join(', ')})`\n );\n } catch (error) {\n log(`#${message.id} setData failed: ${toErrorMessage(error)}`);\n postToMain({\n type: 'pivot:error',\n id: message.id,\n message: toErrorMessage(error),\n });\n }\n return;\n }\n\n if (message.type === 'pivot:compute') {\n const startedAt = Date.now();\n const config = message.config || {};\n try {\n const dataset = datasetCache.get(message.dataKey);\n if (!dataset) {\n throw new Error(`no dataset staged for key ${message.dataKey}`);\n }\n log(\n `#${message.id} compute received (rows=[${(config.rows || []).join(\n ', '\n )}] cols=[${(config.cols || []).join(', ')}])`\n );\n const result = computePivotState(\n dataset,\n message.config,\n (processed, total, phase) => {\n postToMain({\n type: 'pivot:progress',\n id: message.id,\n processed,\n total,\n phase,\n });\n }\n );\n log(\n `#${message.id} computed in ${Date.now() - startedAt}ms (rowKeys: ${\n result.rowKeys.length\n }, colKeys: ${\n result.colKeys.length\n }, aggregations: ${result.aggregations.map(agg => agg.key).join(', ')})`\n );\n postToMain({type: 'pivot:result', id: message.id, result});\n } catch (error) {\n log(`#${message.id} compute failed: ${toErrorMessage(error)}`);\n postToMain({\n type: 'pivot:error',\n id: message.id,\n message: toErrorMessage(error),\n });\n }\n }\n});\n"]}
|