@hrforte/react-pivottable 2.0.0 → 2.1.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.
package/README.md CHANGED
@@ -26,18 +26,107 @@ npm view @hrforte/react-pivottable version
26
26
  With large datasets (e.g. 200,000 records) pivot computation is offloaded to a
27
27
  Web Worker so the main thread (and the UI) stays responsive.
28
28
 
29
- - Copy `dist/pivot-worker.js` next to the page that uses the library (it is
30
- served from the site root by default), or pass a custom `workerUrl`.
31
- - If the worker file cannot be loaded, the component automatically falls back
32
- to the previous synchronous computation (same results, main thread blocked).
29
+ ## 1. Get the worker file
33
30
 
34
- Available props:
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
35
116
 
36
117
  - `enableWorker` (`bool`, default `true`): disable to always compute on the
37
118
  main thread.
38
- - `workerThreshold` (`number`, default `10000`): minimum number of records
39
- required before using the worker.
40
- - `workerUrl` (`string`, default `'pivot-worker.js'`): URL of the worker bundle.
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`.
41
130
  - `isShowProcessing` (`bool`, default `false`): testing/debugging hook. When
42
131
  `true` the loading overlay is forced on top of the table even if nothing is
43
132
  being computed, so the `PivotLoadingOverlay` can be inspected without
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.DEFAULT_WORKER_URL = undefined;
7
+ exports.setWorkerDebug = setWorkerDebug;
7
8
  exports.configureWorkerUrl = configureWorkerUrl;
8
9
  exports.computePivotInWorker = computePivotInWorker;
9
10
  exports.computePivotSynchronously = computePivotSynchronously;
@@ -12,6 +13,8 @@ exports.computePivotWithFallback = computePivotWithFallback;
12
13
  var _pivotCompute = require('./pivotCompute');
13
14
 
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 */
15
18
  /*
16
19
  * Main-thread client for the pivot Web Worker (src/pivotWorker.js).
17
20
  *
@@ -20,6 +23,11 @@ var DEFAULT_WORKER_URL = exports.DEFAULT_WORKER_URL = 'pivot-worker.js'; /* esli
20
23
  * - route progress/result/error messages to the matching request,
21
24
  * - transparently fall back to a synchronous main-thread computation when
22
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.
23
31
  */
24
32
 
25
33
 
@@ -34,8 +42,55 @@ var sharedWorkerBroken = false;
34
42
  var hasResultEver = false;
35
43
  var nextRequestId = 1;
36
44
  var lastStagedDataKey = null;
45
+ var workerDebug = false;
37
46
  var pendingRequests = new Map();
38
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
+
39
94
  var failAllPending = function failAllPending(error) {
40
95
  pendingRequests.forEach(function (entry) {
41
96
  clearTimeout(entry.timer);
@@ -62,11 +117,17 @@ function configureWorkerUrl(url) {
62
117
 
63
118
  var handleWorkerMessage = function handleWorkerMessage(event) {
64
119
  var message = event.data || {};
120
+ if (message.type === 'pivot:log') {
121
+ log('(in worker)', message.message, message.detail || '');
122
+ return;
123
+ }
65
124
  var entry = pendingRequests.get(message.id);
66
125
  if (!entry) {
67
126
  return;
68
127
  }
69
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 + '%)'));
70
131
  if (entry.onProgress) {
71
132
  entry.onProgress(message.processed, message.total, message.phase);
72
133
  }
@@ -76,30 +137,51 @@ var handleWorkerMessage = function handleWorkerMessage(event) {
76
137
  pendingRequests.delete(message.id);
77
138
  if (message.type === 'pivot:result') {
78
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
+ });
79
147
  entry.resolve(message.result);
80
148
  } else if (message.type === 'pivot:error') {
149
+ log('#' + message.id + ' worker reported an error:', message.message);
81
150
  entry.reject(new Error(message.message || 'Pivot worker error'));
82
151
  }
83
152
  };
84
153
 
85
154
  var ensureWorker = function ensureWorker() {
86
- if (sharedWorkerBroken || typeof Worker === 'undefined') {
155
+ if (sharedWorkerBroken) {
156
+ return null;
157
+ }
158
+
159
+ if (typeof Worker === 'undefined') {
160
+ log('no Worker support in this environment');
87
161
  return null;
88
162
  }
163
+
89
164
  if (!sharedWorker) {
90
165
  try {
91
166
  sharedWorker = new Worker(workerUrl);
167
+ log('worker created:', workerUrl);
92
168
  } catch (error) {
93
169
  sharedWorkerBroken = true;
170
+ log('worker creation failed:', error && error.message);
94
171
  return null;
95
172
  }
96
173
  sharedWorker.onmessage = handleWorkerMessage;
97
- sharedWorker.onerror = function () {
174
+ sharedWorker.onerror = function (event) {
175
+ log('worker error event (script failed to load / runtime error):', event && event.message || '');
98
176
  failAllPending(new Error('Pivot worker failed to load'));
99
177
  if (!hasResultEver) {
100
178
  sharedWorkerBroken = true;
179
+ log('worker marked unusable -> will use the synchronous fallback');
101
180
  }
102
181
  };
182
+ if (isDebugEnabled()) {
183
+ sharedWorker.postMessage({ type: 'pivot:debug', enabled: true });
184
+ }
103
185
  }
104
186
  return sharedWorker;
105
187
  };
@@ -114,16 +196,26 @@ var ensureWorker = function ensureWorker() {
114
196
  function computePivotInWorker(data, dataKey, config, onProgress) {
115
197
  var worker = ensureWorker();
116
198
  if (!worker) {
199
+ log('worker unavailable -> synchronous fallback');
117
200
  return Promise.reject(new Error('Web worker unavailable'));
118
201
  }
119
202
  var id = nextRequestId++;
120
203
  return new Promise(function (resolve, reject) {
121
204
  var timer = setTimeout(function () {
122
205
  pendingRequests.delete(id);
206
+ log('#' + id + ' timed out after ' + REQUEST_TIMEOUT_MS + 'ms');
123
207
  reject(new Error('Pivot worker timed out'));
124
208
  }, REQUEST_TIMEOUT_MS);
125
- pendingRequests.set(id, { resolve: resolve, reject: reject, onProgress: onProgress, timer: timer });
126
- if (lastStagedDataKey !== dataKey) {
209
+ pendingRequests.set(id, {
210
+ resolve: resolve,
211
+ reject: reject,
212
+ onProgress: onProgress,
213
+ timer: timer,
214
+ startedAt: Date.now()
215
+ });
216
+ var mustStage = lastStagedDataKey !== dataKey;
217
+ log('#' + id + ' dispatching to worker (dataKey=' + dataKey + (mustStage ? ', staging ' + (data || []).length + ' records' : ', cached dataset') + ', ' + describeConfig(config) + ')');
218
+ if (mustStage) {
127
219
  worker.postMessage({ type: 'pivot:setData', id: id, dataKey: dataKey, data: data });
128
220
  lastStagedDataKey = dataKey;
129
221
  }
@@ -139,12 +231,21 @@ function computePivotInWorker(data, dataKey, config, onProgress) {
139
231
  function computePivotSynchronously(data, config, onProgress) {
140
232
  return new Promise(function (resolve, reject) {
141
233
  setTimeout(function () {
234
+ var startedAt = Date.now();
235
+ log('running synchronous computation on the main thread...');
142
236
  try {
143
237
  var materialized = (0, _pivotCompute.materializeData)(data, {}, function (processed, total) {
144
238
  onProgress(processed, total, 'materialize');
145
239
  });
146
- resolve((0, _pivotCompute.computePivotState)(materialized, config, onProgress));
240
+ var result = (0, _pivotCompute.computePivotState)(materialized, config, onProgress);
241
+ log('synchronous computation finished in ' + (Date.now() - startedAt) + 'ms', {
242
+ totalRecords: result.totalRecords,
243
+ rowKeys: result.rowKeys.length,
244
+ colKeys: result.colKeys.length
245
+ });
246
+ resolve(result);
147
247
  } catch (error) {
248
+ log('synchronous computation failed:', error && error.message);
148
249
  reject(error);
149
250
  }
150
251
  }, 0);
@@ -157,7 +258,8 @@ function computePivotSynchronously(data, config, onProgress) {
157
258
  * either way.
158
259
  */
159
260
  function computePivotWithFallback(data, dataKey, config, onProgress) {
160
- return computePivotInWorker(data, dataKey, config, onProgress).catch(function () {
261
+ return computePivotInWorker(data, dataKey, config, onProgress).catch(function (error) {
262
+ log('worker request failed, falling back to the main thread:', error && error.message || error);
161
263
  return computePivotSynchronously(data, config, onProgress);
162
264
  });
163
265
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/PivotDataWorker.js"],"names":["configureWorkerUrl","computePivotInWorker","computePivotSynchronously","computePivotWithFallback","DEFAULT_WORKER_URL","REQUEST_TIMEOUT_MS","workerUrl","sharedWorker","sharedWorkerBroken","hasResultEver","nextRequestId","lastStagedDataKey","pendingRequests","Map","failAllPending","forEach","clearTimeout","entry","timer","reject","error","clear","url","Error","terminate","handleWorkerMessage","message","event","data","get","id","type","onProgress","processed","total","phase","delete","resolve","result","ensureWorker","Worker","onmessage","onerror","dataKey","config","worker","Promise","setTimeout","set","postMessage","materialized","catch"],"mappings":"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","file":"PivotDataWorker.js","sourcesContent":["/* eslint-disable valid-jsdoc */\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 */\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;\nconst pendingRequests = new Map();\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 const entry = pendingRequests.get(message.id);\n if (!entry) {\n return;\n }\n if (message.type === 'pivot:progress') {\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 entry.resolve(message.result);\n } else if (message.type === 'pivot:error') {\n entry.reject(new Error(message.message || 'Pivot worker error'));\n }\n};\n\nconst ensureWorker = () => {\n if (sharedWorkerBroken || typeof Worker === 'undefined') {\n return null;\n }\n if (!sharedWorker) {\n try {\n sharedWorker = new Worker(workerUrl);\n } catch (error) {\n sharedWorkerBroken = true;\n return null;\n }\n sharedWorker.onmessage = handleWorkerMessage;\n sharedWorker.onerror = () => {\n failAllPending(new Error('Pivot worker failed to load'));\n if (!hasResultEver) {\n sharedWorkerBroken = true;\n }\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 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 reject(new Error('Pivot worker timed out'));\n }, REQUEST_TIMEOUT_MS);\n pendingRequests.set(id, {resolve, reject, onProgress, timer});\n if (lastStagedDataKey !== dataKey) {\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 try {\n const materialized = materializeData(data, {}, (processed, total) => {\n onProgress(processed, total, 'materialize');\n });\n resolve(computePivotState(materialized, config, onProgress));\n } catch (error) {\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 computePivotSynchronously(data, config, onProgress)\n );\n}\n"]}
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\n if (typeof Worker === 'undefined') {\n log('no Worker support in this environment');\n return null;\n }\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"]}
@@ -1231,7 +1231,7 @@ function PivotTableUI(props) {
1231
1231
  enabled: props.enableWorker,
1232
1232
  threshold: props.workerThreshold
1233
1233
  });
1234
- }, [props.data, props.enableWorker, props.workerThreshold, props.derivedAttributes, props.aggregators]);
1234
+ }, [props.data, props.enableWorker, props.workerThreshold]);
1235
1235
 
1236
1236
  var computeSignature = (0, _react.useMemo)(function () {
1237
1237
  return (0, _pivotCompute.createComputeSignature)(props);
@@ -1266,6 +1266,12 @@ function PivotTableUI(props) {
1266
1266
  };
1267
1267
  }, []);
1268
1268
 
1269
+ // Forward the `workerDebug` prop to the worker client so the whole
1270
+ // worker pipeline can be traced from the browser console.
1271
+ (0, _react.useEffect)(function () {
1272
+ (0, _PivotDataWorker.setWorkerDebug)(props.workerDebug);
1273
+ }, [props.workerDebug]);
1274
+
1269
1275
  // Snapshot of the last consistently rendered pivot view: the props that
1270
1276
  // match the currently applied precomputed result. While a new computation
1271
1277
  // runs, this snapshot is rendered underneath the loading overlay so the
@@ -1294,19 +1300,23 @@ function PivotTableUI(props) {
1294
1300
  setIsProcessing(false);
1295
1301
  return undefined;
1296
1302
  }
1303
+
1297
1304
  if (versionedDataRef.current !== props.data) {
1298
1305
  versionedDataRef.current = props.data;
1299
1306
  dataVersionRef.current += 1;
1300
1307
  }
1308
+
1301
1309
  var signature = computeSignature + '#' + dataVersionRef.current;
1302
1310
  if (precomputedRef.current && precomputedRef.current.signature === signature) {
1303
1311
  setIsProcessing(false);
1304
1312
  return undefined;
1305
1313
  }
1314
+
1306
1315
  (0, _PivotDataWorker.configureWorkerUrl)(props.workerUrl || _PivotDataWorker.DEFAULT_WORKER_URL);
1307
1316
  setIsProcessing(true);
1308
1317
  setComputeError(null);
1309
1318
  setProgress({ processed: 0, total: props.data.length, phase: 'queued' });
1319
+
1310
1320
  var timer = setTimeout(function () {
1311
1321
  var requestId = activeRequestRef.current;
1312
1322
  var dataKey = instanceIdRef.current + '-data-' + dataVersionRef.current;
@@ -1874,36 +1884,40 @@ function PivotTableUI(props) {
1874
1884
  var outputCell = _react2.default.createElement(
1875
1885
  'td',
1876
1886
  { className: 'pvtOutput' },
1877
- outputContent,
1878
- showProcessing ? _react2.default.createElement(PivotLoadingOverlay, { progress: overlayProgress }) : null
1887
+ outputContent
1879
1888
  );
1880
1889
 
1881
1890
  if (horizUnused) {
1882
1891
  return _react2.default.createElement(
1883
- 'table',
1884
- { className: 'pvtUi' },
1892
+ _react2.default.Fragment,
1893
+ null,
1894
+ showProcessing ? _react2.default.createElement(PivotLoadingOverlay, { progress: overlayProgress }) : null,
1885
1895
  _react2.default.createElement(
1886
- 'tbody',
1887
- { onClick: function onClick() {
1888
- return setOpenDropdown(false);
1889
- } },
1896
+ 'table',
1897
+ { className: 'pvtUi' },
1890
1898
  _react2.default.createElement(
1891
- 'tr',
1892
- null,
1893
- rendererCell,
1894
- unusedAttrsCell
1895
- ),
1896
- _react2.default.createElement(
1897
- 'tr',
1898
- null,
1899
- aggregatorCell,
1900
- colAttrsCell
1901
- ),
1902
- _react2.default.createElement(
1903
- 'tr',
1904
- null,
1905
- rowAttrsCell,
1906
- outputCell
1899
+ 'tbody',
1900
+ { onClick: function onClick() {
1901
+ return setOpenDropdown(false);
1902
+ } },
1903
+ _react2.default.createElement(
1904
+ 'tr',
1905
+ null,
1906
+ rendererCell,
1907
+ unusedAttrsCell
1908
+ ),
1909
+ _react2.default.createElement(
1910
+ 'tr',
1911
+ null,
1912
+ aggregatorCell,
1913
+ colAttrsCell
1914
+ ),
1915
+ _react2.default.createElement(
1916
+ 'tr',
1917
+ null,
1918
+ rowAttrsCell,
1919
+ outputCell
1920
+ )
1907
1921
  )
1908
1922
  )
1909
1923
  );
@@ -1947,6 +1961,7 @@ PivotTableUI.propTypes = Object.assign({}, _PivotTable2.default.propTypes, {
1947
1961
  enableWorker: _propTypes2.default.bool,
1948
1962
  workerThreshold: _propTypes2.default.number,
1949
1963
  workerUrl: _propTypes2.default.string,
1964
+ workerDebug: _propTypes2.default.bool,
1950
1965
  isShowProcessing: _propTypes2.default.bool
1951
1966
  });
1952
1967
 
@@ -1961,6 +1976,7 @@ PivotTableUI.defaultProps = Object.assign({}, _PivotTable2.default.defaultProps,
1961
1976
  enableWorker: true,
1962
1977
  workerThreshold: _pivotCompute.DEFAULT_WORKER_THRESHOLD,
1963
1978
  workerUrl: _PivotDataWorker.DEFAULT_WORKER_URL,
1979
+ workerDebug: false,
1964
1980
  isShowProcessing: false
1965
1981
  });
1966
1982