@hrforte/react-pivottable 1.0.1 → 2.0.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 CHANGED
@@ -0,0 +1,45 @@
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
+ - 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).
33
+
34
+ Available props:
35
+
36
+ - `enableWorker` (`bool`, default `true`): disable to always compute on the
37
+ 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.
41
+ - `isShowProcessing` (`bool`, default `false`): testing/debugging hook. When
42
+ `true` the loading overlay is forced on top of the table even if nothing is
43
+ being computed, so the `PivotLoadingOverlay` can be inspected without
44
+ generating a large dataset. A sample progress value is supplied so the
45
+ progress bar and percentage are rendered as well.
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DEFAULT_WORKER_URL = undefined;
7
+ exports.configureWorkerUrl = configureWorkerUrl;
8
+ exports.computePivotInWorker = computePivotInWorker;
9
+ exports.computePivotSynchronously = computePivotSynchronously;
10
+ exports.computePivotWithFallback = computePivotWithFallback;
11
+
12
+ var _pivotCompute = require('./pivotCompute');
13
+
14
+ var DEFAULT_WORKER_URL = exports.DEFAULT_WORKER_URL = 'pivot-worker.js'; /* eslint-disable valid-jsdoc */
15
+ /*
16
+ * Main-thread client for the pivot Web Worker (src/pivotWorker.js).
17
+ *
18
+ * Responsibilities:
19
+ * - lazily create a single shared Worker (at `workerUrl`, overrideable),
20
+ * - route progress/result/error messages to the matching request,
21
+ * - transparently fall back to a synchronous main-thread computation when
22
+ * workers are unavailable or the worker bundle fails to load.
23
+ */
24
+
25
+
26
+ var REQUEST_TIMEOUT_MS = 300000;
27
+
28
+ var workerUrl = DEFAULT_WORKER_URL;
29
+ var sharedWorker = null;
30
+ // The worker is marked broken only if it fails before ever producing a
31
+ // result (e.g. a 404 worker script); runtime hiccups later on just reject
32
+ // the affected request and fall back once.
33
+ var sharedWorkerBroken = false;
34
+ var hasResultEver = false;
35
+ var nextRequestId = 1;
36
+ var lastStagedDataKey = null;
37
+ var pendingRequests = new Map();
38
+
39
+ var failAllPending = function failAllPending(error) {
40
+ pendingRequests.forEach(function (entry) {
41
+ clearTimeout(entry.timer);
42
+ entry.reject(error);
43
+ });
44
+ pendingRequests.clear();
45
+ };
46
+
47
+ function configureWorkerUrl(url) {
48
+ if (url && url !== workerUrl) {
49
+ workerUrl = url;
50
+ if (sharedWorker) {
51
+ // Reject in-flight requests first so callers fall back to the
52
+ // synchronous path instead of waiting for the request timeout.
53
+ failAllPending(new Error('Pivot worker was reconfigured'));
54
+ sharedWorker.terminate();
55
+ sharedWorker = null;
56
+ sharedWorkerBroken = false;
57
+ hasResultEver = false;
58
+ lastStagedDataKey = null;
59
+ }
60
+ }
61
+ }
62
+
63
+ var handleWorkerMessage = function handleWorkerMessage(event) {
64
+ var message = event.data || {};
65
+ var entry = pendingRequests.get(message.id);
66
+ if (!entry) {
67
+ return;
68
+ }
69
+ if (message.type === 'pivot:progress') {
70
+ if (entry.onProgress) {
71
+ entry.onProgress(message.processed, message.total, message.phase);
72
+ }
73
+ return;
74
+ }
75
+ clearTimeout(entry.timer);
76
+ pendingRequests.delete(message.id);
77
+ if (message.type === 'pivot:result') {
78
+ hasResultEver = true;
79
+ entry.resolve(message.result);
80
+ } else if (message.type === 'pivot:error') {
81
+ entry.reject(new Error(message.message || 'Pivot worker error'));
82
+ }
83
+ };
84
+
85
+ var ensureWorker = function ensureWorker() {
86
+ if (sharedWorkerBroken || typeof Worker === 'undefined') {
87
+ return null;
88
+ }
89
+ if (!sharedWorker) {
90
+ try {
91
+ sharedWorker = new Worker(workerUrl);
92
+ } catch (error) {
93
+ sharedWorkerBroken = true;
94
+ return null;
95
+ }
96
+ sharedWorker.onmessage = handleWorkerMessage;
97
+ sharedWorker.onerror = function () {
98
+ failAllPending(new Error('Pivot worker failed to load'));
99
+ if (!hasResultEver) {
100
+ sharedWorkerBroken = true;
101
+ }
102
+ };
103
+ }
104
+ return sharedWorker;
105
+ };
106
+
107
+ /*
108
+ * Runs a full computation inside the shared worker.
109
+ *
110
+ * `dataKey` must change whenever the underlying dataset changes. It lets the
111
+ * worker reuse the materialized dataset across consecutive computations and
112
+ * lets the client skip re-staging identical data.
113
+ */
114
+ function computePivotInWorker(data, dataKey, config, onProgress) {
115
+ var worker = ensureWorker();
116
+ if (!worker) {
117
+ return Promise.reject(new Error('Web worker unavailable'));
118
+ }
119
+ var id = nextRequestId++;
120
+ return new Promise(function (resolve, reject) {
121
+ var timer = setTimeout(function () {
122
+ pendingRequests.delete(id);
123
+ reject(new Error('Pivot worker timed out'));
124
+ }, REQUEST_TIMEOUT_MS);
125
+ pendingRequests.set(id, { resolve: resolve, reject: reject, onProgress: onProgress, timer: timer });
126
+ if (lastStagedDataKey !== dataKey) {
127
+ worker.postMessage({ type: 'pivot:setData', id: id, dataKey: dataKey, data: data });
128
+ lastStagedDataKey = dataKey;
129
+ }
130
+ worker.postMessage({ type: 'pivot:compute', id: id, dataKey: dataKey, config: config });
131
+ });
132
+ }
133
+
134
+ /*
135
+ * Same contract as the worker path, executed on the main thread. Used as the
136
+ * automatic fallback whenever the worker path is unavailable. Deferred by
137
+ * one tick so a loading indicator can paint before the blocking work starts.
138
+ */
139
+ function computePivotSynchronously(data, config, onProgress) {
140
+ return new Promise(function (resolve, reject) {
141
+ setTimeout(function () {
142
+ try {
143
+ var materialized = (0, _pivotCompute.materializeData)(data, {}, function (processed, total) {
144
+ onProgress(processed, total, 'materialize');
145
+ });
146
+ resolve((0, _pivotCompute.computePivotState)(materialized, config, onProgress));
147
+ } catch (error) {
148
+ reject(error);
149
+ }
150
+ }, 0);
151
+ });
152
+ }
153
+
154
+ /*
155
+ * Worker-first entry point with automatic synchronous fallback. The fallback
156
+ * produces the exact same serialized shape, so callers render identically
157
+ * either way.
158
+ */
159
+ function computePivotWithFallback(data, dataKey, config, onProgress) {
160
+ return computePivotInWorker(data, dataKey, config, onProgress).catch(function () {
161
+ return computePivotSynchronously(data, config, onProgress);
162
+ });
163
+ }
164
+ //# sourceMappingURL=PivotDataWorker.js.map
@@ -0,0 +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"]}