@hrforte/react-pivottable 1.1.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 +134 -0
- package/dist/PivotDataWorker.js +264 -0
- package/dist/PivotDataWorker.js.map +1 -0
- package/dist/PivotTableUI.js +1742 -1612
- package/dist/PivotTableUI.js.map +1 -1
- package/dist/PlotlyRenderers.js +3 -1
- package/dist/PlotlyRenderers.js.map +1 -1
- package/dist/Utilities.js +134 -24
- package/dist/Utilities.js.map +1 -1
- package/dist/__tests__/PivotDataWorker-test.js +170 -0
- package/dist/__tests__/PivotDataWorker-test.js.map +1 -0
- package/dist/__tests__/PivotTableUI-processing-test.js +114 -0
- package/dist/__tests__/PivotTableUI-processing-test.js.map +1 -0
- package/dist/__tests__/pivotCompute-test.js +215 -0
- package/dist/__tests__/pivotCompute-test.js.map +1 -0
- package/dist/pivot-worker.js +3397 -0
- package/dist/pivotCompute.js +274 -0
- package/dist/pivotCompute.js.map +1 -0
- package/dist/pivotWorker.js +101 -0
- package/dist/pivotWorker.js.map +1 -0
- package/dist/pivottable.css +74 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Login npm
|
|
4
|
+
npm login
|
|
5
|
+
|
|
6
|
+
# Patch: 1.0.0 -> 1.0.1
|
|
7
|
+
npm version patch
|
|
8
|
+
|
|
9
|
+
# Minor: 1.0.0 -> 1.1.0
|
|
10
|
+
npm version minor
|
|
11
|
+
|
|
12
|
+
# Major: 1.0.0 -> 2.0.0
|
|
13
|
+
npm version major
|
|
14
|
+
|
|
15
|
+
# Build
|
|
16
|
+
npm run build
|
|
17
|
+
|
|
18
|
+
# Publish to npm
|
|
19
|
+
npm publish
|
|
20
|
+
|
|
21
|
+
# View version
|
|
22
|
+
npm view @hrforte/react-pivottable version
|
|
23
|
+
|
|
24
|
+
# Large datasets (Web Worker)
|
|
25
|
+
|
|
26
|
+
With large datasets (e.g. 200,000 records) pivot computation is offloaded to a
|
|
27
|
+
Web Worker so the main thread (and the UI) stays responsive.
|
|
28
|
+
|
|
29
|
+
## 1. Get the worker file
|
|
30
|
+
|
|
31
|
+
The published package ships a **self-contained** worker bundle (no imports, no
|
|
32
|
+
dependencies) at:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
node_modules/@hrforte/react-pivottable/dist/pivot-worker.js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 2. Copy it somewhere the browser can fetch
|
|
39
|
+
|
|
40
|
+
`new Worker(url)` resolves `url` **against the page URL**, not against the
|
|
41
|
+
bundle, and it must be served **same-origin**. So the file has to be copied to
|
|
42
|
+
a public/static folder of your app - it is not enough to just install the
|
|
43
|
+
package.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# copy into the folder that the dev server / web server exposes as static root
|
|
47
|
+
mkdir -p public
|
|
48
|
+
cp node_modules/@hrforte/react-pivottable/dist/pivot-worker.js public/
|
|
49
|
+
|
|
50
|
+
# optional but recommended: keep it in sync automatically after install
|
|
51
|
+
# package.json -> "scripts": { "postinstall": "node -e \"require('fs').copyFileSync('node_modules/@hrforte/react-pivottable/dist/pivot-worker.js','public/pivot-worker.js')\"" }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Where to put it per setup:
|
|
55
|
+
|
|
56
|
+
| Setup | Target folder | `workerUrl` prop |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Plain static site / webpack (this repo) | `examples/` (next to `index.html`) | not needed (default) |
|
|
59
|
+
| Create React App | `public/` | not needed (default) |
|
|
60
|
+
| Vite | `public/` | not needed (default) |
|
|
61
|
+
| Next.js (app or pages) | `public/` | not needed (default) |
|
|
62
|
+
| Page in a sub-folder (`/admin/report.html`) | `/admin/pivot-worker.js` | `workerUrl="/admin/pivot-worker.js"` |
|
|
63
|
+
| App deployed under a sub-path (`/myapp/`) | root of that base path | `workerUrl="/myapp/pivot-worker.js"` |
|
|
64
|
+
| Worker hosted elsewhere (CDN) | - | must be same-origin, see below |
|
|
65
|
+
|
|
66
|
+
## 3. Pass `workerUrl` (only when the default does not match)
|
|
67
|
+
|
|
68
|
+
```jsx
|
|
69
|
+
<PivotTableUI
|
|
70
|
+
data={rows}
|
|
71
|
+
workerUrl="/myapp/pivot-worker.js" // absolute web-root path (sub-path deploy)
|
|
72
|
+
// or a relative path resolved against the page URL:
|
|
73
|
+
// workerUrl="assets/js/pivot-worker.js"
|
|
74
|
+
// or a full URL on the SAME origin:
|
|
75
|
+
// workerUrl={`${window.location.origin}/static/pivot-worker.js`}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Accepted forms (all verified):
|
|
80
|
+
|
|
81
|
+
| Value | Resolved by the browser as | Use when |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `'pivot-worker.js'` (default) | `<page-directory>/pivot-worker.js` | file sits next to the page |
|
|
84
|
+
| `'assets/js/pivot-worker.js'` | `<page-directory>/assets/js/...` | nested static folder |
|
|
85
|
+
| `'/myapp/pivot-worker.js'` | origin root + `/myapp/...` | app served under a sub-path |
|
|
86
|
+
| `'https://cdn.example.com/pivot-worker.js'` | absolute | same-origin only (CORS does not apply to `new Worker`) |
|
|
87
|
+
|
|
88
|
+
Notes:
|
|
89
|
+
|
|
90
|
+
- A worker script must be **same-origin** by default. Cross-origin URLs only
|
|
91
|
+
work if the server sends CORS headers (`Access-Control-Allow-Origin`) and the
|
|
92
|
+
browser allows it - support varies, so prefer serving the file from your own
|
|
93
|
+
origin. If you must host it elsewhere, fetch the script yourself and pass a
|
|
94
|
+
`Blob` URL as `workerUrl`.
|
|
95
|
+
- Changing `workerUrl` at runtime is supported: the previous worker is
|
|
96
|
+
terminated, a new one is created from the new URL and the current dataset is
|
|
97
|
+
staged on it again.
|
|
98
|
+
- If you bundle the worker yourself, pass the emitted URL, e.g. with Vite:
|
|
99
|
+
`import workerUrl from '@hrforte/react-pivottable/dist/pivot-worker.js?url'`
|
|
100
|
+
then `workerUrl={workerUrl}`.
|
|
101
|
+
|
|
102
|
+
## 4. Fallback
|
|
103
|
+
|
|
104
|
+
If the worker file cannot be loaded (404, blocked, old browser), the component
|
|
105
|
+
automatically falls back to the previous synchronous computation: same results,
|
|
106
|
+
but the main thread is used. Verify with `workerDebug`:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
[pivottable worker] worker created: /myapp/pivot-worker.js
|
|
110
|
+
[pivottable worker] #1 dispatching to worker (dataKey=..., staging 1500 records, ...)
|
|
111
|
+
[pivottable worker] (in worker) #1 computed in 25ms (rowKeys: 4, colKeys: 3, ...)
|
|
112
|
+
[pivottable worker] #1 result received in 86ms { totalRecords: 1500, ... }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Available props
|
|
116
|
+
|
|
117
|
+
- `enableWorker` (`bool`, default `true`): disable to always compute on the
|
|
118
|
+
main thread.
|
|
119
|
+
- `workerThreshold` (`number`, default `DEFAULT_WORKER_THRESHOLD`, currently
|
|
120
|
+
`200` in this build): minimum number of records required before using the
|
|
121
|
+
worker.
|
|
122
|
+
- `workerUrl` (`string`, default `'pivot-worker.js'`): URL of the worker bundle
|
|
123
|
+
(see above).
|
|
124
|
+
- `workerDebug` (`bool`, default `false`): prints the whole worker pipeline
|
|
125
|
+
(worker creation, data staging, progress, compute, result, errors and the
|
|
126
|
+
fallback to the main thread) to the browser console with a
|
|
127
|
+
`[pivottable worker]` prefix. Logs emitted inside the worker thread are
|
|
128
|
+
forwarded to the page console as well. Can also be enabled globally with
|
|
129
|
+
`window.__PVT_WORKER_DEBUG__ = true`.
|
|
130
|
+
- `isShowProcessing` (`bool`, default `false`): testing/debugging hook. When
|
|
131
|
+
`true` the loading overlay is forced on top of the table even if nothing is
|
|
132
|
+
being computed, so the `PivotLoadingOverlay` can be inspected without
|
|
133
|
+
generating a large dataset. A sample progress value is supplied so the
|
|
134
|
+
progress bar and percentage are rendered as well.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.DEFAULT_WORKER_URL = undefined;
|
|
7
|
+
exports.setWorkerDebug = setWorkerDebug;
|
|
8
|
+
exports.configureWorkerUrl = configureWorkerUrl;
|
|
9
|
+
exports.computePivotInWorker = computePivotInWorker;
|
|
10
|
+
exports.computePivotSynchronously = computePivotSynchronously;
|
|
11
|
+
exports.computePivotWithFallback = computePivotWithFallback;
|
|
12
|
+
|
|
13
|
+
var _pivotCompute = require('./pivotCompute');
|
|
14
|
+
|
|
15
|
+
var DEFAULT_WORKER_URL = exports.DEFAULT_WORKER_URL = 'pivot-worker.js'; /* eslint-disable valid-jsdoc */
|
|
16
|
+
/* eslint-disable no-console */
|
|
17
|
+
/* eslint-disable no-undefined */
|
|
18
|
+
/*
|
|
19
|
+
* Main-thread client for the pivot Web Worker (src/pivotWorker.js).
|
|
20
|
+
*
|
|
21
|
+
* Responsibilities:
|
|
22
|
+
* - lazily create a single shared Worker (at `workerUrl`, overrideable),
|
|
23
|
+
* - route progress/result/error messages to the matching request,
|
|
24
|
+
* - transparently fall back to a synchronous main-thread computation when
|
|
25
|
+
* workers are unavailable or the worker bundle fails to load.
|
|
26
|
+
*
|
|
27
|
+
* Debug logging is off by default. Turn it on with the `workerDebug` prop of
|
|
28
|
+
* PivotTableUI (or by setting `window.__PVT_WORKER_DEBUG__ = true` before
|
|
29
|
+
* rendering) to trace worker creation, staging, computation, progress,
|
|
30
|
+
* results, errors and fallbacks in the browser console.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
var REQUEST_TIMEOUT_MS = 300000;
|
|
35
|
+
|
|
36
|
+
var workerUrl = DEFAULT_WORKER_URL;
|
|
37
|
+
var sharedWorker = null;
|
|
38
|
+
// The worker is marked broken only if it fails before ever producing a
|
|
39
|
+
// result (e.g. a 404 worker script); runtime hiccups later on just reject
|
|
40
|
+
// the affected request and fall back once.
|
|
41
|
+
var sharedWorkerBroken = false;
|
|
42
|
+
var hasResultEver = false;
|
|
43
|
+
var nextRequestId = 1;
|
|
44
|
+
var lastStagedDataKey = null;
|
|
45
|
+
var workerDebug = false;
|
|
46
|
+
var pendingRequests = new Map();
|
|
47
|
+
|
|
48
|
+
function setWorkerDebug(enabled) {
|
|
49
|
+
var next = Boolean(enabled);
|
|
50
|
+
var wasEnabled = workerDebug;
|
|
51
|
+
if (next === wasEnabled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (next) {
|
|
55
|
+
workerDebug = true;
|
|
56
|
+
log('debug logging enabled');
|
|
57
|
+
} else {
|
|
58
|
+
// Log while logging is still active, so the "disabled" notice is shown.
|
|
59
|
+
log('debug logging disabled');
|
|
60
|
+
workerDebug = false;
|
|
61
|
+
}
|
|
62
|
+
if (sharedWorker) {
|
|
63
|
+
sharedWorker.postMessage({ type: 'pivot:debug', enabled: workerDebug });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var isDebugEnabled = function isDebugEnabled() {
|
|
68
|
+
return workerDebug || typeof window !== 'undefined' && window.__PVT_WORKER_DEBUG__ === true;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function log() {
|
|
72
|
+
var _console;
|
|
73
|
+
|
|
74
|
+
if (!isDebugEnabled()) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
|
79
|
+
args[_key] = arguments[_key];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
(_console = console).log.apply(_console, ['%c[pivottable worker]', 'color:#ff6600;font-weight:bold'].concat(args));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
var describeConfig = function describeConfig(config) {
|
|
86
|
+
return 'rows=[' + (config.rows || []).join(', ') + '] cols=[' + (config.cols || []).join(', ') + '] aggregations=' + (config.aggregations || []).length;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Length of an optional array field, used for defensive debug logging.
|
|
90
|
+
var countOf = function countOf(value) {
|
|
91
|
+
return Array.isArray(value) ? value.length : undefined;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
var failAllPending = function failAllPending(error) {
|
|
95
|
+
pendingRequests.forEach(function (entry) {
|
|
96
|
+
clearTimeout(entry.timer);
|
|
97
|
+
entry.reject(error);
|
|
98
|
+
});
|
|
99
|
+
pendingRequests.clear();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function configureWorkerUrl(url) {
|
|
103
|
+
if (url && url !== workerUrl) {
|
|
104
|
+
workerUrl = url;
|
|
105
|
+
if (sharedWorker) {
|
|
106
|
+
// Reject in-flight requests first so callers fall back to the
|
|
107
|
+
// synchronous path instead of waiting for the request timeout.
|
|
108
|
+
failAllPending(new Error('Pivot worker was reconfigured'));
|
|
109
|
+
sharedWorker.terminate();
|
|
110
|
+
sharedWorker = null;
|
|
111
|
+
sharedWorkerBroken = false;
|
|
112
|
+
hasResultEver = false;
|
|
113
|
+
lastStagedDataKey = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var handleWorkerMessage = function handleWorkerMessage(event) {
|
|
119
|
+
var message = event.data || {};
|
|
120
|
+
if (message.type === 'pivot:log') {
|
|
121
|
+
log('(in worker)', message.message, message.detail || '');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
var entry = pendingRequests.get(message.id);
|
|
125
|
+
if (!entry) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (message.type === 'pivot:progress') {
|
|
129
|
+
var percent = message.total > 0 ? Math.round(message.processed / message.total * 100) : null;
|
|
130
|
+
log('#' + message.id + ' progress ' + message.phase + ': ' + message.processed + (message.total ? '/' + message.total : '') + (percent === null ? '' : ' (' + percent + '%)'));
|
|
131
|
+
if (entry.onProgress) {
|
|
132
|
+
entry.onProgress(message.processed, message.total, message.phase);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
clearTimeout(entry.timer);
|
|
137
|
+
pendingRequests.delete(message.id);
|
|
138
|
+
if (message.type === 'pivot:result') {
|
|
139
|
+
hasResultEver = true;
|
|
140
|
+
var result = message.result;
|
|
141
|
+
log('#' + message.id + ' result received in ' + (Date.now() - entry.startedAt) + 'ms', {
|
|
142
|
+
totalRecords: result && result.totalRecords,
|
|
143
|
+
rowKeys: countOf(result && result.rowKeys),
|
|
144
|
+
colKeys: countOf(result && result.colKeys),
|
|
145
|
+
aggregations: countOf(result && result.aggregations)
|
|
146
|
+
});
|
|
147
|
+
entry.resolve(message.result);
|
|
148
|
+
} else if (message.type === 'pivot:error') {
|
|
149
|
+
log('#' + message.id + ' worker reported an error:', message.message);
|
|
150
|
+
entry.reject(new Error(message.message || 'Pivot worker error'));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
var ensureWorker = function ensureWorker() {
|
|
155
|
+
if (sharedWorkerBroken) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (typeof Worker === 'undefined') {
|
|
159
|
+
log('no Worker support in this environment');
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
if (!sharedWorker) {
|
|
163
|
+
try {
|
|
164
|
+
sharedWorker = new Worker(workerUrl);
|
|
165
|
+
log('worker created:', workerUrl);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
sharedWorkerBroken = true;
|
|
168
|
+
log('worker creation failed:', error && error.message);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
sharedWorker.onmessage = handleWorkerMessage;
|
|
172
|
+
sharedWorker.onerror = function (event) {
|
|
173
|
+
log('worker error event (script failed to load / runtime error):', event && event.message || '');
|
|
174
|
+
failAllPending(new Error('Pivot worker failed to load'));
|
|
175
|
+
if (!hasResultEver) {
|
|
176
|
+
sharedWorkerBroken = true;
|
|
177
|
+
log('worker marked unusable -> will use the synchronous fallback');
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
if (isDebugEnabled()) {
|
|
181
|
+
sharedWorker.postMessage({ type: 'pivot:debug', enabled: true });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return sharedWorker;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
* Runs a full computation inside the shared worker.
|
|
189
|
+
*
|
|
190
|
+
* `dataKey` must change whenever the underlying dataset changes. It lets the
|
|
191
|
+
* worker reuse the materialized dataset across consecutive computations and
|
|
192
|
+
* lets the client skip re-staging identical data.
|
|
193
|
+
*/
|
|
194
|
+
function computePivotInWorker(data, dataKey, config, onProgress) {
|
|
195
|
+
var worker = ensureWorker();
|
|
196
|
+
if (!worker) {
|
|
197
|
+
log('worker unavailable -> synchronous fallback');
|
|
198
|
+
return Promise.reject(new Error('Web worker unavailable'));
|
|
199
|
+
}
|
|
200
|
+
var id = nextRequestId++;
|
|
201
|
+
return new Promise(function (resolve, reject) {
|
|
202
|
+
var timer = setTimeout(function () {
|
|
203
|
+
pendingRequests.delete(id);
|
|
204
|
+
log('#' + id + ' timed out after ' + REQUEST_TIMEOUT_MS + 'ms');
|
|
205
|
+
reject(new Error('Pivot worker timed out'));
|
|
206
|
+
}, REQUEST_TIMEOUT_MS);
|
|
207
|
+
pendingRequests.set(id, {
|
|
208
|
+
resolve: resolve,
|
|
209
|
+
reject: reject,
|
|
210
|
+
onProgress: onProgress,
|
|
211
|
+
timer: timer,
|
|
212
|
+
startedAt: Date.now()
|
|
213
|
+
});
|
|
214
|
+
var mustStage = lastStagedDataKey !== dataKey;
|
|
215
|
+
log('#' + id + ' dispatching to worker (dataKey=' + dataKey + (mustStage ? ', staging ' + (data || []).length + ' records' : ', cached dataset') + ', ' + describeConfig(config) + ')');
|
|
216
|
+
if (mustStage) {
|
|
217
|
+
worker.postMessage({ type: 'pivot:setData', id: id, dataKey: dataKey, data: data });
|
|
218
|
+
lastStagedDataKey = dataKey;
|
|
219
|
+
}
|
|
220
|
+
worker.postMessage({ type: 'pivot:compute', id: id, dataKey: dataKey, config: config });
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* Same contract as the worker path, executed on the main thread. Used as the
|
|
226
|
+
* automatic fallback whenever the worker path is unavailable. Deferred by
|
|
227
|
+
* one tick so a loading indicator can paint before the blocking work starts.
|
|
228
|
+
*/
|
|
229
|
+
function computePivotSynchronously(data, config, onProgress) {
|
|
230
|
+
return new Promise(function (resolve, reject) {
|
|
231
|
+
setTimeout(function () {
|
|
232
|
+
var startedAt = Date.now();
|
|
233
|
+
log('running synchronous computation on the main thread...');
|
|
234
|
+
try {
|
|
235
|
+
var materialized = (0, _pivotCompute.materializeData)(data, {}, function (processed, total) {
|
|
236
|
+
onProgress(processed, total, 'materialize');
|
|
237
|
+
});
|
|
238
|
+
var result = (0, _pivotCompute.computePivotState)(materialized, config, onProgress);
|
|
239
|
+
log('synchronous computation finished in ' + (Date.now() - startedAt) + 'ms', {
|
|
240
|
+
totalRecords: result.totalRecords,
|
|
241
|
+
rowKeys: result.rowKeys.length,
|
|
242
|
+
colKeys: result.colKeys.length
|
|
243
|
+
});
|
|
244
|
+
resolve(result);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
log('synchronous computation failed:', error && error.message);
|
|
247
|
+
reject(error);
|
|
248
|
+
}
|
|
249
|
+
}, 0);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/*
|
|
254
|
+
* Worker-first entry point with automatic synchronous fallback. The fallback
|
|
255
|
+
* produces the exact same serialized shape, so callers render identically
|
|
256
|
+
* either way.
|
|
257
|
+
*/
|
|
258
|
+
function computePivotWithFallback(data, dataKey, config, onProgress) {
|
|
259
|
+
return computePivotInWorker(data, dataKey, config, onProgress).catch(function (error) {
|
|
260
|
+
log('worker request failed, falling back to the main thread:', error && error.message || error);
|
|
261
|
+
return computePivotSynchronously(data, config, onProgress);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=PivotDataWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/PivotDataWorker.js"],"names":["setWorkerDebug","configureWorkerUrl","computePivotInWorker","computePivotSynchronously","computePivotWithFallback","DEFAULT_WORKER_URL","REQUEST_TIMEOUT_MS","workerUrl","sharedWorker","sharedWorkerBroken","hasResultEver","nextRequestId","lastStagedDataKey","workerDebug","pendingRequests","Map","enabled","next","Boolean","wasEnabled","log","postMessage","type","isDebugEnabled","window","__PVT_WORKER_DEBUG__","args","describeConfig","config","rows","join","cols","aggregations","length","countOf","Array","isArray","value","undefined","failAllPending","forEach","clearTimeout","entry","timer","reject","error","clear","url","Error","terminate","handleWorkerMessage","message","event","data","detail","get","id","percent","total","Math","round","processed","phase","onProgress","delete","result","Date","now","startedAt","totalRecords","rowKeys","colKeys","resolve","ensureWorker","Worker","onmessage","onerror","dataKey","worker","Promise","setTimeout","set","mustStage","materialized","catch"],"mappings":"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","file":"PivotDataWorker.js","sourcesContent":["/* eslint-disable valid-jsdoc */\n/* eslint-disable no-console */\n/* eslint-disable no-undefined */\n/*\n * Main-thread client for the pivot Web Worker (src/pivotWorker.js).\n *\n * Responsibilities:\n * - lazily create a single shared Worker (at `workerUrl`, overrideable),\n * - route progress/result/error messages to the matching request,\n * - transparently fall back to a synchronous main-thread computation when\n * workers are unavailable or the worker bundle fails to load.\n *\n * Debug logging is off by default. Turn it on with the `workerDebug` prop of\n * PivotTableUI (or by setting `window.__PVT_WORKER_DEBUG__ = true` before\n * rendering) to trace worker creation, staging, computation, progress,\n * results, errors and fallbacks in the browser console.\n */\nimport {materializeData, computePivotState} from './pivotCompute';\n\nexport const DEFAULT_WORKER_URL = 'pivot-worker.js';\n\nconst REQUEST_TIMEOUT_MS = 300000;\n\nlet workerUrl = DEFAULT_WORKER_URL;\nlet sharedWorker = null;\n// The worker is marked broken only if it fails before ever producing a\n// result (e.g. a 404 worker script); runtime hiccups later on just reject\n// the affected request and fall back once.\nlet sharedWorkerBroken = false;\nlet hasResultEver = false;\nlet nextRequestId = 1;\nlet lastStagedDataKey = null;\nlet workerDebug = false;\nconst pendingRequests = new Map();\n\nexport function setWorkerDebug(enabled) {\n const next = Boolean(enabled);\n const wasEnabled = workerDebug;\n if (next === wasEnabled) {\n return;\n }\n if (next) {\n workerDebug = true;\n log('debug logging enabled');\n } else {\n // Log while logging is still active, so the \"disabled\" notice is shown.\n log('debug logging disabled');\n workerDebug = false;\n }\n if (sharedWorker) {\n sharedWorker.postMessage({type: 'pivot:debug', enabled: workerDebug});\n }\n}\n\nconst isDebugEnabled = () =>\n workerDebug ||\n (typeof window !== 'undefined' && window.__PVT_WORKER_DEBUG__ === true);\n\nfunction log(...args) {\n if (!isDebugEnabled()) {\n return;\n }\n console.log(\n '%c[pivottable worker]',\n 'color:#ff6600;font-weight:bold',\n ...args\n );\n}\n\nconst describeConfig = config =>\n `rows=[${(config.rows || []).join(', ')}] cols=[${(config.cols || []).join(\n ', '\n )}] aggregations=${(config.aggregations || []).length}`;\n\n// Length of an optional array field, used for defensive debug logging.\nconst countOf = value => (Array.isArray(value) ? value.length : undefined);\n\nconst failAllPending = error => {\n pendingRequests.forEach(entry => {\n clearTimeout(entry.timer);\n entry.reject(error);\n });\n pendingRequests.clear();\n};\n\nexport function configureWorkerUrl(url) {\n if (url && url !== workerUrl) {\n workerUrl = url;\n if (sharedWorker) {\n // Reject in-flight requests first so callers fall back to the\n // synchronous path instead of waiting for the request timeout.\n failAllPending(new Error('Pivot worker was reconfigured'));\n sharedWorker.terminate();\n sharedWorker = null;\n sharedWorkerBroken = false;\n hasResultEver = false;\n lastStagedDataKey = null;\n }\n }\n}\n\nconst handleWorkerMessage = event => {\n const message = event.data || {};\n if (message.type === 'pivot:log') {\n log('(in worker)', message.message, message.detail || '');\n return;\n }\n const entry = pendingRequests.get(message.id);\n if (!entry) {\n return;\n }\n if (message.type === 'pivot:progress') {\n const percent =\n message.total > 0\n ? Math.round((message.processed / message.total) * 100)\n : null;\n log(\n `#${message.id} progress ${message.phase}: ${message.processed}` +\n (message.total ? `/${message.total}` : '') +\n (percent === null ? '' : ` (${percent}%)`)\n );\n if (entry.onProgress) {\n entry.onProgress(message.processed, message.total, message.phase);\n }\n return;\n }\n clearTimeout(entry.timer);\n pendingRequests.delete(message.id);\n if (message.type === 'pivot:result') {\n hasResultEver = true;\n const result = message.result;\n log(`#${message.id} result received in ${Date.now() - entry.startedAt}ms`, {\n totalRecords: result && result.totalRecords,\n rowKeys: countOf(result && result.rowKeys),\n colKeys: countOf(result && result.colKeys),\n aggregations: countOf(result && result.aggregations),\n });\n entry.resolve(message.result);\n } else if (message.type === 'pivot:error') {\n log(`#${message.id} worker reported an error:`, message.message);\n entry.reject(new Error(message.message || 'Pivot worker error'));\n }\n};\n\nconst ensureWorker = () => {\n if (sharedWorkerBroken) {\n return null;\n }\n if (typeof Worker === 'undefined') {\n log('no Worker support in this environment');\n return null;\n }\n if (!sharedWorker) {\n try {\n sharedWorker = new Worker(workerUrl);\n log('worker created:', workerUrl);\n } catch (error) {\n sharedWorkerBroken = true;\n log('worker creation failed:', error && error.message);\n return null;\n }\n sharedWorker.onmessage = handleWorkerMessage;\n sharedWorker.onerror = event => {\n log(\n 'worker error event (script failed to load / runtime error):',\n (event && event.message) || ''\n );\n failAllPending(new Error('Pivot worker failed to load'));\n if (!hasResultEver) {\n sharedWorkerBroken = true;\n log('worker marked unusable -> will use the synchronous fallback');\n }\n };\n if (isDebugEnabled()) {\n sharedWorker.postMessage({type: 'pivot:debug', enabled: true});\n }\n }\n return sharedWorker;\n};\n\n/*\n * Runs a full computation inside the shared worker.\n *\n * `dataKey` must change whenever the underlying dataset changes. It lets the\n * worker reuse the materialized dataset across consecutive computations and\n * lets the client skip re-staging identical data.\n */\nexport function computePivotInWorker(data, dataKey, config, onProgress) {\n const worker = ensureWorker();\n if (!worker) {\n log('worker unavailable -> synchronous fallback');\n return Promise.reject(new Error('Web worker unavailable'));\n }\n const id = nextRequestId++;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n pendingRequests.delete(id);\n log(`#${id} timed out after ${REQUEST_TIMEOUT_MS}ms`);\n reject(new Error('Pivot worker timed out'));\n }, REQUEST_TIMEOUT_MS);\n pendingRequests.set(id, {\n resolve,\n reject,\n onProgress,\n timer,\n startedAt: Date.now(),\n });\n const mustStage = lastStagedDataKey !== dataKey;\n log(\n `#${id} dispatching to worker (dataKey=${dataKey}${\n mustStage\n ? ', staging ' + (data || []).length + ' records'\n : ', cached dataset'\n }, ${describeConfig(config)})`\n );\n if (mustStage) {\n worker.postMessage({type: 'pivot:setData', id, dataKey, data});\n lastStagedDataKey = dataKey;\n }\n worker.postMessage({type: 'pivot:compute', id, dataKey, config});\n });\n}\n\n/*\n * Same contract as the worker path, executed on the main thread. Used as the\n * automatic fallback whenever the worker path is unavailable. Deferred by\n * one tick so a loading indicator can paint before the blocking work starts.\n */\nexport function computePivotSynchronously(data, config, onProgress) {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n const startedAt = Date.now();\n log('running synchronous computation on the main thread...');\n try {\n const materialized = materializeData(data, {}, (processed, total) => {\n onProgress(processed, total, 'materialize');\n });\n const result = computePivotState(materialized, config, onProgress);\n log(`synchronous computation finished in ${Date.now() - startedAt}ms`, {\n totalRecords: result.totalRecords,\n rowKeys: result.rowKeys.length,\n colKeys: result.colKeys.length,\n });\n resolve(result);\n } catch (error) {\n log('synchronous computation failed:', error && error.message);\n reject(error);\n }\n }, 0);\n });\n}\n\n/*\n * Worker-first entry point with automatic synchronous fallback. The fallback\n * produces the exact same serialized shape, so callers render identically\n * either way.\n */\nexport function computePivotWithFallback(data, dataKey, config, onProgress) {\n return computePivotInWorker(data, dataKey, config, onProgress).catch(\n error => {\n log(\n 'worker request failed, falling back to the main thread:',\n (error && error.message) || error\n );\n return computePivotSynchronously(data, config, onProgress);\n }\n );\n}\n"]}
|