@farhantallei/excel 1.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 ADDED
@@ -0,0 +1,182 @@
1
+ # @farhantallei/excel
2
+
3
+ > Type-safe, declarative Excel (XLSX) export for Node.js server environments.
4
+
5
+ Build Excel files using a clean configuration object — with typed columns, formatting, and row mapping. Designed for server-side execution only.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - ✅ Declarative worksheet configuration
12
+ - ✅ Fully type-safe (TypeScript-first)
13
+ - ✅ Column metadata (width, type, format)
14
+ - ✅ Generic row mapping
15
+ - ✅ Server-only safe (no browser XLSX usage)
16
+ - ✅ Zero global state
17
+ - ✅ Clean separation of concerns
18
+ - ✅ JSON import from Excel
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @farhantallei/excel
26
+ # or
27
+ yarn add @farhantallei/excel
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Usage
33
+
34
+ ### Quick Start (Export)
35
+
36
+ ```ts
37
+ import { exportToExcel } from "@farhantallei/excel"
38
+
39
+ const blob = await exportToExcel({
40
+ sheetName: "Users",
41
+ header: ["No", "Name", "Active"],
42
+ columns: [
43
+ { width: 50, type: "number" },
44
+ { width: 200, type: "string" },
45
+ { width: 100, type: "string" },
46
+ ],
47
+ data: users,
48
+ mapRow: (user, index) => [
49
+ index + 1,
50
+ user.name,
51
+ user.active ? "Yes" : "No",
52
+ ],
53
+ })
54
+ ```
55
+
56
+ #### Send to Client (Next.js Example)
57
+
58
+ ```ts
59
+ // Server Action / Route Handler
60
+ return new Response(blob, {
61
+ headers: {
62
+ "Content-Type":
63
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
64
+ "Content-Disposition": 'attachment; filename="users.xlsx"',
65
+ },
66
+ })
67
+ ```
68
+
69
+ ### Import Excel → JSON
70
+
71
+ ```ts
72
+ import { readExcelToJson } from "@farhantallei/excel"
73
+
74
+ const data = await readExcelToJson<User>(file)
75
+ ```
76
+
77
+ #### Optional sheet selection:
78
+
79
+ ```ts
80
+ await readExcelToJson<User>(file, {
81
+ sheetName: "Users",
82
+ })
83
+ ```
84
+
85
+ ---
86
+
87
+ ## API
88
+
89
+ ### `exportToExcel<T>(config)`
90
+
91
+ Generate an Excel `Blob` from a declarative configuration.
92
+
93
+ #### Config
94
+
95
+ ```ts
96
+ {
97
+ sheetName: string
98
+ header: string[]
99
+ columns: {
100
+ width?: number
101
+ type?: "string" | "number" | "date"
102
+ format?: string
103
+ }[]
104
+ data: T[]
105
+ mapRow: (item: T, index: number) => unknown[]
106
+ }
107
+ ```
108
+
109
+ #### Returns
110
+
111
+ ```ts
112
+ Promise<Blob>
113
+ ```
114
+
115
+ ### `readExcelToJson<T>(file, options?)`
116
+
117
+ Read an Excel file and convert the first (or specified) worksheet into JSON.
118
+
119
+ #### Options
120
+
121
+ ```ts
122
+ {
123
+ sheetName?: string
124
+ }
125
+ ```
126
+
127
+ #### Returns
128
+
129
+ ```ts
130
+ Promise<T[]>
131
+ ```
132
+
133
+ ### Error Handling
134
+
135
+ * `APIError` – Standardized error class for HTTP requests
136
+
137
+ ```ts
138
+ try {
139
+ await fetcher("/users")({ method: "GET" })
140
+ } catch (err) {
141
+ if (err instanceof APIError) {
142
+ console.error(err.status, err.message)
143
+ }
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## ⚠️ Environment Constraint
150
+
151
+ This library is server-side only.
152
+
153
+ It must be executed in:
154
+
155
+ - Node.js
156
+ - Next.js Server Actions
157
+ - Next.js Route Handlers
158
+ - Any server runtime
159
+
160
+ It **must not** be used in the browser, as `xlsx` is not browser-compatible in this setup.
161
+
162
+ ---
163
+
164
+ ## 🏗 Design Principles
165
+
166
+ - No global state mutation
167
+ - No external I/O
168
+ - XLSX logic fully encapsulated
169
+ - Caller provides only configuration
170
+ - Strict TypeScript compatibility
171
+
172
+ ---
173
+
174
+ ## Contributing
175
+
176
+ PRs and issues are welcome. please fork the repository, create a feature branch, ensure all tests and type checks pass, and submit a pull request.
177
+
178
+ ---
179
+
180
+ ## License
181
+
182
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ var XLSX3 = require('xlsx');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var XLSX3__default = /*#__PURE__*/_interopDefault(XLSX3);
8
+
9
+ // src/index.ts
10
+ function buildSheet(config) {
11
+ const { header, headerRows, data, mapRow, footerRows } = config;
12
+ const rows = [];
13
+ if (headerRows && headerRows.length > 0) {
14
+ for (let i = 0; i < headerRows.length; i++) {
15
+ const row = headerRows[i];
16
+ if (row) rows.push(row);
17
+ }
18
+ } else if (header && header.length > 0) {
19
+ rows.push(header);
20
+ }
21
+ for (let i = 0; i < data.length; i++) {
22
+ const item = data[i];
23
+ if (item) rows.push(mapRow(item, i));
24
+ }
25
+ if (footerRows && footerRows.length > 0) {
26
+ for (let i = 0; i < footerRows.length; i++) {
27
+ const footerRow = footerRows[i];
28
+ if (footerRow) rows.push(footerRow);
29
+ }
30
+ }
31
+ const ws = XLSX3__default.default.utils.aoa_to_sheet(rows);
32
+ if (config.merges?.length) {
33
+ ws["!merges"] = config.merges;
34
+ }
35
+ const totalRows = rows.length;
36
+ const headerRowCount = config.headerRowCount ?? (config.headerRows ? config.headerRows.length : 1);
37
+ return { ws, totalRows, headerRowCount };
38
+ }
39
+ function applyColumnConfig({
40
+ ws,
41
+ columns,
42
+ totalRows,
43
+ headerRowCount
44
+ }) {
45
+ ws["!cols"] = columns.map((col) => ({ wpx: col.width ?? 100 }));
46
+ for (let colIndex = 0; colIndex < columns.length; colIndex++) {
47
+ const col = columns[colIndex];
48
+ if (!col) continue;
49
+ if (!col.type && !col.format) continue;
50
+ const colLetter = XLSX3__default.default.utils.encode_col(colIndex);
51
+ for (let row = headerRowCount; row < totalRows; row++) {
52
+ const cellAddress = `${colLetter}${row + 1}`;
53
+ const cell = ws[cellAddress];
54
+ if (!cell) continue;
55
+ switch (col.type) {
56
+ case "number":
57
+ if (typeof cell.v === "number") {
58
+ cell.t = "n";
59
+ cell.z = col.format || "#,##0";
60
+ }
61
+ break;
62
+ case "date": {
63
+ const dt = new Date(cell.v);
64
+ if (!Number.isNaN(dt.getTime())) {
65
+ cell.t = "d";
66
+ cell.v = dt;
67
+ }
68
+ break;
69
+ }
70
+ default:
71
+ cell.t = "s";
72
+ }
73
+ if (col.format) {
74
+ cell.z = col.format;
75
+ }
76
+ }
77
+ }
78
+ }
79
+ function writeToBlob(ws, sheetName) {
80
+ const wb = XLSX3__default.default.utils.book_new();
81
+ XLSX3__default.default.utils.book_append_sheet(wb, ws, sheetName);
82
+ const wbout = XLSX3__default.default.write(wb, { bookType: "xlsx", type: "array" });
83
+ return new Blob([wbout], {
84
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
85
+ });
86
+ }
87
+
88
+ // src/index.ts
89
+ function exportToExcel(config) {
90
+ const { ws, totalRows, headerRowCount } = buildSheet(config);
91
+ applyColumnConfig({
92
+ ws,
93
+ columns: config.columns,
94
+ totalRows,
95
+ headerRowCount
96
+ });
97
+ return writeToBlob(ws, config.sheetName);
98
+ }
99
+ async function readExcelToJson(file) {
100
+ const arrayBuffer = await file.arrayBuffer();
101
+ const workbook = XLSX3__default.default.read(arrayBuffer, { type: "array" });
102
+ const sheetName = workbook.SheetNames[0];
103
+ if (!sheetName) {
104
+ throw new Error("The Excel file does not contain any worksheets.");
105
+ }
106
+ const worksheet = workbook.Sheets[sheetName];
107
+ if (!worksheet) {
108
+ throw new Error(`Worksheet "${sheetName}" could not be found.`);
109
+ }
110
+ return XLSX3__default.default.utils.sheet_to_json(worksheet, { defval: "" });
111
+ }
112
+
113
+ exports.exportToExcel = exportToExcel;
114
+ exports.readExcelToJson = readExcelToJson;
115
+ //# sourceMappingURL=index.cjs.map
116
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/builder.ts","../src/formatter.ts","../src/writer.ts","../src/index.ts"],"names":["XLSX"],"mappings":"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","file":"index.cjs","sourcesContent":["import XLSX from \"xlsx\"\n\nimport type { ExcelExportConfig } from \"./types\"\n\nexport function buildSheet<T>(config: ExcelExportConfig<T>) {\n\tconst { header, headerRows, data, mapRow, footerRows } = config\n\n\tconst rows: (string | number | Date | null)[][] = []\n\n\tif (headerRows && headerRows.length > 0) {\n\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\tconst row = headerRows[i]\n\t\t\tif (row) rows.push(row)\n\t\t}\n\t} else if (header && header.length > 0) {\n\t\trows.push(header)\n\t}\n\n\tfor (let i = 0; i < data.length; i++) {\n\t\tconst item = data[i]\n\t\tif (item) rows.push(mapRow(item, i))\n\t}\n\n\tif (footerRows && footerRows.length > 0) {\n\t\tfor (let i = 0; i < footerRows.length; i++) {\n\t\t\tconst footerRow = footerRows[i]\n\t\t\tif (footerRow) rows.push(footerRow)\n\t\t}\n\t}\n\n\tconst ws = XLSX.utils.aoa_to_sheet(rows)\n\n\tif (config.merges?.length) {\n\t\tws[\"!merges\"] = config.merges\n\t}\n\n\tconst totalRows = rows.length\n\n\tconst headerRowCount =\n\t\tconfig.headerRowCount ?? (config.headerRows ? config.headerRows.length : 1)\n\n\treturn { ws, totalRows, headerRowCount }\n}\n","import XLSX from \"xlsx\"\n\nimport type { ColumnConfig } from \"./types\"\n\ninterface ApplyProps {\n\tws: XLSX.WorkSheet\n\tcolumns: ColumnConfig[]\n\ttotalRows: number\n\theaderRowCount: number\n}\n\nexport function applyColumnConfig({\n\tws,\n\tcolumns,\n\ttotalRows,\n\theaderRowCount,\n}: ApplyProps) {\n\tws[\"!cols\"] = columns.map((col) => ({ wpx: col.width ?? 100 }))\n\n\tfor (let colIndex = 0; colIndex < columns.length; colIndex++) {\n\t\tconst col = columns[colIndex]\n\t\tif (!col) continue\n\t\tif (!col.type && !col.format) continue\n\n\t\tconst colLetter = XLSX.utils.encode_col(colIndex)\n\n\t\tfor (let row = headerRowCount; row < totalRows; row++) {\n\t\t\tconst cellAddress = `${colLetter}${row + 1}`\n\t\t\tconst cell = ws[cellAddress]\n\t\t\tif (!cell) continue\n\n\t\t\tswitch (col.type) {\n\t\t\t\tcase \"number\":\n\t\t\t\t\tif (typeof cell.v === \"number\") {\n\t\t\t\t\t\tcell.t = \"n\"\n\t\t\t\t\t\tcell.z = col.format || \"#,##0\"\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"date\": {\n\t\t\t\t\tconst dt = new Date(cell.v)\n\t\t\t\t\tif (!Number.isNaN(dt.getTime())) {\n\t\t\t\t\t\tcell.t = \"d\"\n\t\t\t\t\t\tcell.v = dt\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t\tcell.t = \"s\"\n\t\t\t}\n\n\t\t\tif (col.format) {\n\t\t\t\tcell.z = col.format\n\t\t\t}\n\t\t}\n\t}\n}\n","import XLSX from \"xlsx\"\n\nexport function writeToBlob(ws: XLSX.WorkSheet, sheetName: string) {\n\tconst wb = XLSX.utils.book_new()\n\tXLSX.utils.book_append_sheet(wb, ws, sheetName)\n\n\tconst wbout = XLSX.write(wb, { bookType: \"xlsx\", type: \"array\" })\n\n\treturn new Blob([wbout], {\n\t\ttype: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\t})\n}\n","import XLSX from \"xlsx\"\n\nimport { buildSheet } from \"./builder\"\nimport { applyColumnConfig } from \"./formatter\"\nimport type { ExcelExportConfig } from \"./types\"\nimport { writeToBlob } from \"./writer\"\n\n/**\n * Generate an Excel file (XLSX) from a declarative configuration.\n *\n * This function builds a worksheet using the provided header, data, and row mapping\n * defined in the configuration object. Each column can define metadata such as\n * data type (string/number/date), column width, and optional custom formatting.\n * Once the worksheet is constructed, the function returns a downloadable XLSX `Blob`.\n *\n * ! Environment Constraint:\n * - This function must be executed in a server-side environment\n * as `xlsx` is not browser-compatible.\n * - The output is a `Blob` that can safely be sent to the client for download.\n *\n * Guarantees:\n * - No global state is modified.\n * - No external I/O is performed beyond internal workbook operations.\n * - All XLSX logic is fully encapsulated; the caller only provides configuration.\n *\n * @typeParam T - The type of each item in the `data` array.\n *\n * @param config - Excel export configuration.\n * - `sheetName`: The name of the worksheet.\n * - `header`: Column order for the first row of the Excel file.\n * - `columns`: Column metadata (width/type/format).\n * - `data`: Raw data to be mapped into Excel rows.\n * - `mapRow`: A function that maps each data item into an array of cell values (aligned with the header).\n *\n * @returns A downloadable Excel `Blob`.\n *\n * @example\n * const blob = await exportToExcel({\n * sheetName: \"Master Banner\",\n * header: [\"No\", \"Title\", \"Status\"],\n * columns: [\n * { width: 35, type: \"number\" },\n * { width: 300, type: \"string\" },\n * { width: 100, type: \"string\" },\n * ],\n * data: banners,\n * mapRow: (item, idx) => [idx + 1, item.title, item.active ? \"Active\" : \"Inactive\"],\n * })\n *\n * // on the client\n * const url = URL.createObjectURL(blob)\n * downloadFile(url, \"master-banner.xlsx\")\n */\nexport function exportToExcel<T = unknown>(config: ExcelExportConfig<T>) {\n\tconst { ws, totalRows, headerRowCount } = buildSheet(config)\n\n\tapplyColumnConfig({\n\t\tws,\n\t\tcolumns: config.columns,\n\t\ttotalRows,\n\t\theaderRowCount,\n\t})\n\n\treturn writeToBlob(ws, config.sheetName)\n}\n\nexport async function readExcelToJson<T>(file: File): Promise<T[]> {\n\tconst arrayBuffer = await file.arrayBuffer()\n\tconst workbook = XLSX.read(arrayBuffer, { type: \"array\" })\n\tconst sheetName = workbook.SheetNames[0]\n\tif (!sheetName) {\n\t\tthrow new Error(\"The Excel file does not contain any worksheets.\")\n\t}\n\tconst worksheet = workbook.Sheets[sheetName]\n\tif (!worksheet) {\n\t\tthrow new Error(`Worksheet \"${sheetName}\" could not be found.`)\n\t}\n\treturn XLSX.utils.sheet_to_json(worksheet, { defval: \"\" })\n}\n\nexport type { ExcelExportConfig }\n"]}
@@ -0,0 +1,52 @@
1
+ import { ExcelExportConfig } from './types.cjs';
2
+
3
+ /**
4
+ * Generate an Excel file (XLSX) from a declarative configuration.
5
+ *
6
+ * This function builds a worksheet using the provided header, data, and row mapping
7
+ * defined in the configuration object. Each column can define metadata such as
8
+ * data type (string/number/date), column width, and optional custom formatting.
9
+ * Once the worksheet is constructed, the function returns a downloadable XLSX `Blob`.
10
+ *
11
+ * ! Environment Constraint:
12
+ * - This function must be executed in a server-side environment
13
+ * as `xlsx` is not browser-compatible.
14
+ * - The output is a `Blob` that can safely be sent to the client for download.
15
+ *
16
+ * Guarantees:
17
+ * - No global state is modified.
18
+ * - No external I/O is performed beyond internal workbook operations.
19
+ * - All XLSX logic is fully encapsulated; the caller only provides configuration.
20
+ *
21
+ * @typeParam T - The type of each item in the `data` array.
22
+ *
23
+ * @param config - Excel export configuration.
24
+ * - `sheetName`: The name of the worksheet.
25
+ * - `header`: Column order for the first row of the Excel file.
26
+ * - `columns`: Column metadata (width/type/format).
27
+ * - `data`: Raw data to be mapped into Excel rows.
28
+ * - `mapRow`: A function that maps each data item into an array of cell values (aligned with the header).
29
+ *
30
+ * @returns A downloadable Excel `Blob`.
31
+ *
32
+ * @example
33
+ * const blob = await exportToExcel({
34
+ * sheetName: "Master Banner",
35
+ * header: ["No", "Title", "Status"],
36
+ * columns: [
37
+ * { width: 35, type: "number" },
38
+ * { width: 300, type: "string" },
39
+ * { width: 100, type: "string" },
40
+ * ],
41
+ * data: banners,
42
+ * mapRow: (item, idx) => [idx + 1, item.title, item.active ? "Active" : "Inactive"],
43
+ * })
44
+ *
45
+ * // on the client
46
+ * const url = URL.createObjectURL(blob)
47
+ * downloadFile(url, "master-banner.xlsx")
48
+ */
49
+ declare function exportToExcel<T = unknown>(config: ExcelExportConfig<T>): Blob;
50
+ declare function readExcelToJson<T>(file: File): Promise<T[]>;
51
+
52
+ export { ExcelExportConfig, exportToExcel, readExcelToJson };
@@ -0,0 +1,52 @@
1
+ import { ExcelExportConfig } from './types.js';
2
+
3
+ /**
4
+ * Generate an Excel file (XLSX) from a declarative configuration.
5
+ *
6
+ * This function builds a worksheet using the provided header, data, and row mapping
7
+ * defined in the configuration object. Each column can define metadata such as
8
+ * data type (string/number/date), column width, and optional custom formatting.
9
+ * Once the worksheet is constructed, the function returns a downloadable XLSX `Blob`.
10
+ *
11
+ * ! Environment Constraint:
12
+ * - This function must be executed in a server-side environment
13
+ * as `xlsx` is not browser-compatible.
14
+ * - The output is a `Blob` that can safely be sent to the client for download.
15
+ *
16
+ * Guarantees:
17
+ * - No global state is modified.
18
+ * - No external I/O is performed beyond internal workbook operations.
19
+ * - All XLSX logic is fully encapsulated; the caller only provides configuration.
20
+ *
21
+ * @typeParam T - The type of each item in the `data` array.
22
+ *
23
+ * @param config - Excel export configuration.
24
+ * - `sheetName`: The name of the worksheet.
25
+ * - `header`: Column order for the first row of the Excel file.
26
+ * - `columns`: Column metadata (width/type/format).
27
+ * - `data`: Raw data to be mapped into Excel rows.
28
+ * - `mapRow`: A function that maps each data item into an array of cell values (aligned with the header).
29
+ *
30
+ * @returns A downloadable Excel `Blob`.
31
+ *
32
+ * @example
33
+ * const blob = await exportToExcel({
34
+ * sheetName: "Master Banner",
35
+ * header: ["No", "Title", "Status"],
36
+ * columns: [
37
+ * { width: 35, type: "number" },
38
+ * { width: 300, type: "string" },
39
+ * { width: 100, type: "string" },
40
+ * ],
41
+ * data: banners,
42
+ * mapRow: (item, idx) => [idx + 1, item.title, item.active ? "Active" : "Inactive"],
43
+ * })
44
+ *
45
+ * // on the client
46
+ * const url = URL.createObjectURL(blob)
47
+ * downloadFile(url, "master-banner.xlsx")
48
+ */
49
+ declare function exportToExcel<T = unknown>(config: ExcelExportConfig<T>): Blob;
50
+ declare function readExcelToJson<T>(file: File): Promise<T[]>;
51
+
52
+ export { ExcelExportConfig, exportToExcel, readExcelToJson };
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ import XLSX3 from 'xlsx';
2
+
3
+ // src/index.ts
4
+ function buildSheet(config) {
5
+ const { header, headerRows, data, mapRow, footerRows } = config;
6
+ const rows = [];
7
+ if (headerRows && headerRows.length > 0) {
8
+ for (let i = 0; i < headerRows.length; i++) {
9
+ const row = headerRows[i];
10
+ if (row) rows.push(row);
11
+ }
12
+ } else if (header && header.length > 0) {
13
+ rows.push(header);
14
+ }
15
+ for (let i = 0; i < data.length; i++) {
16
+ const item = data[i];
17
+ if (item) rows.push(mapRow(item, i));
18
+ }
19
+ if (footerRows && footerRows.length > 0) {
20
+ for (let i = 0; i < footerRows.length; i++) {
21
+ const footerRow = footerRows[i];
22
+ if (footerRow) rows.push(footerRow);
23
+ }
24
+ }
25
+ const ws = XLSX3.utils.aoa_to_sheet(rows);
26
+ if (config.merges?.length) {
27
+ ws["!merges"] = config.merges;
28
+ }
29
+ const totalRows = rows.length;
30
+ const headerRowCount = config.headerRowCount ?? (config.headerRows ? config.headerRows.length : 1);
31
+ return { ws, totalRows, headerRowCount };
32
+ }
33
+ function applyColumnConfig({
34
+ ws,
35
+ columns,
36
+ totalRows,
37
+ headerRowCount
38
+ }) {
39
+ ws["!cols"] = columns.map((col) => ({ wpx: col.width ?? 100 }));
40
+ for (let colIndex = 0; colIndex < columns.length; colIndex++) {
41
+ const col = columns[colIndex];
42
+ if (!col) continue;
43
+ if (!col.type && !col.format) continue;
44
+ const colLetter = XLSX3.utils.encode_col(colIndex);
45
+ for (let row = headerRowCount; row < totalRows; row++) {
46
+ const cellAddress = `${colLetter}${row + 1}`;
47
+ const cell = ws[cellAddress];
48
+ if (!cell) continue;
49
+ switch (col.type) {
50
+ case "number":
51
+ if (typeof cell.v === "number") {
52
+ cell.t = "n";
53
+ cell.z = col.format || "#,##0";
54
+ }
55
+ break;
56
+ case "date": {
57
+ const dt = new Date(cell.v);
58
+ if (!Number.isNaN(dt.getTime())) {
59
+ cell.t = "d";
60
+ cell.v = dt;
61
+ }
62
+ break;
63
+ }
64
+ default:
65
+ cell.t = "s";
66
+ }
67
+ if (col.format) {
68
+ cell.z = col.format;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ function writeToBlob(ws, sheetName) {
74
+ const wb = XLSX3.utils.book_new();
75
+ XLSX3.utils.book_append_sheet(wb, ws, sheetName);
76
+ const wbout = XLSX3.write(wb, { bookType: "xlsx", type: "array" });
77
+ return new Blob([wbout], {
78
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
79
+ });
80
+ }
81
+
82
+ // src/index.ts
83
+ function exportToExcel(config) {
84
+ const { ws, totalRows, headerRowCount } = buildSheet(config);
85
+ applyColumnConfig({
86
+ ws,
87
+ columns: config.columns,
88
+ totalRows,
89
+ headerRowCount
90
+ });
91
+ return writeToBlob(ws, config.sheetName);
92
+ }
93
+ async function readExcelToJson(file) {
94
+ const arrayBuffer = await file.arrayBuffer();
95
+ const workbook = XLSX3.read(arrayBuffer, { type: "array" });
96
+ const sheetName = workbook.SheetNames[0];
97
+ if (!sheetName) {
98
+ throw new Error("The Excel file does not contain any worksheets.");
99
+ }
100
+ const worksheet = workbook.Sheets[sheetName];
101
+ if (!worksheet) {
102
+ throw new Error(`Worksheet "${sheetName}" could not be found.`);
103
+ }
104
+ return XLSX3.utils.sheet_to_json(worksheet, { defval: "" });
105
+ }
106
+
107
+ export { exportToExcel, readExcelToJson };
108
+ //# sourceMappingURL=index.js.map
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/builder.ts","../src/formatter.ts","../src/writer.ts","../src/index.ts"],"names":["XLSX"],"mappings":"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","file":"index.js","sourcesContent":["import XLSX from \"xlsx\"\n\nimport type { ExcelExportConfig } from \"./types\"\n\nexport function buildSheet<T>(config: ExcelExportConfig<T>) {\n\tconst { header, headerRows, data, mapRow, footerRows } = config\n\n\tconst rows: (string | number | Date | null)[][] = []\n\n\tif (headerRows && headerRows.length > 0) {\n\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\tconst row = headerRows[i]\n\t\t\tif (row) rows.push(row)\n\t\t}\n\t} else if (header && header.length > 0) {\n\t\trows.push(header)\n\t}\n\n\tfor (let i = 0; i < data.length; i++) {\n\t\tconst item = data[i]\n\t\tif (item) rows.push(mapRow(item, i))\n\t}\n\n\tif (footerRows && footerRows.length > 0) {\n\t\tfor (let i = 0; i < footerRows.length; i++) {\n\t\t\tconst footerRow = footerRows[i]\n\t\t\tif (footerRow) rows.push(footerRow)\n\t\t}\n\t}\n\n\tconst ws = XLSX.utils.aoa_to_sheet(rows)\n\n\tif (config.merges?.length) {\n\t\tws[\"!merges\"] = config.merges\n\t}\n\n\tconst totalRows = rows.length\n\n\tconst headerRowCount =\n\t\tconfig.headerRowCount ?? (config.headerRows ? config.headerRows.length : 1)\n\n\treturn { ws, totalRows, headerRowCount }\n}\n","import XLSX from \"xlsx\"\n\nimport type { ColumnConfig } from \"./types\"\n\ninterface ApplyProps {\n\tws: XLSX.WorkSheet\n\tcolumns: ColumnConfig[]\n\ttotalRows: number\n\theaderRowCount: number\n}\n\nexport function applyColumnConfig({\n\tws,\n\tcolumns,\n\ttotalRows,\n\theaderRowCount,\n}: ApplyProps) {\n\tws[\"!cols\"] = columns.map((col) => ({ wpx: col.width ?? 100 }))\n\n\tfor (let colIndex = 0; colIndex < columns.length; colIndex++) {\n\t\tconst col = columns[colIndex]\n\t\tif (!col) continue\n\t\tif (!col.type && !col.format) continue\n\n\t\tconst colLetter = XLSX.utils.encode_col(colIndex)\n\n\t\tfor (let row = headerRowCount; row < totalRows; row++) {\n\t\t\tconst cellAddress = `${colLetter}${row + 1}`\n\t\t\tconst cell = ws[cellAddress]\n\t\t\tif (!cell) continue\n\n\t\t\tswitch (col.type) {\n\t\t\t\tcase \"number\":\n\t\t\t\t\tif (typeof cell.v === \"number\") {\n\t\t\t\t\t\tcell.t = \"n\"\n\t\t\t\t\t\tcell.z = col.format || \"#,##0\"\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"date\": {\n\t\t\t\t\tconst dt = new Date(cell.v)\n\t\t\t\t\tif (!Number.isNaN(dt.getTime())) {\n\t\t\t\t\t\tcell.t = \"d\"\n\t\t\t\t\t\tcell.v = dt\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t\tcell.t = \"s\"\n\t\t\t}\n\n\t\t\tif (col.format) {\n\t\t\t\tcell.z = col.format\n\t\t\t}\n\t\t}\n\t}\n}\n","import XLSX from \"xlsx\"\n\nexport function writeToBlob(ws: XLSX.WorkSheet, sheetName: string) {\n\tconst wb = XLSX.utils.book_new()\n\tXLSX.utils.book_append_sheet(wb, ws, sheetName)\n\n\tconst wbout = XLSX.write(wb, { bookType: \"xlsx\", type: \"array\" })\n\n\treturn new Blob([wbout], {\n\t\ttype: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\t})\n}\n","import XLSX from \"xlsx\"\n\nimport { buildSheet } from \"./builder\"\nimport { applyColumnConfig } from \"./formatter\"\nimport type { ExcelExportConfig } from \"./types\"\nimport { writeToBlob } from \"./writer\"\n\n/**\n * Generate an Excel file (XLSX) from a declarative configuration.\n *\n * This function builds a worksheet using the provided header, data, and row mapping\n * defined in the configuration object. Each column can define metadata such as\n * data type (string/number/date), column width, and optional custom formatting.\n * Once the worksheet is constructed, the function returns a downloadable XLSX `Blob`.\n *\n * ! Environment Constraint:\n * - This function must be executed in a server-side environment\n * as `xlsx` is not browser-compatible.\n * - The output is a `Blob` that can safely be sent to the client for download.\n *\n * Guarantees:\n * - No global state is modified.\n * - No external I/O is performed beyond internal workbook operations.\n * - All XLSX logic is fully encapsulated; the caller only provides configuration.\n *\n * @typeParam T - The type of each item in the `data` array.\n *\n * @param config - Excel export configuration.\n * - `sheetName`: The name of the worksheet.\n * - `header`: Column order for the first row of the Excel file.\n * - `columns`: Column metadata (width/type/format).\n * - `data`: Raw data to be mapped into Excel rows.\n * - `mapRow`: A function that maps each data item into an array of cell values (aligned with the header).\n *\n * @returns A downloadable Excel `Blob`.\n *\n * @example\n * const blob = await exportToExcel({\n * sheetName: \"Master Banner\",\n * header: [\"No\", \"Title\", \"Status\"],\n * columns: [\n * { width: 35, type: \"number\" },\n * { width: 300, type: \"string\" },\n * { width: 100, type: \"string\" },\n * ],\n * data: banners,\n * mapRow: (item, idx) => [idx + 1, item.title, item.active ? \"Active\" : \"Inactive\"],\n * })\n *\n * // on the client\n * const url = URL.createObjectURL(blob)\n * downloadFile(url, \"master-banner.xlsx\")\n */\nexport function exportToExcel<T = unknown>(config: ExcelExportConfig<T>) {\n\tconst { ws, totalRows, headerRowCount } = buildSheet(config)\n\n\tapplyColumnConfig({\n\t\tws,\n\t\tcolumns: config.columns,\n\t\ttotalRows,\n\t\theaderRowCount,\n\t})\n\n\treturn writeToBlob(ws, config.sheetName)\n}\n\nexport async function readExcelToJson<T>(file: File): Promise<T[]> {\n\tconst arrayBuffer = await file.arrayBuffer()\n\tconst workbook = XLSX.read(arrayBuffer, { type: \"array\" })\n\tconst sheetName = workbook.SheetNames[0]\n\tif (!sheetName) {\n\t\tthrow new Error(\"The Excel file does not contain any worksheets.\")\n\t}\n\tconst worksheet = workbook.Sheets[sheetName]\n\tif (!worksheet) {\n\t\tthrow new Error(`Worksheet \"${sheetName}\" could not be found.`)\n\t}\n\treturn XLSX.utils.sheet_to_json(worksheet, { defval: \"\" })\n}\n\nexport type { ExcelExportConfig }\n"]}
package/dist/types.cjs ADDED
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ //# sourceMappingURL=types.cjs.map
4
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"types.cjs"}
@@ -0,0 +1,28 @@
1
+ interface CellMergeRange {
2
+ s: {
3
+ r: number;
4
+ c: number;
5
+ };
6
+ e: {
7
+ r: number;
8
+ c: number;
9
+ };
10
+ }
11
+ interface ColumnConfig {
12
+ width?: number;
13
+ type?: "string" | "number" | "date";
14
+ format?: string;
15
+ }
16
+ interface ExcelExportConfig<T = unknown> {
17
+ sheetName: string;
18
+ header?: string[];
19
+ headerRows?: (string | null)[][];
20
+ footerRows?: (string | number | Date | null)[][];
21
+ headerRowCount?: number;
22
+ columns: ColumnConfig[];
23
+ data: T[];
24
+ mapRow: (item: T, index: number) => (string | number | Date | null)[];
25
+ merges?: CellMergeRange[];
26
+ }
27
+
28
+ export type { CellMergeRange, ColumnConfig, ExcelExportConfig };
@@ -0,0 +1,28 @@
1
+ interface CellMergeRange {
2
+ s: {
3
+ r: number;
4
+ c: number;
5
+ };
6
+ e: {
7
+ r: number;
8
+ c: number;
9
+ };
10
+ }
11
+ interface ColumnConfig {
12
+ width?: number;
13
+ type?: "string" | "number" | "date";
14
+ format?: string;
15
+ }
16
+ interface ExcelExportConfig<T = unknown> {
17
+ sheetName: string;
18
+ header?: string[];
19
+ headerRows?: (string | null)[][];
20
+ footerRows?: (string | number | Date | null)[][];
21
+ headerRowCount?: number;
22
+ columns: ColumnConfig[];
23
+ data: T[];
24
+ mapRow: (item: T, index: number) => (string | number | Date | null)[];
25
+ merges?: CellMergeRange[];
26
+ }
27
+
28
+ export type { CellMergeRange, ColumnConfig, ExcelExportConfig };
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=types.js.map
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"types.js"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@farhantallei/excel",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "description": "Declarative and type-safe Excel (XLSX) export for Node.js.",
6
+ "keywords": [
7
+ "excel",
8
+ "xlsx",
9
+ "excel-export",
10
+ "xlsx-export",
11
+ "typescript",
12
+ "type-safe",
13
+ "nodejs",
14
+ "server-only",
15
+ "spreadsheet",
16
+ "excel-generator",
17
+ "excel-builder",
18
+ "data-export",
19
+ "tabular-data"
20
+ ],
21
+ "author": {
22
+ "name": "Farhan Pradana Tallei",
23
+ "email": "farhan.pradana55@gmail.com"
24
+ },
25
+ "module": "dist/index.js",
26
+ "type": "module",
27
+ "types": "./dist/index.d.ts",
28
+ "scripts": {
29
+ "build": "tsup --config tsup.config.ts",
30
+ "lint": "biome check",
31
+ "format": "biome check --write --unsafe",
32
+ "publint": "publint",
33
+ "typecheck": "tsc --noEmit",
34
+ "test": "bun test",
35
+ "prepare": "bun run build"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "publishConfig": {
41
+ "registry": "https://registry.npmjs.org/",
42
+ "access": "public"
43
+ },
44
+ "homepage": "https://github.com/farhantallei/excel",
45
+ "exports": {
46
+ ".": {
47
+ "import": {
48
+ "types": "./dist/index.d.ts",
49
+ "default": "./dist/index.js"
50
+ },
51
+ "require": {
52
+ "types": "./dist/index.d.cts",
53
+ "default": "./dist/index.cjs"
54
+ }
55
+ },
56
+ "./types": {
57
+ "types": "./dist/types.d.ts"
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@biomejs/biome": "2.3.15",
62
+ "@types/bun": "latest",
63
+ "publint": "^0.3.17",
64
+ "tsup": "^8.5.1"
65
+ },
66
+ "peerDependencies": {
67
+ "typescript": "^5"
68
+ },
69
+ "dependencies": {
70
+ "xlsx": "^0.18.5"
71
+ }
72
+ }