@krutai/excel-comparison 0.0.6 → 0.0.8

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/AI_REFERENCE.md CHANGED
@@ -1,353 +1,151 @@
1
1
  # AI Reference Guide for @krutai/excel-comparison
2
2
 
3
- This document provides comprehensive context for AI assistants to help users integrate and use the excel-comparison library effectively.
3
+ This document provides comprehensive context for AI assistants to help users integrate and use the excel-comparison library effectively in Next.js applications.
4
4
 
5
5
  ## Library Purpose
6
6
 
7
- The `@krutai/excel-comparison` library compares Excel (.xlsx, .xls) and CSV files, finding:
7
+ The `@krutai/excel-comparison` library compares Excel (.xlsx, .xls) and CSV files by offloading processing to a backend API. It finds:
8
8
  - Matching rows between two files
9
9
  - Different values in matching rows
10
10
  - Unique rows in each file
11
11
  - Generates downloadable Excel reports with differences highlighted
12
12
 
13
- ## Core Classes and Exports
13
+ ## Next.js API Route Integration (Recommended)
14
14
 
15
- // Main class - use this for comparing files via API
16
- import { krutExcelComparison, createComparisonClient } from "@krutai/excel-comparison";
17
-
18
- // Create client using convenience factory (recommended)
19
- const client = krutExcelComparison({
20
- serverUrl: "https://api.krut.ai",
21
- apiKey: "your-krut-api-key"
22
- });
23
-
24
- // Or using createComparisonClient
25
- const client2 = createComparisonClient({
26
- apiKey: "..."
27
- });
28
-
29
- // API client class
30
- import { ComparisonApiClient } from "@krutai/excel-comparison";
31
-
32
- ### Type Exports
33
-
34
- ```typescript
35
- import type {
36
- ComparisonApiResponse, // Result from API comparison
37
- PreviewResponse, // Preview info for files
38
- CompareFilesOptions, // Configuration options
39
- DataRecord, // Row data as object (used in reporting/engine)
40
- } from "@krutai/excel-comparison";
41
- ```
42
-
43
- ## Common Use Cases
44
-
45
- ### 1. Basic File Comparison (via API)
46
-
47
- ```typescript
48
- import { krutExcelComparison } from "@krutai/excel-comparison";
49
-
50
- const client = krutExcelComparison({
51
- apiKey: "your-api-key"
52
- });
53
-
54
- // For browser - using File objects
55
- const result = await client.compareFilesFromFileObjects(file1, file2);
56
-
57
- // For Node.js/Server - using Buffer
58
- const result2 = await client.compareFiles(buffer1, "file1.xlsx", buffer2, "file2.xlsx");
59
- ```
60
-
61
- ### 2. With Comparison Options
62
-
63
- ```typescript
64
- const result = await client.compareFilesFromFileObjects(file1, file2, {
65
- matchColumn: "Invoice", // Optional: column to match rows
66
- tolerance: 0.01, // Optional: numeric tolerance
67
- caseSensitive: false, // Optional: case insensitive
68
- ignoreColumns: ["Timestamp"] // Optional: ignore these columns
69
- });
70
- ```
71
-
72
- ### 3. Preview Files Before Comparison
73
-
74
- ```typescript
75
- // Get preview metadata and suggested match columns
76
- const preview = await client.previewFiles(file1, file2);
77
-
78
- // Returns:
79
- {
80
- success: true,
81
- file1: { name: "sales.xlsx", rowCount: 100, columns: [...], sample: [...] },
82
- file2: { name: "sales2.xlsx", rowCount: 95, columns: [...], sample: [...] },
83
- suggestedMatchColumn: "Invoice"
84
- }
85
- ```
86
-
87
- ### 4. Generate Excel Report
88
-
89
- ```typescript
90
- import { StyledReporter } from "@krutai/excel-comparison";
91
-
92
- const reporter = new StyledReporter({
93
- headerColor: "4472C4", // Blue header
94
- differenceColor: "FFCCCC", // Red for differences
95
- matchColor: "CCFFCC", // Green for matches
96
- includeSummary: true
97
- });
98
-
99
- // Transform comparison results for report
100
- const reportData = result.results.map((r, i) => ({
101
- Row: i + 1,
102
- Status: r.status,
103
- "Match %": `${r.matchPercentage}%`,
104
- ...Object.fromEntries(
105
- result.allColumns.flatMap(col => [
106
- [`File1_${col}`, r.file1Row?.[col] ?? 'N/A'],
107
- [`File2_${col}`, r.file2Row?.[col] ?? 'N/A'],
108
- ])
109
- )
110
- }));
111
-
112
- const report = await reporter.generateReport({
113
- data: reportData,
114
- summary: result.summary,
115
- });
116
-
117
- // Download as base64
118
- const downloadUrl = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${report.toString('base64')}`;
119
- ```
120
-
121
- ### 5. Next.js API Route Integration (Recommended)
15
+ This is the primary way to use the library in a modern Next.js application.
122
16
 
123
17
  ```typescript
