react-csv-autopilot 1.1.0 → 1.1.1-beta.3
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/dist/index.cjs +150 -6
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +150 -5
- package/dist/index.js.map +1 -0
- package/package.json +1 -1
- package/dist/worker.js +0 -114
package/dist/index.cjs
CHANGED
|
@@ -63,18 +63,156 @@ resolveStrategy_fn = function() {
|
|
|
63
63
|
var WEB_WORKER_NAME = "scv-worker";
|
|
64
64
|
var BROADCAST_CHANNEL_NAME = "react-csv-exporter";
|
|
65
65
|
|
|
66
|
+
// src/core/workerCode.ts
|
|
67
|
+
var workerCode = `
|
|
68
|
+
const headersWritten = new Map();
|
|
69
|
+
|
|
70
|
+
function getNested(obj, keyPath) {
|
|
71
|
+
return keyPath.split(".").reduce((acc, key) => {
|
|
72
|
+
if (acc && typeof acc === "object") {
|
|
73
|
+
return acc[key];
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}, obj);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalisedValue(value) {
|
|
80
|
+
let result = value;
|
|
81
|
+
|
|
82
|
+
if (typeof result === "string") {
|
|
83
|
+
result = '"' + result.replace(/"/g, '""') + '"';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (result === undefined || result === null) {
|
|
87
|
+
result = "";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function makeFormatters(locale = "en-US", timeZone = "UTC", currency = "USD") {
|
|
94
|
+
return {
|
|
95
|
+
dateFull: new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone }),
|
|
96
|
+
dateMediumTime: new Intl.DateTimeFormat(locale, {
|
|
97
|
+
dateStyle: "medium",
|
|
98
|
+
timeStyle: "short",
|
|
99
|
+
timeZone,
|
|
100
|
+
}),
|
|
101
|
+
numCompact: new Intl.NumberFormat(locale, {
|
|
102
|
+
maximumFractionDigits: 1,
|
|
103
|
+
notation: "compact",
|
|
104
|
+
}),
|
|
105
|
+
numCurrency: new Intl.NumberFormat(locale, {
|
|
106
|
+
currency,
|
|
107
|
+
maximumFractionDigits: 2,
|
|
108
|
+
style: "currency",
|
|
109
|
+
}),
|
|
110
|
+
numDecimal: new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }),
|
|
111
|
+
numPercent: new Intl.NumberFormat(locale, {
|
|
112
|
+
maximumFractionDigits: 1,
|
|
113
|
+
style: "percent",
|
|
114
|
+
}),
|
|
115
|
+
timeShort: new Intl.DateTimeFormat(locale, {
|
|
116
|
+
timeStyle: "short",
|
|
117
|
+
timeZone,
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getFormatter(col, value) {
|
|
123
|
+
const formatters = makeFormatters();
|
|
124
|
+
const availableFormatters = Object.keys(formatters);
|
|
125
|
+
|
|
126
|
+
if ("formatType" in col && availableFormatters.includes(col.formatType)) {
|
|
127
|
+
const enhance = formatters[col.formatType];
|
|
128
|
+
return enhance.format(Number(value));
|
|
129
|
+
} else {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function objectsToCSV(jsonArray, columns, includeHeaders) {
|
|
135
|
+
if (!jsonArray.length || !columns.length) return "";
|
|
136
|
+
|
|
137
|
+
const rows = [];
|
|
138
|
+
|
|
139
|
+
if (includeHeaders) {
|
|
140
|
+
rows.push(columns.map((col) => col.label).join(","));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
jsonArray.forEach((row) => {
|
|
144
|
+
rows.push(
|
|
145
|
+
columns
|
|
146
|
+
.map((col) => {
|
|
147
|
+
const val = getNested(row, col.key);
|
|
148
|
+
const normalizedValue = normalisedValue(val);
|
|
149
|
+
const maybeFormattedValue = getFormatter(col, normalizedValue);
|
|
150
|
+
return maybeFormattedValue;
|
|
151
|
+
})
|
|
152
|
+
.join(",")
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return rows.join("\\n") + "\\n";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
self.onmessage = (event) => {
|
|
160
|
+
const msg = event.data;
|
|
161
|
+
try {
|
|
162
|
+
switch (msg.type) {
|
|
163
|
+
case "to_csv_chunk": {
|
|
164
|
+
const { columns, data, id } = msg;
|
|
165
|
+
const csvChunk = objectsToCSV(data, columns, !headersWritten.get(id));
|
|
166
|
+
const out = {
|
|
167
|
+
id,
|
|
168
|
+
result: csvChunk,
|
|
169
|
+
type: "csv_chunk",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
headersWritten.set(id, true);
|
|
173
|
+
|
|
174
|
+
self.postMessage(out);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case "completed": {
|
|
179
|
+
const out = { id: msg.id, type: "done" };
|
|
180
|
+
self.postMessage(out);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
default: {
|
|
185
|
+
console.warn("Unsupported worker message: " + JSON.stringify(msg));
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
const _error = error instanceof Error ? error : new Error(String(error));
|
|
191
|
+
|
|
192
|
+
self.postMessage({
|
|
193
|
+
error: { name: _error.name, message: _error.message, stack: _error.stack },
|
|
194
|
+
id: msg.id,
|
|
195
|
+
type: "error",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
`;
|
|
200
|
+
|
|
66
201
|
// src/core/WorkerManager.ts
|
|
67
|
-
var import_meta = {};
|
|
68
202
|
var pending = /* @__PURE__ */ new Map();
|
|
69
|
-
|
|
203
|
+
function createWorkerBlobUrl() {
|
|
204
|
+
const blob = new Blob([workerCode], { type: "application/javascript" });
|
|
205
|
+
return URL.createObjectURL(blob);
|
|
206
|
+
}
|
|
207
|
+
var _worker, _blobUrl, _WorkerManager_instances, listenerRegistry_fn;
|
|
70
208
|
var _WorkerManager = class _WorkerManager {
|
|
71
209
|
constructor() {
|
|
72
210
|
__privateAdd(this, _WorkerManager_instances);
|
|
73
211
|
__privateAdd(this, _worker);
|
|
74
|
-
|
|
75
|
-
__privateSet(this,
|
|
76
|
-
|
|
77
|
-
|
|
212
|
+
__privateAdd(this, _blobUrl);
|
|
213
|
+
__privateSet(this, _blobUrl, createWorkerBlobUrl());
|
|
214
|
+
__privateSet(this, _worker, new Worker(__privateGet(this, _blobUrl), {
|
|
215
|
+
name: WEB_WORKER_NAME
|
|
78
216
|
}));
|
|
79
217
|
__privateMethod(this, _WorkerManager_instances, listenerRegistry_fn).call(this);
|
|
80
218
|
}
|
|
@@ -94,9 +232,14 @@ var _WorkerManager = class _WorkerManager {
|
|
|
94
232
|
__privateGet(this, _worker).terminate();
|
|
95
233
|
__privateSet(this, _worker, null);
|
|
96
234
|
}
|
|
235
|
+
if (__privateGet(this, _blobUrl)) {
|
|
236
|
+
URL.revokeObjectURL(__privateGet(this, _blobUrl));
|
|
237
|
+
__privateSet(this, _blobUrl, null);
|
|
238
|
+
}
|
|
97
239
|
}
|
|
98
240
|
};
|
|
99
241
|
_worker = new WeakMap();
|
|
242
|
+
_blobUrl = new WeakMap();
|
|
100
243
|
_WorkerManager_instances = new WeakSet();
|
|
101
244
|
listenerRegistry_fn = function() {
|
|
102
245
|
__privateGet(this, _worker)?.addEventListener("message", (event) => {
|
|
@@ -378,3 +521,4 @@ function useMessageExportCSV(cb) {
|
|
|
378
521
|
}, [cb]);
|
|
379
522
|
}
|
|
380
523
|
var useMessageExportCSV_default = useMessageExportCSV;
|
|
524
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/hooks/useExportCSV.ts","../src/core/controllers/ExportController.ts","../src/core/contants/index.ts","../src/core/workerCode.ts","../src/core/WorkerManager.ts","../src/core/strategy/BolbExportStrategy.ts","../src/core/strategy/FsAccessExportStrategy.ts","../src/core/createExportController.ts","../src/core/ExportControllerSingleton.ts","../src/hooks/useMessageExportCSV.ts"],"sourcesContent":["export type { ExportController } from \"./core/controllers/ExportController\";\nexport { Column, ExportParams } from \"./core/types\";\nexport { useExportCSV, useMessageExportCSV } from \"./hooks\";\n","import { useRef } from \"react\";\nimport { type ExportController, ExportControllerSingleton } from \"../core\";\n\nfunction useExportCSV() {\n const exportCallbackRef = useRef<ExportController>(ExportControllerSingleton.init());\n\n return {\n handler: exportCallbackRef?.current,\n };\n}\n\nexport default useExportCSV;\n","import type BolbExportStrategy from \"../strategy/BolbExportStrategy\";\nimport type FsAccessExportStrategy from \"../strategy/FsAccessExportStrategy\";\nimport type { ExportParams, ExportResponse, ExportStrategy } from \"../types\";\n\ntype ExportControllerDeps = {\n fsAccessStrategy: FsAccessExportStrategy;\n blobExportStrategy: BolbExportStrategy;\n};\n\nexport class ExportController {\n constructor(private readonly deps: ExportControllerDeps) {}\n\n public async start<T>(params: ExportParams<T>): Promise<ExportResponse> {\n const strategy = this.#resolveStrategy();\n\n return strategy.export(params);\n }\n\n #canUseFSAccess() {\n return typeof window.showSaveFilePicker === \"function\";\n }\n\n #resolveStrategy(): ExportStrategy {\n if (this.#canUseFSAccess()) {\n return this.deps.fsAccessStrategy;\n }\n\n return this.deps.blobExportStrategy;\n }\n}\n","export const WEB_WORKER_NAME = \"scv-worker\";\nexport const BROADCAST_CHANNEL_NAME = \"react-csv-exporter\";\n","export const workerCode = `\nconst headersWritten = new Map();\n\nfunction getNested(obj, keyPath) {\n return keyPath.split(\".\").reduce((acc, key) => {\n if (acc && typeof acc === \"object\") {\n return acc[key];\n }\n return undefined;\n }, obj);\n}\n\nfunction normalisedValue(value) {\n let result = value;\n\n if (typeof result === \"string\") {\n result = '\"' + result.replace(/\"/g, '\"\"') + '\"';\n }\n\n if (result === undefined || result === null) {\n result = \"\";\n }\n\n return result;\n}\n\nfunction makeFormatters(locale = \"en-US\", timeZone = \"UTC\", currency = \"USD\") {\n return {\n dateFull: new Intl.DateTimeFormat(locale, { dateStyle: \"full\", timeZone }),\n dateMediumTime: new Intl.DateTimeFormat(locale, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n timeZone,\n }),\n numCompact: new Intl.NumberFormat(locale, {\n maximumFractionDigits: 1,\n notation: \"compact\",\n }),\n numCurrency: new Intl.NumberFormat(locale, {\n currency,\n maximumFractionDigits: 2,\n style: \"currency\",\n }),\n numDecimal: new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }),\n numPercent: new Intl.NumberFormat(locale, {\n maximumFractionDigits: 1,\n style: \"percent\",\n }),\n timeShort: new Intl.DateTimeFormat(locale, {\n timeStyle: \"short\",\n timeZone,\n }),\n };\n}\n\nfunction getFormatter(col, value) {\n const formatters = makeFormatters();\n const availableFormatters = Object.keys(formatters);\n\n if (\"formatType\" in col && availableFormatters.includes(col.formatType)) {\n const enhance = formatters[col.formatType];\n return enhance.format(Number(value));\n } else {\n return value;\n }\n}\n\nfunction objectsToCSV(jsonArray, columns, includeHeaders) {\n if (!jsonArray.length || !columns.length) return \"\";\n\n const rows = [];\n\n if (includeHeaders) {\n rows.push(columns.map((col) => col.label).join(\",\"));\n }\n\n jsonArray.forEach((row) => {\n rows.push(\n columns\n .map((col) => {\n const val = getNested(row, col.key);\n const normalizedValue = normalisedValue(val);\n const maybeFormattedValue = getFormatter(col, normalizedValue);\n return maybeFormattedValue;\n })\n .join(\",\")\n );\n });\n\n return rows.join(\"\\\\n\") + \"\\\\n\";\n}\n\nself.onmessage = (event) => {\n const msg = event.data;\n try {\n switch (msg.type) {\n case \"to_csv_chunk\": {\n const { columns, data, id } = msg;\n const csvChunk = objectsToCSV(data, columns, !headersWritten.get(id));\n const out = {\n id,\n result: csvChunk,\n type: \"csv_chunk\",\n };\n\n headersWritten.set(id, true);\n\n self.postMessage(out);\n break;\n }\n\n case \"completed\": {\n const out = { id: msg.id, type: \"done\" };\n self.postMessage(out);\n break;\n }\n\n default: {\n console.warn(\"Unsupported worker message: \" + JSON.stringify(msg));\n break;\n }\n }\n } catch (error) {\n const _error = error instanceof Error ? error : new Error(String(error));\n\n self.postMessage({\n error: { name: _error.name, message: _error.message, stack: _error.stack },\n id: msg.id,\n type: \"error\",\n });\n }\n};\n`;\n","import { WEB_WORKER_NAME } from \"./contants\";\nimport type { JobId, ToWorkerMessage } from \"./types\";\nimport { workerCode } from \"./workerCode\";\n\nconst pending = new Map<JobId, { resolve: (value: unknown) => void; reject: (reason?: ErrorEvent) => void }>();\n\nfunction createWorkerBlobUrl(): string {\n const blob = new Blob([workerCode], { type: \"application/javascript\" });\n return URL.createObjectURL(blob);\n}\n\nclass WorkerManager {\n #worker: Worker | null;\n #blobUrl: string | null;\n\n constructor() {\n this.#blobUrl = createWorkerBlobUrl();\n\n this.#worker = new Worker(this.#blobUrl, {\n name: WEB_WORKER_NAME,\n });\n\n this.#listenerRegistry();\n }\n\n static initialise() {\n return new WorkerManager();\n }\n\n #listenerRegistry() {\n this.#worker?.addEventListener(\"message\", (event) => {\n const { id, result, error } = event.data;\n const entity = pending.get(id);\n\n if (!entity) {\n return;\n }\n\n pending.delete(id);\n\n if (error) {\n entity.reject(error);\n } else {\n entity.resolve(result);\n }\n });\n\n this.#worker?.addEventListener(\"error\", (event) => {\n for (const [, { reject }] of pending) {\n reject(event);\n }\n\n pending.clear();\n });\n }\n\n async triggerWorker(payload: ToWorkerMessage) {\n const id = payload.id ?? Math.random().toString(36).substr(2);\n\n const p = new Promise((resolve, reject) => {\n pending.set(id, { reject, resolve });\n });\n\n this.#worker?.postMessage(payload);\n\n return p;\n }\n\n terminate() {\n if (this.#worker) {\n this.#worker.terminate();\n this.#worker = null;\n }\n\n if (this.#blobUrl) {\n URL.revokeObjectURL(this.#blobUrl);\n this.#blobUrl = null;\n }\n }\n}\n\nexport default WorkerManager;\n","import { BROADCAST_CHANNEL_NAME } from \"../contants\";\nimport type { ExportParams, ExportResponse, ExportStrategy, JobId } from \"../types\";\nimport WorkerManager from \"../WorkerManager\";\n\nclass BolbExportStrategy implements ExportStrategy {\n private workerManager: WorkerManager;\n\n constructor() {\n this.workerManager = WorkerManager.initialise();\n }\n\n async export<T>(params: ExportParams<T>): Promise<ExportResponse> {\n const suggestedName = params.fileName ?? \"export\";\n const filename = suggestedName.endsWith(\".csv\") ? suggestedName : `${suggestedName}.csv`;\n\n let iterator: JobId = 0 as JobId;\n let totalRowsLoaded = 0;\n\n const messaging = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n\n const csvParts: BlobPart[] = [];\n\n try {\n while (true) {\n const response = await params.getNextPage(iterator++);\n\n const safeRows = Array.isArray(response.rows) ? response.rows : [];\n const safeTotal = response.total ?? 0;\n\n const isRowsEmpty = safeRows.length === 0;\n\n const nextRowsLoaded = totalRowsLoaded + safeRows.length;\n totalRowsLoaded = isRowsEmpty ? safeTotal : nextRowsLoaded;\n\n const isFinished = safeTotal > 0 ? totalRowsLoaded >= safeTotal : isRowsEmpty;\n\n if (isRowsEmpty) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n break;\n }\n\n const csvChunk = (await this.workerManager.triggerWorker({\n columns: params.columns,\n data: safeRows as Record<string, unknown>[],\n id: iterator,\n type: \"to_csv_chunk\",\n })) as string;\n\n csvParts.push(csvChunk);\n\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"progress\",\n }),\n );\n\n if (isFinished) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n break;\n }\n }\n\n const blob = new Blob([\"\\uFEFF\", ...csvParts], {\n type: \"text/csv;charset=utf-8\",\n });\n\n this.downloadBlob(blob, filename);\n\n return { finished: true, totalRowsLoaded };\n } catch (error) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: 0,\n type: \"failed\",\n }),\n );\n throw error;\n } finally {\n messaging.close();\n this.workerManager.terminate();\n }\n }\n\n private downloadBlob(blob: Blob, filename: string) {\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.rel = \"noopener\";\n\n document.body.appendChild(a);\n a.click();\n a.remove();\n\n URL.revokeObjectURL(url);\n }\n}\n\nexport default BolbExportStrategy;\n","import { BROADCAST_CHANNEL_NAME } from \"../contants\";\nimport type { ExportParams, ExportResponse, ExportStrategy, JobId } from \"../types\";\nimport WorkerManager from \"../WorkerManager\";\n\nclass FsAccessExportStrategy implements ExportStrategy {\n private workerManager: WorkerManager;\n\n constructor() {\n this.workerManager = WorkerManager.initialise();\n }\n\n async export<T>(params: ExportParams<T>): Promise<ExportResponse> {\n const _suggestedName = params?.fileName || \"export\";\n\n const fileHandle = await window.showSaveFilePicker({\n suggestedName: _suggestedName,\n types: [{ accept: { \"text/csv\": [\".csv\"] }, description: \"CSV file\" }],\n });\n\n const writableFileStream = await fileHandle.createWritable();\n let iterator: JobId = 0 as JobId;\n let totalRowsLoaded = 0;\n\n const encoder = new TextEncoder();\n const messaging = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n\n const readable = new ReadableStream({\n pull: async (controller) => {\n try {\n const response = await params.getNextPage(iterator++);\n\n const safeRows = Array.isArray(response.rows) ? response?.rows : [];\n const safeTotal = response.total ?? 0;\n\n const isRowsEmpty = !safeRows || !safeRows.length;\n const nextRowsLoaded = totalRowsLoaded + safeRows.length;\n totalRowsLoaded = isRowsEmpty ? safeTotal : nextRowsLoaded;\n const isFinished = totalRowsLoaded >= safeTotal;\n\n if (isRowsEmpty) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n messaging.close();\n controller.close();\n\n return;\n }\n\n const csvChunks = await this.workerManager.triggerWorker({\n columns: params.columns,\n data: safeRows as Record<string, unknown>[],\n id: iterator,\n type: \"to_csv_chunk\",\n });\n\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"progress\",\n }),\n );\n\n controller.enqueue(encoder.encode(csvChunks as string));\n\n if (isFinished) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n messaging.close();\n controller.close();\n\n return;\n }\n } catch (error) {\n controller.error(error);\n\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: 0,\n type: \"failed\",\n }),\n );\n }\n },\n });\n\n try {\n await readable.pipeTo(writableFileStream);\n } catch (err) {\n console.error(\"Export failed:\", err);\n throw err;\n } finally {\n this.workerManager.terminate();\n }\n\n return {\n finished: true,\n totalRowsLoaded,\n };\n }\n}\n\nexport default FsAccessExportStrategy;\n","import { ExportController } from \"./controllers/ExportController\";\nimport BolbExportStrategy from \"./strategy/BolbExportStrategy\";\nimport FsAccessExportStrategy from \"./strategy/FsAccessExportStrategy\";\n\nfunction createExportController(): ExportController {\n return new ExportController({\n blobExportStrategy: new BolbExportStrategy(),\n fsAccessStrategy: new FsAccessExportStrategy(),\n });\n}\n\nexport default createExportController;\n","import type { ExportController } from \"./controllers/ExportController\";\nimport createExportController from \"./createExportController\";\n\n// biome-ignore lint/complexity/noStaticOnlyClass: Note(Pavlo) Prefer to keep as class\nclass ExportControllerSingleton {\n static instance: ExportController | null = null;\n static initialized: boolean = false;\n\n static init() {\n if (ExportControllerSingleton.instance) {\n return ExportControllerSingleton.instance;\n }\n\n ExportControllerSingleton.instance = createExportController();\n ExportControllerSingleton.initialized = true;\n\n return ExportControllerSingleton.instance;\n }\n\n static getInstance(): ExportController {\n if (!ExportControllerSingleton.instance) {\n return ExportControllerSingleton.init();\n }\n\n return ExportControllerSingleton.instance;\n }\n}\n\nexport default ExportControllerSingleton;\n","import { useEffect } from \"react\";\nimport { BROADCAST_CHANNEL_NAME } from \"../core/contants\";\n\ntype Payload = {\n total: number;\n loadedItemsCount: number;\n state: \"progress\" | \"failed\" | \"done\";\n};\n\nfunction useMessageExportCSV(cb: (payload: Payload) => void) {\n useEffect(() => {\n const channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n\n channel.addEventListener(\"message\", (params: MessageEvent) => {\n try {\n const json = JSON.parse(params.data) as Payload;\n\n cb(json);\n } catch (error) {\n console.error({ error });\n }\n });\n\n return () => {\n channel.close();\n };\n }, [cb]);\n}\n\nexport default useMessageExportCSV;\n"],"mappings":"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","names":["import_react"]}
|
package/dist/index.js
CHANGED
|
@@ -37,17 +37,156 @@ resolveStrategy_fn = function() {
|
|
|
37
37
|
var WEB_WORKER_NAME = "scv-worker";
|
|
38
38
|
var BROADCAST_CHANNEL_NAME = "react-csv-exporter";
|
|
39
39
|
|
|
40
|
+
// src/core/workerCode.ts
|
|
41
|
+
var workerCode = `
|
|
42
|
+
const headersWritten = new Map();
|
|
43
|
+
|
|
44
|
+
function getNested(obj, keyPath) {
|
|
45
|
+
return keyPath.split(".").reduce((acc, key) => {
|
|
46
|
+
if (acc && typeof acc === "object") {
|
|
47
|
+
return acc[key];
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}, obj);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalisedValue(value) {
|
|
54
|
+
let result = value;
|
|
55
|
+
|
|
56
|
+
if (typeof result === "string") {
|
|
57
|
+
result = '"' + result.replace(/"/g, '""') + '"';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result === undefined || result === null) {
|
|
61
|
+
result = "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function makeFormatters(locale = "en-US", timeZone = "UTC", currency = "USD") {
|
|
68
|
+
return {
|
|
69
|
+
dateFull: new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone }),
|
|
70
|
+
dateMediumTime: new Intl.DateTimeFormat(locale, {
|
|
71
|
+
dateStyle: "medium",
|
|
72
|
+
timeStyle: "short",
|
|
73
|
+
timeZone,
|
|
74
|
+
}),
|
|
75
|
+
numCompact: new Intl.NumberFormat(locale, {
|
|
76
|
+
maximumFractionDigits: 1,
|
|
77
|
+
notation: "compact",
|
|
78
|
+
}),
|
|
79
|
+
numCurrency: new Intl.NumberFormat(locale, {
|
|
80
|
+
currency,
|
|
81
|
+
maximumFractionDigits: 2,
|
|
82
|
+
style: "currency",
|
|
83
|
+
}),
|
|
84
|
+
numDecimal: new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }),
|
|
85
|
+
numPercent: new Intl.NumberFormat(locale, {
|
|
86
|
+
maximumFractionDigits: 1,
|
|
87
|
+
style: "percent",
|
|
88
|
+
}),
|
|
89
|
+
timeShort: new Intl.DateTimeFormat(locale, {
|
|
90
|
+
timeStyle: "short",
|
|
91
|
+
timeZone,
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getFormatter(col, value) {
|
|
97
|
+
const formatters = makeFormatters();
|
|
98
|
+
const availableFormatters = Object.keys(formatters);
|
|
99
|
+
|
|
100
|
+
if ("formatType" in col && availableFormatters.includes(col.formatType)) {
|
|
101
|
+
const enhance = formatters[col.formatType];
|
|
102
|
+
return enhance.format(Number(value));
|
|
103
|
+
} else {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function objectsToCSV(jsonArray, columns, includeHeaders) {
|
|
109
|
+
if (!jsonArray.length || !columns.length) return "";
|
|
110
|
+
|
|
111
|
+
const rows = [];
|
|
112
|
+
|
|
113
|
+
if (includeHeaders) {
|
|
114
|
+
rows.push(columns.map((col) => col.label).join(","));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
jsonArray.forEach((row) => {
|
|
118
|
+
rows.push(
|
|
119
|
+
columns
|
|
120
|
+
.map((col) => {
|
|
121
|
+
const val = getNested(row, col.key);
|
|
122
|
+
const normalizedValue = normalisedValue(val);
|
|
123
|
+
const maybeFormattedValue = getFormatter(col, normalizedValue);
|
|
124
|
+
return maybeFormattedValue;
|
|
125
|
+
})
|
|
126
|
+
.join(",")
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return rows.join("\\n") + "\\n";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
self.onmessage = (event) => {
|
|
134
|
+
const msg = event.data;
|
|
135
|
+
try {
|
|
136
|
+
switch (msg.type) {
|
|
137
|
+
case "to_csv_chunk": {
|
|
138
|
+
const { columns, data, id } = msg;
|
|
139
|
+
const csvChunk = objectsToCSV(data, columns, !headersWritten.get(id));
|
|
140
|
+
const out = {
|
|
141
|
+
id,
|
|
142
|
+
result: csvChunk,
|
|
143
|
+
type: "csv_chunk",
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
headersWritten.set(id, true);
|
|
147
|
+
|
|
148
|
+
self.postMessage(out);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case "completed": {
|
|
153
|
+
const out = { id: msg.id, type: "done" };
|
|
154
|
+
self.postMessage(out);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
default: {
|
|
159
|
+
console.warn("Unsupported worker message: " + JSON.stringify(msg));
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const _error = error instanceof Error ? error : new Error(String(error));
|
|
165
|
+
|
|
166
|
+
self.postMessage({
|
|
167
|
+
error: { name: _error.name, message: _error.message, stack: _error.stack },
|
|
168
|
+
id: msg.id,
|
|
169
|
+
type: "error",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
`;
|
|
174
|
+
|
|
40
175
|
// src/core/WorkerManager.ts
|
|
41
176
|
var pending = /* @__PURE__ */ new Map();
|
|
42
|
-
|
|
177
|
+
function createWorkerBlobUrl() {
|
|
178
|
+
const blob = new Blob([workerCode], { type: "application/javascript" });
|
|
179
|
+
return URL.createObjectURL(blob);
|
|
180
|
+
}
|
|
181
|
+
var _worker, _blobUrl, _WorkerManager_instances, listenerRegistry_fn;
|
|
43
182
|
var _WorkerManager = class _WorkerManager {
|
|
44
183
|
constructor() {
|
|
45
184
|
__privateAdd(this, _WorkerManager_instances);
|
|
46
185
|
__privateAdd(this, _worker);
|
|
47
|
-
|
|
48
|
-
__privateSet(this,
|
|
49
|
-
|
|
50
|
-
|
|
186
|
+
__privateAdd(this, _blobUrl);
|
|
187
|
+
__privateSet(this, _blobUrl, createWorkerBlobUrl());
|
|
188
|
+
__privateSet(this, _worker, new Worker(__privateGet(this, _blobUrl), {
|
|
189
|
+
name: WEB_WORKER_NAME
|
|
51
190
|
}));
|
|
52
191
|
__privateMethod(this, _WorkerManager_instances, listenerRegistry_fn).call(this);
|
|
53
192
|
}
|
|
@@ -67,9 +206,14 @@ var _WorkerManager = class _WorkerManager {
|
|
|
67
206
|
__privateGet(this, _worker).terminate();
|
|
68
207
|
__privateSet(this, _worker, null);
|
|
69
208
|
}
|
|
209
|
+
if (__privateGet(this, _blobUrl)) {
|
|
210
|
+
URL.revokeObjectURL(__privateGet(this, _blobUrl));
|
|
211
|
+
__privateSet(this, _blobUrl, null);
|
|
212
|
+
}
|
|
70
213
|
}
|
|
71
214
|
};
|
|
72
215
|
_worker = new WeakMap();
|
|
216
|
+
_blobUrl = new WeakMap();
|
|
73
217
|
_WorkerManager_instances = new WeakSet();
|
|
74
218
|
listenerRegistry_fn = function() {
|
|
75
219
|
__privateGet(this, _worker)?.addEventListener("message", (event) => {
|
|
@@ -355,3 +499,4 @@ export {
|
|
|
355
499
|
useExportCSV_default as useExportCSV,
|
|
356
500
|
useMessageExportCSV_default as useMessageExportCSV
|
|
357
501
|
};
|
|
502
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useExportCSV.ts","../src/core/controllers/ExportController.ts","../src/core/contants/index.ts","../src/core/workerCode.ts","../src/core/WorkerManager.ts","../src/core/strategy/BolbExportStrategy.ts","../src/core/strategy/FsAccessExportStrategy.ts","../src/core/createExportController.ts","../src/core/ExportControllerSingleton.ts","../src/hooks/useMessageExportCSV.ts"],"sourcesContent":["import { useRef } from \"react\";\nimport { type ExportController, ExportControllerSingleton } from \"../core\";\n\nfunction useExportCSV() {\n const exportCallbackRef = useRef<ExportController>(ExportControllerSingleton.init());\n\n return {\n handler: exportCallbackRef?.current,\n };\n}\n\nexport default useExportCSV;\n","import type BolbExportStrategy from \"../strategy/BolbExportStrategy\";\nimport type FsAccessExportStrategy from \"../strategy/FsAccessExportStrategy\";\nimport type { ExportParams, ExportResponse, ExportStrategy } from \"../types\";\n\ntype ExportControllerDeps = {\n fsAccessStrategy: FsAccessExportStrategy;\n blobExportStrategy: BolbExportStrategy;\n};\n\nexport class ExportController {\n constructor(private readonly deps: ExportControllerDeps) {}\n\n public async start<T>(params: ExportParams<T>): Promise<ExportResponse> {\n const strategy = this.#resolveStrategy();\n\n return strategy.export(params);\n }\n\n #canUseFSAccess() {\n return typeof window.showSaveFilePicker === \"function\";\n }\n\n #resolveStrategy(): ExportStrategy {\n if (this.#canUseFSAccess()) {\n return this.deps.fsAccessStrategy;\n }\n\n return this.deps.blobExportStrategy;\n }\n}\n","export const WEB_WORKER_NAME = \"scv-worker\";\nexport const BROADCAST_CHANNEL_NAME = \"react-csv-exporter\";\n","export const workerCode = `\nconst headersWritten = new Map();\n\nfunction getNested(obj, keyPath) {\n return keyPath.split(\".\").reduce((acc, key) => {\n if (acc && typeof acc === \"object\") {\n return acc[key];\n }\n return undefined;\n }, obj);\n}\n\nfunction normalisedValue(value) {\n let result = value;\n\n if (typeof result === \"string\") {\n result = '\"' + result.replace(/\"/g, '\"\"') + '\"';\n }\n\n if (result === undefined || result === null) {\n result = \"\";\n }\n\n return result;\n}\n\nfunction makeFormatters(locale = \"en-US\", timeZone = \"UTC\", currency = \"USD\") {\n return {\n dateFull: new Intl.DateTimeFormat(locale, { dateStyle: \"full\", timeZone }),\n dateMediumTime: new Intl.DateTimeFormat(locale, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n timeZone,\n }),\n numCompact: new Intl.NumberFormat(locale, {\n maximumFractionDigits: 1,\n notation: \"compact\",\n }),\n numCurrency: new Intl.NumberFormat(locale, {\n currency,\n maximumFractionDigits: 2,\n style: \"currency\",\n }),\n numDecimal: new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }),\n numPercent: new Intl.NumberFormat(locale, {\n maximumFractionDigits: 1,\n style: \"percent\",\n }),\n timeShort: new Intl.DateTimeFormat(locale, {\n timeStyle: \"short\",\n timeZone,\n }),\n };\n}\n\nfunction getFormatter(col, value) {\n const formatters = makeFormatters();\n const availableFormatters = Object.keys(formatters);\n\n if (\"formatType\" in col && availableFormatters.includes(col.formatType)) {\n const enhance = formatters[col.formatType];\n return enhance.format(Number(value));\n } else {\n return value;\n }\n}\n\nfunction objectsToCSV(jsonArray, columns, includeHeaders) {\n if (!jsonArray.length || !columns.length) return \"\";\n\n const rows = [];\n\n if (includeHeaders) {\n rows.push(columns.map((col) => col.label).join(\",\"));\n }\n\n jsonArray.forEach((row) => {\n rows.push(\n columns\n .map((col) => {\n const val = getNested(row, col.key);\n const normalizedValue = normalisedValue(val);\n const maybeFormattedValue = getFormatter(col, normalizedValue);\n return maybeFormattedValue;\n })\n .join(\",\")\n );\n });\n\n return rows.join(\"\\\\n\") + \"\\\\n\";\n}\n\nself.onmessage = (event) => {\n const msg = event.data;\n try {\n switch (msg.type) {\n case \"to_csv_chunk\": {\n const { columns, data, id } = msg;\n const csvChunk = objectsToCSV(data, columns, !headersWritten.get(id));\n const out = {\n id,\n result: csvChunk,\n type: \"csv_chunk\",\n };\n\n headersWritten.set(id, true);\n\n self.postMessage(out);\n break;\n }\n\n case \"completed\": {\n const out = { id: msg.id, type: \"done\" };\n self.postMessage(out);\n break;\n }\n\n default: {\n console.warn(\"Unsupported worker message: \" + JSON.stringify(msg));\n break;\n }\n }\n } catch (error) {\n const _error = error instanceof Error ? error : new Error(String(error));\n\n self.postMessage({\n error: { name: _error.name, message: _error.message, stack: _error.stack },\n id: msg.id,\n type: \"error\",\n });\n }\n};\n`;\n","import { WEB_WORKER_NAME } from \"./contants\";\nimport type { JobId, ToWorkerMessage } from \"./types\";\nimport { workerCode } from \"./workerCode\";\n\nconst pending = new Map<JobId, { resolve: (value: unknown) => void; reject: (reason?: ErrorEvent) => void }>();\n\nfunction createWorkerBlobUrl(): string {\n const blob = new Blob([workerCode], { type: \"application/javascript\" });\n return URL.createObjectURL(blob);\n}\n\nclass WorkerManager {\n #worker: Worker | null;\n #blobUrl: string | null;\n\n constructor() {\n this.#blobUrl = createWorkerBlobUrl();\n\n this.#worker = new Worker(this.#blobUrl, {\n name: WEB_WORKER_NAME,\n });\n\n this.#listenerRegistry();\n }\n\n static initialise() {\n return new WorkerManager();\n }\n\n #listenerRegistry() {\n this.#worker?.addEventListener(\"message\", (event) => {\n const { id, result, error } = event.data;\n const entity = pending.get(id);\n\n if (!entity) {\n return;\n }\n\n pending.delete(id);\n\n if (error) {\n entity.reject(error);\n } else {\n entity.resolve(result);\n }\n });\n\n this.#worker?.addEventListener(\"error\", (event) => {\n for (const [, { reject }] of pending) {\n reject(event);\n }\n\n pending.clear();\n });\n }\n\n async triggerWorker(payload: ToWorkerMessage) {\n const id = payload.id ?? Math.random().toString(36).substr(2);\n\n const p = new Promise((resolve, reject) => {\n pending.set(id, { reject, resolve });\n });\n\n this.#worker?.postMessage(payload);\n\n return p;\n }\n\n terminate() {\n if (this.#worker) {\n this.#worker.terminate();\n this.#worker = null;\n }\n\n if (this.#blobUrl) {\n URL.revokeObjectURL(this.#blobUrl);\n this.#blobUrl = null;\n }\n }\n}\n\nexport default WorkerManager;\n","import { BROADCAST_CHANNEL_NAME } from \"../contants\";\nimport type { ExportParams, ExportResponse, ExportStrategy, JobId } from \"../types\";\nimport WorkerManager from \"../WorkerManager\";\n\nclass BolbExportStrategy implements ExportStrategy {\n private workerManager: WorkerManager;\n\n constructor() {\n this.workerManager = WorkerManager.initialise();\n }\n\n async export<T>(params: ExportParams<T>): Promise<ExportResponse> {\n const suggestedName = params.fileName ?? \"export\";\n const filename = suggestedName.endsWith(\".csv\") ? suggestedName : `${suggestedName}.csv`;\n\n let iterator: JobId = 0 as JobId;\n let totalRowsLoaded = 0;\n\n const messaging = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n\n const csvParts: BlobPart[] = [];\n\n try {\n while (true) {\n const response = await params.getNextPage(iterator++);\n\n const safeRows = Array.isArray(response.rows) ? response.rows : [];\n const safeTotal = response.total ?? 0;\n\n const isRowsEmpty = safeRows.length === 0;\n\n const nextRowsLoaded = totalRowsLoaded + safeRows.length;\n totalRowsLoaded = isRowsEmpty ? safeTotal : nextRowsLoaded;\n\n const isFinished = safeTotal > 0 ? totalRowsLoaded >= safeTotal : isRowsEmpty;\n\n if (isRowsEmpty) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n break;\n }\n\n const csvChunk = (await this.workerManager.triggerWorker({\n columns: params.columns,\n data: safeRows as Record<string, unknown>[],\n id: iterator,\n type: \"to_csv_chunk\",\n })) as string;\n\n csvParts.push(csvChunk);\n\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"progress\",\n }),\n );\n\n if (isFinished) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n break;\n }\n }\n\n const blob = new Blob([\"\\uFEFF\", ...csvParts], {\n type: \"text/csv;charset=utf-8\",\n });\n\n this.downloadBlob(blob, filename);\n\n return { finished: true, totalRowsLoaded };\n } catch (error) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: 0,\n type: \"failed\",\n }),\n );\n throw error;\n } finally {\n messaging.close();\n this.workerManager.terminate();\n }\n }\n\n private downloadBlob(blob: Blob, filename: string) {\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.rel = \"noopener\";\n\n document.body.appendChild(a);\n a.click();\n a.remove();\n\n URL.revokeObjectURL(url);\n }\n}\n\nexport default BolbExportStrategy;\n","import { BROADCAST_CHANNEL_NAME } from \"../contants\";\nimport type { ExportParams, ExportResponse, ExportStrategy, JobId } from \"../types\";\nimport WorkerManager from \"../WorkerManager\";\n\nclass FsAccessExportStrategy implements ExportStrategy {\n private workerManager: WorkerManager;\n\n constructor() {\n this.workerManager = WorkerManager.initialise();\n }\n\n async export<T>(params: ExportParams<T>): Promise<ExportResponse> {\n const _suggestedName = params?.fileName || \"export\";\n\n const fileHandle = await window.showSaveFilePicker({\n suggestedName: _suggestedName,\n types: [{ accept: { \"text/csv\": [\".csv\"] }, description: \"CSV file\" }],\n });\n\n const writableFileStream = await fileHandle.createWritable();\n let iterator: JobId = 0 as JobId;\n let totalRowsLoaded = 0;\n\n const encoder = new TextEncoder();\n const messaging = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n\n const readable = new ReadableStream({\n pull: async (controller) => {\n try {\n const response = await params.getNextPage(iterator++);\n\n const safeRows = Array.isArray(response.rows) ? response?.rows : [];\n const safeTotal = response.total ?? 0;\n\n const isRowsEmpty = !safeRows || !safeRows.length;\n const nextRowsLoaded = totalRowsLoaded + safeRows.length;\n totalRowsLoaded = isRowsEmpty ? safeTotal : nextRowsLoaded;\n const isFinished = totalRowsLoaded >= safeTotal;\n\n if (isRowsEmpty) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n messaging.close();\n controller.close();\n\n return;\n }\n\n const csvChunks = await this.workerManager.triggerWorker({\n columns: params.columns,\n data: safeRows as Record<string, unknown>[],\n id: iterator,\n type: \"to_csv_chunk\",\n });\n\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"progress\",\n }),\n );\n\n controller.enqueue(encoder.encode(csvChunks as string));\n\n if (isFinished) {\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: safeTotal,\n type: \"done\",\n }),\n );\n\n await this.workerManager.triggerWorker({\n id: iterator,\n type: \"completed\",\n });\n\n messaging.close();\n controller.close();\n\n return;\n }\n } catch (error) {\n controller.error(error);\n\n messaging.postMessage(\n JSON.stringify({\n loadedItemsCount: totalRowsLoaded,\n total: 0,\n type: \"failed\",\n }),\n );\n }\n },\n });\n\n try {\n await readable.pipeTo(writableFileStream);\n } catch (err) {\n console.error(\"Export failed:\", err);\n throw err;\n } finally {\n this.workerManager.terminate();\n }\n\n return {\n finished: true,\n totalRowsLoaded,\n };\n }\n}\n\nexport default FsAccessExportStrategy;\n","import { ExportController } from \"./controllers/ExportController\";\nimport BolbExportStrategy from \"./strategy/BolbExportStrategy\";\nimport FsAccessExportStrategy from \"./strategy/FsAccessExportStrategy\";\n\nfunction createExportController(): ExportController {\n return new ExportController({\n blobExportStrategy: new BolbExportStrategy(),\n fsAccessStrategy: new FsAccessExportStrategy(),\n });\n}\n\nexport default createExportController;\n","import type { ExportController } from \"./controllers/ExportController\";\nimport createExportController from \"./createExportController\";\n\n// biome-ignore lint/complexity/noStaticOnlyClass: Note(Pavlo) Prefer to keep as class\nclass ExportControllerSingleton {\n static instance: ExportController | null = null;\n static initialized: boolean = false;\n\n static init() {\n if (ExportControllerSingleton.instance) {\n return ExportControllerSingleton.instance;\n }\n\n ExportControllerSingleton.instance = createExportController();\n ExportControllerSingleton.initialized = true;\n\n return ExportControllerSingleton.instance;\n }\n\n static getInstance(): ExportController {\n if (!ExportControllerSingleton.instance) {\n return ExportControllerSingleton.init();\n }\n\n return ExportControllerSingleton.instance;\n }\n}\n\nexport default ExportControllerSingleton;\n","import { useEffect } from \"react\";\nimport { BROADCAST_CHANNEL_NAME } from \"../core/contants\";\n\ntype Payload = {\n total: number;\n loadedItemsCount: number;\n state: \"progress\" | \"failed\" | \"done\";\n};\n\nfunction useMessageExportCSV(cb: (payload: Payload) => void) {\n useEffect(() => {\n const channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n\n channel.addEventListener(\"message\", (params: MessageEvent) => {\n try {\n const json = JSON.parse(params.data) as Payload;\n\n cb(json);\n } catch (error) {\n console.error({ error });\n }\n });\n\n return () => {\n channel.close();\n };\n }, [cb]);\n}\n\nexport default useMessageExportCSV;\n"],"mappings":";;;;;;;;;;AAAA,SAAS,cAAc;;;ACAvB;AASO,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAA6B,MAA4B;AAA5B;AADxB;AAAA,EACqD;AAAA,EAE1D,MAAa,MAAS,QAAkD;AACtE,UAAM,WAAW,sBAAK,iDAAL;AAEjB,WAAO,SAAS,OAAO,MAAM;AAAA,EAC/B;AAaF;AApBO;AASL,oBAAe,WAAG;AAChB,SAAO,OAAO,OAAO,uBAAuB;AAC9C;AAEA,qBAAgB,WAAmB;AACjC,MAAI,sBAAK,gDAAL,YAAwB;AAC1B,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,SAAO,KAAK,KAAK;AACnB;;;AC5BK,IAAM,kBAAkB;AACxB,IAAM,yBAAyB;;;ACD/B,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACI1B,IAAM,UAAU,oBAAI,IAAyF;AAE7G,SAAS,sBAA8B;AACrC,QAAM,OAAO,IAAI,KAAK,CAAC,UAAU,GAAG,EAAE,MAAM,yBAAyB,CAAC;AACtE,SAAO,IAAI,gBAAgB,IAAI;AACjC;AATA;AAWA,IAAM,iBAAN,MAAM,eAAc;AAAA,EAIlB,cAAc;AAJhB;AACE;AACA;AAGE,uBAAK,UAAW,oBAAoB;AAEpC,uBAAK,SAAU,IAAI,OAAO,mBAAK,WAAU;AAAA,MACvC,MAAM;AAAA,IACR,CAAC;AAED,0BAAK,+CAAL;AAAA,EACF;AAAA,EAEA,OAAO,aAAa;AAClB,WAAO,IAAI,eAAc;AAAA,EAC3B;AAAA,EA6BA,MAAM,cAAc,SAA0B;AAC5C,UAAM,KAAK,QAAQ,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC;AAE5D,UAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,WAAW;AACzC,cAAQ,IAAI,IAAI,EAAE,QAAQ,QAAQ,CAAC;AAAA,IACrC,CAAC;AAED,uBAAK,UAAS,YAAY,OAAO;AAEjC,WAAO;AAAA,EACT;AAAA,EAEA,YAAY;AACV,QAAI,mBAAK,UAAS;AAChB,yBAAK,SAAQ,UAAU;AACvB,yBAAK,SAAU;AAAA,IACjB;AAEA,QAAI,mBAAK,WAAU;AACjB,UAAI,gBAAgB,mBAAK,SAAQ;AACjC,yBAAK,UAAW;AAAA,IAClB;AAAA,EACF;AACF;AAnEE;AACA;AAFF;AAkBE,sBAAiB,WAAG;AAClB,qBAAK,UAAS,iBAAiB,WAAW,CAAC,UAAU;AACnD,UAAM,EAAE,IAAI,QAAQ,MAAM,IAAI,MAAM;AACpC,UAAM,SAAS,QAAQ,IAAI,EAAE;AAE7B,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAEA,YAAQ,OAAO,EAAE;AAEjB,QAAI,OAAO;AACT,aAAO,OAAO,KAAK;AAAA,IACrB,OAAO;AACL,aAAO,QAAQ,MAAM;AAAA,IACvB;AAAA,EACF,CAAC;AAED,qBAAK,UAAS,iBAAiB,SAAS,CAAC,UAAU;AACjD,eAAW,CAAC,EAAE,EAAE,OAAO,CAAC,KAAK,SAAS;AACpC,aAAO,KAAK;AAAA,IACd;AAEA,YAAQ,MAAM;AAAA,EAChB,CAAC;AACH;AA3CF,IAAM,gBAAN;AAsEA,IAAO,wBAAQ;;;AC7Ef,IAAM,qBAAN,MAAmD;AAAA,EAGjD,cAAc;AACZ,SAAK,gBAAgB,sBAAc,WAAW;AAAA,EAChD;AAAA,EAEA,MAAM,OAAU,QAAkD;AAChE,UAAM,gBAAgB,OAAO,YAAY;AACzC,UAAM,WAAW,cAAc,SAAS,MAAM,IAAI,gBAAgB,GAAG,aAAa;AAElF,QAAI,WAAkB;AACtB,QAAI,kBAAkB;AAEtB,UAAM,YAAY,IAAI,iBAAiB,sBAAsB;AAE7D,UAAM,WAAuB,CAAC;AAE9B,QAAI;AACF,aAAO,MAAM;AACX,cAAM,WAAW,MAAM,OAAO,YAAY,UAAU;AAEpD,cAAM,WAAW,MAAM,QAAQ,SAAS,IAAI,IAAI,SAAS,OAAO,CAAC;AACjE,cAAM,YAAY,SAAS,SAAS;AAEpC,cAAM,cAAc,SAAS,WAAW;AAExC,cAAM,iBAAiB,kBAAkB,SAAS;AAClD,0BAAkB,cAAc,YAAY;AAE5C,cAAM,aAAa,YAAY,IAAI,mBAAmB,YAAY;AAElE,YAAI,aAAa;AACf,oBAAU;AAAA,YACR,KAAK,UAAU;AAAA,cACb,kBAAkB;AAAA,cAClB,OAAO;AAAA,cACP,MAAM;AAAA,YACR,CAAC;AAAA,UACH;AAEA,gBAAM,KAAK,cAAc,cAAc;AAAA,YACrC,IAAI;AAAA,YACJ,MAAM;AAAA,UACR,CAAC;AAED;AAAA,QACF;AAEA,cAAM,WAAY,MAAM,KAAK,cAAc,cAAc;AAAA,UACvD,SAAS,OAAO;AAAA,UAChB,MAAM;AAAA,UACN,IAAI;AAAA,UACJ,MAAM;AAAA,QACR,CAAC;AAED,iBAAS,KAAK,QAAQ;AAEtB,kBAAU;AAAA,UACR,KAAK,UAAU;AAAA,YACb,kBAAkB;AAAA,YAClB,OAAO;AAAA,YACP,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AAEA,YAAI,YAAY;AACd,oBAAU;AAAA,YACR,KAAK,UAAU;AAAA,cACb,kBAAkB;AAAA,cAClB,OAAO;AAAA,cACP,MAAM;AAAA,YACR,CAAC;AAAA,UACH;AAEA,gBAAM,KAAK,cAAc,cAAc;AAAA,YACrC,IAAI;AAAA,YACJ,MAAM;AAAA,UACR,CAAC;AAED;AAAA,QACF;AAAA,MACF;AAEA,YAAM,OAAO,IAAI,KAAK,CAAC,UAAU,GAAG,QAAQ,GAAG;AAAA,QAC7C,MAAM;AAAA,MACR,CAAC;AAED,WAAK,aAAa,MAAM,QAAQ;AAEhC,aAAO,EAAE,UAAU,MAAM,gBAAgB;AAAA,IAC3C,SAAS,OAAO;AACd,gBAAU;AAAA,QACR,KAAK,UAAU;AAAA,UACb,kBAAkB;AAAA,UAClB,OAAO;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA,YAAM;AAAA,IACR,UAAE;AACA,gBAAU,MAAM;AAChB,WAAK,cAAc,UAAU;AAAA,IAC/B;AAAA,EACF;AAAA,EAEQ,aAAa,MAAY,UAAkB;AACjD,UAAM,MAAM,IAAI,gBAAgB,IAAI;AAEpC,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,OAAO;AACT,MAAE,WAAW;AACb,MAAE,MAAM;AAER,aAAS,KAAK,YAAY,CAAC;AAC3B,MAAE,MAAM;AACR,MAAE,OAAO;AAET,QAAI,gBAAgB,GAAG;AAAA,EACzB;AACF;AAEA,IAAO,6BAAQ;;;AC1Hf,IAAM,yBAAN,MAAuD;AAAA,EAGrD,cAAc;AACZ,SAAK,gBAAgB,sBAAc,WAAW;AAAA,EAChD;AAAA,EAEA,MAAM,OAAU,QAAkD;AAChE,UAAM,iBAAiB,QAAQ,YAAY;AAE3C,UAAM,aAAa,MAAM,OAAO,mBAAmB;AAAA,MACjD,eAAe;AAAA,MACf,OAAO,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE,GAAG,aAAa,WAAW,CAAC;AAAA,IACvE,CAAC;AAED,UAAM,qBAAqB,MAAM,WAAW,eAAe;AAC3D,QAAI,WAAkB;AACtB,QAAI,kBAAkB;AAEtB,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,YAAY,IAAI,iBAAiB,sBAAsB;AAE7D,UAAM,WAAW,IAAI,eAAe;AAAA,MAClC,MAAM,OAAO,eAAe;AAC1B,YAAI;AACF,gBAAM,WAAW,MAAM,OAAO,YAAY,UAAU;AAEpD,gBAAM,WAAW,MAAM,QAAQ,SAAS,IAAI,IAAI,UAAU,OAAO,CAAC;AAClE,gBAAM,YAAY,SAAS,SAAS;AAEpC,gBAAM,cAAc,CAAC,YAAY,CAAC,SAAS;AAC3C,gBAAM,iBAAiB,kBAAkB,SAAS;AAClD,4BAAkB,cAAc,YAAY;AAC5C,gBAAM,aAAa,mBAAmB;AAEtC,cAAI,aAAa;AACf,sBAAU;AAAA,cACR,KAAK,UAAU;AAAA,gBACb,kBAAkB;AAAA,gBAClB,OAAO;AAAA,gBACP,MAAM;AAAA,cACR,CAAC;AAAA,YACH;AAEA,kBAAM,KAAK,cAAc,cAAc;AAAA,cACrC,IAAI;AAAA,cACJ,MAAM;AAAA,YACR,CAAC;AAED,sBAAU,MAAM;AAChB,uBAAW,MAAM;AAEjB;AAAA,UACF;AAEA,gBAAM,YAAY,MAAM,KAAK,cAAc,cAAc;AAAA,YACvD,SAAS,OAAO;AAAA,YAChB,MAAM;AAAA,YACN,IAAI;AAAA,YACJ,MAAM;AAAA,UACR,CAAC;AAED,oBAAU;AAAA,YACR,KAAK,UAAU;AAAA,cACb,kBAAkB;AAAA,cAClB,OAAO;AAAA,cACP,MAAM;AAAA,YACR,CAAC;AAAA,UACH;AAEA,qBAAW,QAAQ,QAAQ,OAAO,SAAmB,CAAC;AAEtD,cAAI,YAAY;AACd,sBAAU;AAAA,cACR,KAAK,UAAU;AAAA,gBACb,kBAAkB;AAAA,gBAClB,OAAO;AAAA,gBACP,MAAM;AAAA,cACR,CAAC;AAAA,YACH;AAEA,kBAAM,KAAK,cAAc,cAAc;AAAA,cACrC,IAAI;AAAA,cACJ,MAAM;AAAA,YACR,CAAC;AAED,sBAAU,MAAM;AAChB,uBAAW,MAAM;AAEjB;AAAA,UACF;AAAA,QACF,SAAS,OAAO;AACd,qBAAW,MAAM,KAAK;AAEtB,oBAAU;AAAA,YACR,KAAK,UAAU;AAAA,cACb,kBAAkB;AAAA,cAClB,OAAO;AAAA,cACP,MAAM;AAAA,YACR,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,SAAS,OAAO,kBAAkB;AAAA,IAC1C,SAAS,KAAK;AACZ,cAAQ,MAAM,kBAAkB,GAAG;AACnC,YAAM;AAAA,IACR,UAAE;AACA,WAAK,cAAc,UAAU;AAAA,IAC/B;AAEA,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,iCAAQ;;;ACzHf,SAAS,yBAA2C;AAClD,SAAO,IAAI,iBAAiB;AAAA,IAC1B,oBAAoB,IAAI,2BAAmB;AAAA,IAC3C,kBAAkB,IAAI,+BAAuB;AAAA,EAC/C,CAAC;AACH;AAEA,IAAO,iCAAQ;;;ACPf,IAAM,6BAAN,MAAM,2BAA0B;AAAA,EAI9B,OAAO,OAAO;AACZ,QAAI,2BAA0B,UAAU;AACtC,aAAO,2BAA0B;AAAA,IACnC;AAEA,+BAA0B,WAAW,+BAAuB;AAC5D,+BAA0B,cAAc;AAExC,WAAO,2BAA0B;AAAA,EACnC;AAAA,EAEA,OAAO,cAAgC;AACrC,QAAI,CAAC,2BAA0B,UAAU;AACvC,aAAO,2BAA0B,KAAK;AAAA,IACxC;AAEA,WAAO,2BAA0B;AAAA,EACnC;AACF;AAtBM,2BACG,WAAoC;AADvC,2BAEG,cAAuB;AAFhC,IAAM,4BAAN;AAwBA,IAAO,oCAAQ;;;ARzBf,SAAS,eAAe;AACtB,QAAM,oBAAoB,OAAyB,kCAA0B,KAAK,CAAC;AAEnF,SAAO;AAAA,IACL,SAAS,mBAAmB;AAAA,EAC9B;AACF;AAEA,IAAO,uBAAQ;;;ASXf,SAAS,iBAAiB;AAS1B,SAAS,oBAAoB,IAAgC;AAC3D,YAAU,MAAM;AACd,UAAM,UAAU,IAAI,iBAAiB,sBAAsB;AAE3D,YAAQ,iBAAiB,WAAW,CAAC,WAAyB;AAC5D,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,OAAO,IAAI;AAEnC,WAAG,IAAI;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,EAAE,MAAM,CAAC;AAAA,MACzB;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,EAAE,CAAC;AACT;AAEA,IAAO,8BAAQ;","names":[]}
|
package/package.json
CHANGED
package/dist/worker.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// src/core/utils.ts
|
|
2
|
-
function objectsToCSV(jsonArray, columns, includeHeaders) {
|
|
3
|
-
if (!jsonArray.length || !columns.length) return "";
|
|
4
|
-
const rows = [];
|
|
5
|
-
if (includeHeaders) {
|
|
6
|
-
rows.push(columns.map((col) => col.label).join(","));
|
|
7
|
-
}
|
|
8
|
-
jsonArray.forEach((row) => {
|
|
9
|
-
rows.push(
|
|
10
|
-
columns.map((col) => {
|
|
11
|
-
const val = getNested(row, col.key);
|
|
12
|
-
const normalizedValue = normalisedValue(val);
|
|
13
|
-
const maybeFormattedValue = getFormatter(col, normalizedValue);
|
|
14
|
-
return maybeFormattedValue;
|
|
15
|
-
}).join(",")
|
|
16
|
-
);
|
|
17
|
-
});
|
|
18
|
-
return `${rows.join("\n")}
|
|
19
|
-
`;
|
|
20
|
-
}
|
|
21
|
-
function getNested(obj, keyPath) {
|
|
22
|
-
return keyPath.split(".").reduce((acc, key) => {
|
|
23
|
-
if (acc && typeof acc === "object") {
|
|
24
|
-
return acc[key];
|
|
25
|
-
}
|
|
26
|
-
return void 0;
|
|
27
|
-
}, obj);
|
|
28
|
-
}
|
|
29
|
-
function normalisedValue(value) {
|
|
30
|
-
let result = value;
|
|
31
|
-
if (typeof result === "string") {
|
|
32
|
-
result = `"${result.replace(/"/g, '""')}"`;
|
|
33
|
-
}
|
|
34
|
-
if (result === void 0 || result === null) {
|
|
35
|
-
result = "";
|
|
36
|
-
}
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
function makeFormatters(locale = "en-US", timeZone = "UTC", currency = "USD") {
|
|
40
|
-
return {
|
|
41
|
-
dateFull: new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone }),
|
|
42
|
-
dateMediumTime: new Intl.DateTimeFormat(locale, {
|
|
43
|
-
dateStyle: "medium",
|
|
44
|
-
timeStyle: "short",
|
|
45
|
-
timeZone
|
|
46
|
-
}),
|
|
47
|
-
numCompact: new Intl.NumberFormat(locale, {
|
|
48
|
-
maximumFractionDigits: 1,
|
|
49
|
-
notation: "compact"
|
|
50
|
-
}),
|
|
51
|
-
numCurrency: new Intl.NumberFormat(locale, {
|
|
52
|
-
currency,
|
|
53
|
-
maximumFractionDigits: 2,
|
|
54
|
-
style: "currency"
|
|
55
|
-
}),
|
|
56
|
-
numDecimal: new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }),
|
|
57
|
-
numPercent: new Intl.NumberFormat(locale, {
|
|
58
|
-
maximumFractionDigits: 1,
|
|
59
|
-
style: "percent"
|
|
60
|
-
}),
|
|
61
|
-
timeShort: new Intl.DateTimeFormat(locale, {
|
|
62
|
-
timeStyle: "short",
|
|
63
|
-
timeZone
|
|
64
|
-
})
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function getFormatter(col, value) {
|
|
68
|
-
const formatters = makeFormatters();
|
|
69
|
-
const availableFormatters = Object.keys(formatters);
|
|
70
|
-
if ("formatType" in col && availableFormatters.includes(col.formatType)) {
|
|
71
|
-
const enhance = formatters[col.formatType];
|
|
72
|
-
return enhance.format(Number(value));
|
|
73
|
-
} else {
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/core/worker.ts
|
|
79
|
-
var headersWritten = /* @__PURE__ */ new Map();
|
|
80
|
-
self.onmessage = (event) => {
|
|
81
|
-
const msg = event.data;
|
|
82
|
-
try {
|
|
83
|
-
switch (msg.type) {
|
|
84
|
-
case "to_csv_chunk": {
|
|
85
|
-
const { columns, data, id } = msg;
|
|
86
|
-
const csvChunk = objectsToCSV(data, columns, !headersWritten.get(id));
|
|
87
|
-
const out = {
|
|
88
|
-
id,
|
|
89
|
-
result: csvChunk,
|
|
90
|
-
type: "csv_chunk"
|
|
91
|
-
};
|
|
92
|
-
headersWritten.set(id, true);
|
|
93
|
-
self.postMessage(out);
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
case "completed": {
|
|
97
|
-
const out = { id: msg.id, type: "done" };
|
|
98
|
-
self.postMessage(out);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
default: {
|
|
102
|
-
console.warn(`Unsupported for worker message:: ${JSON.stringify(msg)}`);
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
} catch (error) {
|
|
107
|
-
const _error = error instanceof Error ? error : new Error(String(error));
|
|
108
|
-
self.postMessage({
|
|
109
|
-
error: _error,
|
|
110
|
-
id: msg.id,
|
|
111
|
-
type: "error"
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
};
|