124
18
  // app/api/compare/route.ts
125
19
  import { NextRequest, NextResponse } from "next/server";
126
- import {
127
- createComparisonClient,
128
- type CompareFilesOptions,
129
- } from "@krutai/excel-comparison";
20
+ import { krutExcelComparison, type CompareFilesOptions } from "@krutai/excel-comparison";
21
+
22
+ export const dynamic = "force-dynamic";
130
23
 
131
24
  const SUPPORTED_EXTENSIONS = new Set(["xlsx", "xls", "csv"]);
132
25
 
133
26
  function getFileExtension(fileName: string): string {
134
- const parts = fileName.toLowerCase().split(".");
135
- return parts.length > 1 ? parts[parts.length - 1] : "";
27
+ const parts = fileName.toLowerCase().split(".");
28
+ return parts.length > 1 ? parts[parts.length - 1] : "";
136
29
  }
137
30
 
138
31
  function isSupportedFile(file: File): boolean {
139
- return SUPPORTED_EXTENSIONS.has(getFileExtension(file.name));
32
+ return SUPPORTED_EXTENSIONS.has(getFileExtension(file.name));
140
33
  }
141
34
 
142
- function getComparisonErrorMessage(raw: string): string {
143
- const lower = raw.toLowerCase();
144
- if (lower.includes("can't find end of central directory") || lower.includes("zip file")) {
145
- return "One of the uploaded files is not a valid .xlsx file archive. For Excel inputs, use a valid .xlsx or .xls file.";
146
- }
147
- if (lower.includes("request timed out")) {
148
- return "Comparison request timed out. Please try smaller files or increase the timeout.";
35
+ export async function POST(req: NextRequest) {
36
+ try {
37
+ const formData = await req.formData();
38
+ const file1 = formData.get("file1");
39
+ const file2 = formData.get("file2");
40
+
41
+ if (!(file1 instanceof File) || !(file2 instanceof File)) {
42
+ return NextResponse.json(
43
+ { error: "Both file1 and file2 are required." },
44
+ { status: 400 }
45
+ );
149
46
  }
150
- return raw;
151
- }
152
-
153
- function parseIgnoreColumns(value?: string): string[] | undefined {
154
- if (!value?.trim()) return undefined;
155
47
 
156
- if (value.trim().startsWith("[")) {
157
- try {
158
- const parsed = JSON.parse(value) as unknown;
159
- if (Array.isArray(parsed)) {
160
- return parsed
161
- .filter((item): item is string => typeof item === "string")
162
- .map((item) => item.trim())
163
- .filter(Boolean);
164
- }
165
- } catch { }
48
+ if (!isSupportedFile(file1) || !isSupportedFile(file2)) {
49
+ return NextResponse.json(
50
+ { error: "Only .xlsx, .xls, and .csv files are supported." },
51
+ { status: 400 }
52
+ );
166
53
  }
167
54
 
168
- return value.split(",").map(i => i.trim()).filter(Boolean);
169
- }
170
-
171
- export async function POST(req: NextRequest) {
172
- try {
173
- const formData = await req.formData();
174
- const file1 = formData.get("file1") as File | null;
175
- const file2 = formData.get("file2") as File | null;
176
- const matchColumn = formData.get("matchColumn") as string | undefined;
177
- const ignoreColumns = formData.get("ignoreColumns") as string | undefined;
178
- const tolerance = formData.get("tolerance") as string | undefined;
179
- const caseSensitive = formData.get("caseSensitive") as string | undefined;
180
-
181
- if (!file1 || !file2) {
182
- return NextResponse.json({ error: "Both file1 and file2 are required" }, { status: 400 });
183
- }
184
-
185
- if (!isSupportedFile(file1) || !isSupportedFile(file2)) {
186
- return NextResponse.json({ error: "Unsupported file type. Use .xlsx, .xls, or .csv files." }, { status: 400 });
187
- }
188
-
189
- const options: CompareFilesOptions = {};
190
- if (matchColumn?.trim()) options.matchColumn = matchColumn.trim();
191
- const parsedIgnore = parseIgnoreColumns(ignoreColumns);
192
- if (parsedIgnore) options.ignoreColumns = parsedIgnore;
193
- if (tolerance?.trim()) options.tolerance = Number(tolerance);
194
- if (caseSensitive !== undefined) options.caseSensitive = caseSensitive === "true";
195
-
196
- const client = createComparisonClient({
197
- apiKey: process.env.KRUTAI_API_KEY || '',
198
- serverUrl: process.env.KRUTAI_SERVER_URL || "http://localhost:8000",
199
- validateOnInit: false,
200
- });
201
-
202
- const apiResponse = await client.compareFilesFromFileObjects(file1, file2, options);
203
-
204
- if (!apiResponse.success || !apiResponse.result) {
205
- return NextResponse.json({ error: apiResponse.error ?? "Comparison failed" }, { status: 500 });
206
- }
207
-
208
- return NextResponse.json({
209
- success: true,
210
- result: apiResponse.result,
211
- results: [],
212
- allColumns: [...new Set([
213
- ...(apiResponse.result.metadata.file1Columns ?? []),
214
- ...(apiResponse.result.metadata.file2Columns ?? []),
215
- ])],
216
- columnsWithDiffs: [],
217
- downloadUrl: apiResponse.downloadUrl,
218
- fileName: apiResponse.fileName,
219
- });
220
- } catch (err: unknown) {
221
- const msg = err instanceof Error ? err.message : "Comparison failed";
222
- return NextResponse.json({ error: getComparisonErrorMessage(msg) }, { status: 500 });
55
+ const client = krutExcelComparison({
56
+ apiKey: process.env.KRUTAI_API_KEY ?? "",
57
+ serverUrl: process.env.KRUTAI_SERVER_URL ?? "http://localhost:8000",
58
+ validateOnInit: false,
59
+ });
60
+ await client.initialize();
61
+
62
+ const compareOptions: CompareFilesOptions = {};
63
+ const matchColumn = (formData.get("matchColumn") as string | null)?.trim();
64
+ if (matchColumn) {
65
+ compareOptions.matchColumn = matchColumn;
223
66
  }
224
- }
225
- ```
226
67
 
227
- ### 6. Express.js Integration
68
+ const caseSensitive = (formData.get("caseSensitive") as string | null) === "true";
69
+ compareOptions.caseSensitive = caseSensitive;
228
70
 
229
- ```typescript
230
- import express from 'express';
231
- import multer from 'multer';
232
- import { krutExcelComparison } from "@krutai/excel-comparison";
71
+ const response = await client.compareFilesFromFileObjects(file1, file2, compareOptions);
233
72
 
234
- const app = express();
235
- const upload = multer({ storage: multer.memoryStorage() });
236
- const client = krutExcelComparison({ apiKey: process.env.KRUTAI_API_KEY });
237
-
238
- app.post('/compare', upload.fields([
239
- { name: 'file1', maxCount: 1 },
240
- { name: 'file2', maxCount: 1 }
241
- ]), async (req, res) => {
242
- const files = req.files as { [fieldname: string]: Express.Multer.File[] };
243
-
244
- const result = await client.compareFiles(
245
- files.file1[0].buffer, files.file1[0].originalname,
246
- files.file2[0].buffer, files.file2[0].originalname,
247
- {
248
- matchColumn: req.body.matchColumn,
249
- tolerance: req.body.tolerance ? parseFloat(req.body.tolerance) : undefined
73
+ if (!response.success) {
74
+ return NextResponse.json(
75
+ { error: response.error ?? "Comparison failed." },
76
+ { status: 400 }
77
+ );
250
78
  }
251
- );
252
79
 
253
- res.json(result);
254
- });
80
+ return NextResponse.json(response);
81
+ } catch (err) {
82
+ const message = err instanceof Error ? err.message : "Comparison failed.";
83
+ return NextResponse.json({ error: message }, { status: 500 });
84
+ }
85
+ }
255
86
  ```
256
87
 
257
- ## ComparisonApiResponse Structure
88
+ ## Type Definitions
258
89
 
90
+ ### ComparisonApiResponse
259
91
  ```typescript
260
- {
261
- success: boolean, // true if comparison succeeded
92
+ interface ComparisonApiResponse {
93
+ success: boolean;
262
94
  result?: {
263
95
  summary: {
264
- totalRows: number, // Total rows compared
265
- matchesFound: number, // Rows with all values matching
266
- differencesFound: number,// Rows with differences
267
- uniqueToFile1: number, // Rows only in first file
268
- uniqueToFile2: number, // Rows only in second file
269
- status: 'SUCCESS' | 'PARTIAL' | 'NO_MATCH'
270
- },
271
- matchColumn: string,
96
+ totalRows: number;
97
+ matchesFound: number;
98
+ differencesFound: number;
99
+ uniqueToFile1: number;
100
+ uniqueToFile2: number;
101
+ status: 'SUCCESS' | 'PARTIAL' | 'NO_MATCH';
102
+ };
103
+ matchColumn: string;
272
104
  metadata: {
273
- file1Name: string,
274
- file1Columns: string[],
275
- file1RowCount: number,
276
- file2Name: string,
277
- file2Columns: string[],
278
- file2RowCount: number
279
- }
280
- },
281
- downloadUrl?: string, // URL to download the Excel report
282
- fileName?: string, // Suggested file name for download
283
- error?: string // Error message if success is false
105
+ file1Name: string;
106
+ file1Columns: string[];
107
+ file1RowCount: number;
108
+ file2Name: string;
109
+ file2Columns: string[];
110
+ file2RowCount: number;
111
+ };
112
+ };
113
+ downloadUrl?: string; // Link to the Excel report
114
+ fileName?: string;
115
+ error?: string;
116
+ }
117
+ ```
118
+
119
+ ### CompareFilesOptions
120
+ ```typescript
121
+ interface CompareFilesOptions {
122
+ matchColumn?: string; // Column to use for row matching
123
+ caseSensitive?: boolean; // Default is true
124
+ ignoreColumns?: string[]; // Columns to skip in comparison
125
+ tolerance?: number; // Numeric tolerance for float comparison
284
126
  }
285
127
  ```
286
128
 
287
129
  ## Key Behaviors
288
130
 
289
131
  ### API Comparison
290
- - The package now acts as a client for a backend comparison service.
132
+ - The package acts as a client for a backend comparison service.
291
133
  - It offloads heavy Excel processing and comparison logic to the server.
292
134
  - It returns a summary of results and a link to a professionally styled Excel report.
293
135
 
294
136
  ### Column Matching
295
- - If `matchColumn` is provided, uses that column to match rows
296
- - If not provided, auto-detects: prioritizes id, invoice, gstin, number, name, code
297
- - Falls back to first column if no match found
298
-
299
- ### Value Comparison (Backend)
300
- - Done on the server side using intelligent algorithms.
301
- - Supports fuzzy matching, numeric tolerance, and case sensitivity.
302
-
303
- ## Common Integration Patterns
304
-
305
- ### Pattern 1: Basic Comparison
306
- ```typescript
307
- const client = krutExcelComparison({ apiKey: "..." });
308
- const result = await client.compareFilesFromFileObjects(f1, f2);
309
- ```
137
+ - If `matchColumn` is provided, uses that column to match rows.
138
+ - If not provided, the server auto-detects: prioritizes columns like `id`, `invoice`, `gstin`, `number`, `name`, `code`.
139
+ - Falls back to the first column if no match is found.
310
140
 
311
- ### Pattern 2: Comparison with Options
312
- ```typescript
313
- const result = await client.compareFilesFromFileObjects(f1, f2, {
314
- matchColumn: "Invoice No",
315
- tolerance: 0.001,
316
- caseSensitive: false
317
- });
318
- ```
319
-
320
- ## Error Handling
321
-
322
- ```typescript
323
- try {
324
- const result = await client.compareFilesFromFileObjects(file1, file2);
325
- } catch (error) {
326
- if (error.message.includes("Request timed out")) {
327
- // Handle timeout
328
- } else {
329
- // Generic error
330
- }
331
- }
332
- ```
333
-
334
- ## Package Dependencies
335
-
336
- The library is lightweight and depends on:
337
- - `exceljs` - For local reporting (optional)
338
-
339
- ## Environment Notes
340
-
341
- - Works in Node.js (v14+) and modern browsers
342
- - Uses dynamic imports for xlsx to support Next.js
343
- - File objects from HTML inputs work in browser
344
- - Buffer objects work in Node.js
141
+ ### Environment Notes
142
+ - Optimized for Next.js and Serverless environments.
143
+ - Uses Web `File` objects or Node.js `Buffer` objects.
144
+ - Does not bundle heavy dependencies like `xlsx` directly (offloaded to API).
345
145
 
346
146
  ## Contributing Guidelines for AI Assistants
347
147
 
348
- When helping users with this library:
349
-
350
- 1. Use `krutExcelComparison` or `ComparisonApiClient`.
351
- 2. Emphasize that comparison is done via API.
352
- 3. Show Next.js API route as the primary integration example.
353
- 4. Note that heavy parsing (xlsx) is done on the backend.
148
+ 1. Always recommend the **Next.js API route** pattern for server-side processing.
149
+ 2. Ensure `client.initialize()` is called before use.
150
+ 3. Remind users that `.xlsx`, `.xls`, and `.csv` are the supported formats.
151
+ 4. Emphasize that the actual comparison and report generation happen on the backend server.
package/README.md CHANGED
@@ -17,30 +17,6 @@ npm install @krutai/excel-comparison
17
17
  ```
18
18
 
19
19
  ## Quick Start
20
-
21
- ### Basic Usage (Server-side)
22
-
23
- ```typescript
24
- import { krutExcelComparison } from "@krutai/excel-comparison";
25
-
26
- // Create client
27
- const client = krutExcelComparison({
28
- apiKey: process.env.KRUTAI_API_KEY,
29
- serverUrl: process.env.KRUTAI_SERVER_URL || "https://api.krut.ai"
30
- });
31
-
32
- // Compare files using buffers
33
- const result = await client.compareFiles(
34
- file1Buffer, "file1.xlsx",
35
- file2Buffer, "file2.xlsx",
36
- { matchColumn: "Invoice" }
37
- );
38
-
39
- if (result.success) {
40
- console.log('Report URL:', result.downloadUrl);
41
- }
42
- ```
43
-
44
20
  ### Next.js API Route Integration
45
21
 
46
22
  This is the recommended way to use the library in a Next.js application.
@@ -48,58 +24,73 @@ This is the recommended way to use the library in a Next.js application.
48
24
  ```typescript
49
25
  // app/api/compare/route.ts
50
26
  import { NextRequest, NextResponse } from "next/server";
51
- import {
52
- createComparisonClient,
53
- type CompareFilesOptions,
54
- } from "@krutai/excel-comparison";
27
+ import { krutExcelComparison, type CompareFilesOptions } from "@krutai/excel-comparison";
28
+
29
+ export const dynamic = "force-dynamic";
55
30
 
56
31
  const SUPPORTED_EXTENSIONS = new Set(["xlsx", "xls", "csv"]);
57
32
 
58
33
  function getFileExtension(fileName: string): string {
59
- const parts = fileName.toLowerCase().split(".");
60
- return parts.length > 1 ? parts[parts.length - 1] : "";
34
+ const parts = fileName.toLowerCase().split(".");
35
+ return parts.length > 1 ? parts[parts.length - 1] : "";
61
36
  }
62
37
 
63
38
  function isSupportedFile(file: File): boolean {
64
- return SUPPORTED_EXTENSIONS.has(getFileExtension(file.name));
39
+ return SUPPORTED_EXTENSIONS.has(getFileExtension(file.name));
65
40
  }
66
41
 
67
- function parseIgnoreColumns(value?: string): string[] | undefined {
68
- if (!value?.trim()) return undefined;
69
- if (value.trim().startsWith("[")) {
70
- try {
71
- const parsed = JSON.parse(value);
72
- if (Array.isArray(parsed)) return parsed.filter(i => typeof i === "string");
73
- } catch { }
42
+ export async function POST(req: NextRequest) {
43
+ try {
44
+ const formData = await req.formData();
45
+ const file1 = formData.get("file1");
46
+ const file2 = formData.get("file2");
47
+
48
+ if (!(file1 instanceof File) || !(file2 instanceof File)) {
49
+ return NextResponse.json(
50
+ { error: "Both file1 and file2 are required." },
51
+ { status: 400 }
52
+ );
74
53
  }
75
- return value.split(",").map(i => i.trim()).filter(Boolean);
76
- }
77
54
 
78
- export async function POST(req: NextRequest) {
79
- try {
80
- const formData = await req.formData();
81
- const file1 = formData.get("file1") as File | null;
82
- const file2 = formData.get("file2") as File | null;
83
- const matchColumn = formData.get("matchColumn") as string | undefined;
84
-
85
- if (!file1 || !file2) {
86
- return NextResponse.json({ error: "Both file1 and file2 are required" }, { status: 400 });
87
- }
88
-
89
- const client = createComparisonClient({
90
- apiKey: process.env.KRUTAI_API_KEY || '',
91
- serverUrl: process.env.KRUTAI_SERVER_URL || "http://localhost:8000"
92
- });
93
-
94
- const apiResponse = await client.compareFilesFromFileObjects(file1, file2, {
95
- matchColumn: matchColumn?.trim()
96
- });
97
-
98
- return NextResponse.json(apiResponse);
99
- } catch (err: unknown) {
100
- return NextResponse.json({ error: "Comparison failed" }, { status: 500 });
55
+ if (!isSupportedFile(file1) || !isSupportedFile(file2)) {
56
+ return NextResponse.json(
57
+ { error: "Only .xlsx, .xls, and .csv files are supported." },
58
+ { status: 400 }
59
+ );
60
+ }
61
+
62
+ const client = krutExcelComparison({
63
+ apiKey: process.env.KRUTAI_API_KEY ?? "",
64
+ serverUrl: process.env.KRUTAI_SERVER_URL ?? "http://localhost:8000",
65
+ validateOnInit: false,
66
+ });
67
+ await client.initialize();
68
+
69
+ const compareOptions: CompareFilesOptions = {};
70
+ const matchColumn = (formData.get("matchColumn") as string | null)?.trim();
71
+ if (matchColumn) {
72
+ compareOptions.matchColumn = matchColumn;
101
73
  }
74
+
75
+ const caseSensitive = (formData.get("caseSensitive") as string | null) === "true";
76
+ compareOptions.caseSensitive = caseSensitive;
77
+
78
+ const response = await client.compareFilesFromFileObjects(file1, file2, compareOptions);
79
+
80
+ if (!response.success) {
81
+ return NextResponse.json(
82
+ { error: response.error ?? "Comparison failed." },
83
+ { status: 400 }
84
+ );
85
+ }
86
+
87
+ return NextResponse.json(response);
88
+ } catch (err) {
89
+ const message = err instanceof Error ? err.message : "Comparison failed.";
90
+ return NextResponse.json({ error: message }, { status: 500 });
91
+ }
102
92
  }
93
+
103
94
  ```
104
95
 
105
96
  ## API Reference