@jackchuka/gql-ingest 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -6
  3. package/bin/cli.js +55 -52
  4. package/dist/dependency-resolver.d.ts +2 -1
  5. package/dist/dependency-resolver.d.ts.map +1 -1
  6. package/dist/mapper.d.ts +10 -5
  7. package/dist/mapper.d.ts.map +1 -1
  8. package/dist/metrics.d.ts.map +1 -1
  9. package/dist/readers/csv.d.ts +10 -0
  10. package/dist/readers/csv.d.ts.map +1 -0
  11. package/dist/readers/csv.test.d.ts +2 -0
  12. package/dist/readers/csv.test.d.ts.map +1 -0
  13. package/dist/readers/data-reader.d.ts +21 -0
  14. package/dist/readers/data-reader.d.ts.map +1 -0
  15. package/dist/readers/data-reader.test.d.ts +2 -0
  16. package/dist/readers/data-reader.test.d.ts.map +1 -0
  17. package/dist/readers/index.d.ts +6 -0
  18. package/dist/readers/index.d.ts.map +1 -0
  19. package/dist/readers/json.d.ts +6 -0
  20. package/dist/readers/json.d.ts.map +1 -0
  21. package/dist/readers/json.test.d.ts +2 -0
  22. package/dist/readers/json.test.d.ts.map +1 -0
  23. package/dist/readers/jsonl.d.ts +6 -0
  24. package/dist/readers/jsonl.d.ts.map +1 -0
  25. package/dist/readers/jsonl.test.d.ts +2 -0
  26. package/dist/readers/jsonl.test.d.ts.map +1 -0
  27. package/dist/readers/yaml.d.ts +6 -0
  28. package/dist/readers/yaml.d.ts.map +1 -0
  29. package/dist/readers/yaml.test.d.ts +2 -0
  30. package/dist/readers/yaml.test.d.ts.map +1 -0
  31. package/package.json +1 -1
  32. package/src/cli.ts +49 -8
  33. package/src/dependency-resolver.test.ts +15 -1
  34. package/src/dependency-resolver.ts +6 -2
  35. package/src/graphql-client.test.ts +19 -4
  36. package/src/mapper.test.ts +115 -64
  37. package/src/mapper.ts +176 -32
  38. package/src/metrics.ts +18 -10
  39. package/src/{csv-reader.test.ts → readers/csv.test.ts} +1 -1
  40. package/src/readers/csv.ts +29 -0
  41. package/src/readers/data-reader.test.ts +104 -0
  42. package/src/readers/data-reader.ts +61 -0
  43. package/src/readers/index.ts +18 -0
  44. package/src/readers/json.test.ts +80 -0
  45. package/src/readers/json.ts +27 -0
  46. package/src/readers/jsonl.test.ts +96 -0
  47. package/src/readers/jsonl.ts +28 -0
  48. package/src/readers/yaml.test.ts +95 -0
  49. package/src/readers/yaml.ts +28 -0
  50. package/dist/csv-reader.d.ts +0 -5
  51. package/dist/csv-reader.d.ts.map +0 -1
  52. package/dist/csv-reader.test.d.ts +0 -2
  53. package/dist/csv-reader.test.d.ts.map +0 -1
  54. package/src/csv-reader.ts +0 -18
package/src/metrics.ts CHANGED
@@ -119,35 +119,43 @@ export class MetricsCollector {
119
119
  const duration = this.getDurationMs();
120
120
  const successRate = this.getSuccessRate();
121
121
  const avgRequestDuration = this.getAverageRequestDuration();
122
-
122
+
123
123
  let summary = `\nšŸ“Š Processing Summary:\n`;
124
124
  summary += ` Total Processed: ${this.metrics.totalEntities}\n`;
125
125
  summary += ` āœ“ Successes: ${this.metrics.totalSuccesses}\n`;
126
126
  summary += ` āœ— Failures: ${this.metrics.totalFailures}\n`;
127
127
  summary += ` Success Rate: ${successRate.toFixed(1)}%\n`;
128
128
  summary += ` Duration: ${(duration / 1000).toFixed(2)}s\n`;
129
-
129
+
130
130
  if (this.metrics.requestDurations.length > 0) {
131
131
  summary += ` Avg Request Time: ${avgRequestDuration.toFixed(0)}ms\n`;
132
132
  }
133
-
133
+
134
134
  if (this.metrics.retryAttempts > 0) {
135
135
  summary += ` Retry Attempts: ${this.metrics.retryAttempts}\n`;
136
136
  summary += ` Retry Successes: ${this.metrics.retrySuccesses}\n`;
137
137
  summary += ` Retry Failures: ${this.metrics.retryFailures}\n`;
138
138
  }
139
139
 
140
- if (this.metrics.entityMetrics.size > 1) {
140
+ if (this.metrics.entityMetrics.size > 0) {
141
141
  summary += `\nšŸ“‹ Per-Entity Breakdown:\n`;
142
142
  for (const [entityName, entityMetric] of this.metrics.entityMetrics) {
143
- const entityTotal = entityMetric.successCount + entityMetric.failureCount;
144
- const entityRate = entityTotal > 0 ? (entityMetric.successCount / entityTotal) * 100 : 0;
145
- const entityDuration = entityMetric.endTime ? entityMetric.endTime - entityMetric.startTime : 0;
146
-
147
- summary += ` ${entityName}: ${entityTotal} total (${entityMetric.successCount} āœ“, ${entityMetric.failureCount} āœ—) - ${entityRate.toFixed(1)}% success - ${(entityDuration / 1000).toFixed(2)}s\n`;
143
+ const entityTotal =
144
+ entityMetric.successCount + entityMetric.failureCount;
145
+ const entityRate =
146
+ entityTotal > 0 ? (entityMetric.successCount / entityTotal) * 100 : 0;
147
+ const entityDuration = entityMetric.endTime
148
+ ? entityMetric.endTime - entityMetric.startTime
149
+ : 0;
150
+
151
+ summary += ` ${entityName}: ${entityTotal} total (${
152
+ entityMetric.successCount
153
+ } āœ“, ${entityMetric.failureCount} āœ—) - ${entityRate.toFixed(
154
+ 1
155
+ )}% success - ${(entityDuration / 1000).toFixed(2)}s\n`;
148
156
  }
149
157
  }
150
158
 
151
159
  return summary;
152
160
  }
153
- }
161
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { readCsvFile } from "./csv-reader";
3
+ import { readCsvFile } from "./csv";
4
4
 
5
5
  describe("CSV Reader", () => {
6
6
  const testDataDir = path.join(__dirname, "test-data");
@@ -0,0 +1,29 @@
1
+ import fs from "fs";
2
+ import csv from "csv-parser";
3
+ import { DataReader, DataRow } from "./data-reader";
4
+
5
+ export interface CsvRow {
6
+ [key: string]: string;
7
+ }
8
+
9
+ export async function readCsvFile(filePath: string): Promise<CsvRow[]> {
10
+ return new Promise((resolve, reject) => {
11
+ const results: CsvRow[] = [];
12
+
13
+ fs.createReadStream(filePath)
14
+ .pipe(csv())
15
+ .on("data", (data) => results.push(data))
16
+ .on("end", () => resolve(results))
17
+ .on("error", (error) => reject(error));
18
+ });
19
+ }
20
+
21
+ export class CsvReader extends DataReader {
22
+ getSupportedExtensions(): string[] {
23
+ return ["csv"];
24
+ }
25
+
26
+ async readFile(filePath: string): Promise<DataRow[]> {
27
+ return readCsvFile(filePath);
28
+ }
29
+ }
@@ -0,0 +1,104 @@
1
+ import { DataReader, DataReaderFactory } from "./data-reader";
2
+
3
+ class TestReader extends DataReader {
4
+ getSupportedExtensions(): string[] {
5
+ return ["test", "tst"];
6
+ }
7
+
8
+ async readFile(filePath: string): Promise<any[]> {
9
+ return [{ test: true }];
10
+ }
11
+ }
12
+
13
+ describe("DataReader", () => {
14
+ describe("canHandle", () => {
15
+ it("should check if reader can handle file based on extension", () => {
16
+ const reader = new TestReader();
17
+
18
+ expect(reader.canHandle("file.test")).toBe(true);
19
+ expect(reader.canHandle("file.tst")).toBe(true);
20
+ expect(reader.canHandle("path/to/file.test")).toBe(true);
21
+ expect(reader.canHandle("file.other")).toBe(false);
22
+ expect(reader.canHandle("file")).toBe(false);
23
+ });
24
+ });
25
+ });
26
+
27
+ describe("DataReaderFactory", () => {
28
+ beforeEach(() => {
29
+ // Clear the readers array before each test
30
+ (DataReaderFactory as any).readers = [];
31
+ });
32
+
33
+ describe("registerReader", () => {
34
+ it("should register a reader", () => {
35
+ const reader = new TestReader();
36
+ DataReaderFactory.registerReader(reader);
37
+
38
+ expect(DataReaderFactory.getSupportedFormats()).toContain("test");
39
+ expect(DataReaderFactory.getSupportedFormats()).toContain("tst");
40
+ });
41
+ });
42
+
43
+ describe("getReader", () => {
44
+ beforeEach(() => {
45
+ const reader = new TestReader();
46
+ DataReaderFactory.registerReader(reader);
47
+ });
48
+
49
+ it("should get reader by file extension", () => {
50
+ const reader = DataReaderFactory.getReader("file.test");
51
+ expect(reader).toBeInstanceOf(TestReader);
52
+ });
53
+
54
+ it("should get reader by format override", () => {
55
+ const reader = DataReaderFactory.getReader("file.other", "test");
56
+ expect(reader).toBeInstanceOf(TestReader);
57
+ });
58
+
59
+ it("should prioritize format override over file extension", () => {
60
+ const reader = DataReaderFactory.getReader("file.unknown", "tst");
61
+ expect(reader).toBeInstanceOf(TestReader);
62
+ });
63
+
64
+ it("should throw error when no reader found", () => {
65
+ expect(() => DataReaderFactory.getReader("file.unknown")).toThrow(
66
+ "No reader found for file: file.unknown"
67
+ );
68
+ });
69
+
70
+ it("should throw error when format specified but no reader found", () => {
71
+ expect(() => DataReaderFactory.getReader("file.txt", "unknown")).toThrow(
72
+ "No reader found for file: file.txt with format: unknown"
73
+ );
74
+ });
75
+ });
76
+
77
+ describe("getSupportedFormats", () => {
78
+ it("should return empty array when no readers registered", () => {
79
+ expect(DataReaderFactory.getSupportedFormats()).toEqual([]);
80
+ });
81
+
82
+ it("should return all supported formats", () => {
83
+ const reader1 = new TestReader();
84
+ DataReaderFactory.registerReader(reader1);
85
+
86
+ const formats = DataReaderFactory.getSupportedFormats();
87
+ expect(formats).toContain("test");
88
+ expect(formats).toContain("tst");
89
+ expect(formats.length).toBe(2);
90
+ });
91
+
92
+ it("should not duplicate formats", () => {
93
+ const reader1 = new TestReader();
94
+ const reader2 = new TestReader();
95
+ DataReaderFactory.registerReader(reader1);
96
+ DataReaderFactory.registerReader(reader2);
97
+
98
+ const formats = DataReaderFactory.getSupportedFormats();
99
+ expect(formats).toContain("test");
100
+ expect(formats).toContain("tst");
101
+ expect(formats.length).toBe(2);
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,61 @@
1
+ export interface DataRow {
2
+ [key: string]: any;
3
+ }
4
+
5
+ export abstract class DataReader {
6
+ abstract readFile(filePath: string): Promise<DataRow[]>;
7
+
8
+ /**
9
+ * Get the supported file extensions for this reader
10
+ */
11
+ abstract getSupportedExtensions(): string[];
12
+
13
+ /**
14
+ * Check if this reader can handle the given file
15
+ */
16
+ canHandle(filePath: string): boolean {
17
+ const extension = filePath.split(".").pop()?.toLowerCase();
18
+ return extension
19
+ ? this.getSupportedExtensions().includes(extension)
20
+ : false;
21
+ }
22
+ }
23
+
24
+ export class DataReaderFactory {
25
+ private static readers: DataReader[] = [];
26
+
27
+ static registerReader(reader: DataReader): void {
28
+ this.readers.push(reader);
29
+ }
30
+
31
+ static getReader(filePath: string, format?: string): DataReader {
32
+ // If format is specified, try to find reader by format
33
+ if (format) {
34
+ const reader = this.readers.find((r) =>
35
+ r.getSupportedExtensions().includes(format.toLowerCase())
36
+ );
37
+ if (reader) return reader;
38
+ }
39
+
40
+ // Otherwise, try to find reader by file extension
41
+ const reader = this.readers.find((r) => r.canHandle(filePath));
42
+
43
+ if (!reader) {
44
+ throw new Error(
45
+ `No reader found for file: ${filePath}${
46
+ format ? ` with format: ${format}` : ""
47
+ }`
48
+ );
49
+ }
50
+
51
+ return reader;
52
+ }
53
+
54
+ static getSupportedFormats(): string[] {
55
+ const formats = new Set<string>();
56
+ this.readers.forEach((reader) => {
57
+ reader.getSupportedExtensions().forEach((ext) => formats.add(ext));
58
+ });
59
+ return Array.from(formats);
60
+ }
61
+ }
@@ -0,0 +1,18 @@
1
+ export { DataReader, DataRow, DataReaderFactory } from "./data-reader";
2
+ export { CsvReader, readCsvFile, CsvRow } from "./csv";
3
+ export { JsonReader } from "./json";
4
+ export { YamlReader } from "./yaml";
5
+ export { JsonlReader } from "./jsonl";
6
+
7
+ // Register all readers
8
+ import { DataReaderFactory } from "./data-reader";
9
+ import { CsvReader } from "./csv";
10
+ import { JsonReader } from "./json";
11
+ import { YamlReader } from "./yaml";
12
+ import { JsonlReader } from "./jsonl";
13
+
14
+ // Register readers on module load
15
+ DataReaderFactory.registerReader(new CsvReader());
16
+ DataReaderFactory.registerReader(new JsonReader());
17
+ DataReaderFactory.registerReader(new YamlReader());
18
+ DataReaderFactory.registerReader(new JsonlReader());
@@ -0,0 +1,80 @@
1
+ import fs from "fs/promises";
2
+ import { JsonReader } from "./json";
3
+
4
+ jest.mock("fs/promises");
5
+
6
+ describe("JsonReader", () => {
7
+ let reader: JsonReader;
8
+ const mockFs = fs as jest.Mocked<typeof fs>;
9
+
10
+ beforeEach(() => {
11
+ reader = new JsonReader();
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ describe("getSupportedExtensions", () => {
16
+ it("should return json as supported extension", () => {
17
+ expect(reader.getSupportedExtensions()).toEqual(["json"]);
18
+ });
19
+ });
20
+
21
+ describe("canHandle", () => {
22
+ it("should return true for .json files", () => {
23
+ expect(reader.canHandle("data.json")).toBe(true);
24
+ expect(reader.canHandle("path/to/file.json")).toBe(true);
25
+ expect(reader.canHandle("file.JSON")).toBe(true); // case insensitive
26
+ });
27
+
28
+ it("should return false for non-json files", () => {
29
+ expect(reader.canHandle("data.csv")).toBe(false);
30
+ expect(reader.canHandle("data.yaml")).toBe(false);
31
+ expect(reader.canHandle("data")).toBe(false);
32
+ });
33
+ });
34
+
35
+ describe("readFile", () => {
36
+ it("should read and parse JSON array", async () => {
37
+ const mockData = [
38
+ { id: 1, name: "Item 1" },
39
+ { id: 2, name: "Item 2" },
40
+ ];
41
+ mockFs.readFile.mockResolvedValue(JSON.stringify(mockData));
42
+
43
+ const result = await reader.readFile("data.json");
44
+
45
+ expect(mockFs.readFile).toHaveBeenCalledWith("data.json", "utf8");
46
+ expect(result).toEqual(mockData);
47
+ });
48
+
49
+ it("should wrap single object in array", async () => {
50
+ const mockData = { id: 1, name: "Item 1" };
51
+ mockFs.readFile.mockResolvedValue(JSON.stringify(mockData));
52
+
53
+ const result = await reader.readFile("data.json");
54
+
55
+ expect(result).toEqual([mockData]);
56
+ });
57
+
58
+ it("should throw error for invalid JSON", async () => {
59
+ mockFs.readFile.mockResolvedValue("invalid json");
60
+
61
+ await expect(reader.readFile("data.json")).rejects.toThrow();
62
+ });
63
+
64
+ it("should throw error for null data", async () => {
65
+ mockFs.readFile.mockResolvedValue("null");
66
+
67
+ await expect(reader.readFile("data.json")).rejects.toThrow(
68
+ "Invalid JSON data structure in file: data.json. Expected array or object."
69
+ );
70
+ });
71
+
72
+ it("should throw error for primitive values", async () => {
73
+ mockFs.readFile.mockResolvedValue('"string value"');
74
+
75
+ await expect(reader.readFile("data.json")).rejects.toThrow(
76
+ "Invalid JSON data structure in file: data.json. Expected array or object."
77
+ );
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,27 @@
1
+ import fs from "fs/promises";
2
+ import { DataReader, DataRow } from "./data-reader";
3
+
4
+ export class JsonReader extends DataReader {
5
+ getSupportedExtensions(): string[] {
6
+ return ["json"];
7
+ }
8
+
9
+ async readFile(filePath: string): Promise<DataRow[]> {
10
+ const content = await fs.readFile(filePath, "utf8");
11
+ const data = JSON.parse(content);
12
+
13
+ // If the data is already an array, return it
14
+ if (Array.isArray(data)) {
15
+ return data;
16
+ }
17
+
18
+ // If it's a single object, wrap it in an array
19
+ if (typeof data === "object" && data !== null) {
20
+ return [data];
21
+ }
22
+
23
+ throw new Error(
24
+ `Invalid JSON data structure in file: ${filePath}. Expected array or object.`
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,96 @@
1
+ import fs from "fs/promises";
2
+ import { JsonlReader } from "./jsonl";
3
+
4
+ jest.mock("fs/promises");
5
+
6
+ describe("JsonlReader", () => {
7
+ let reader: JsonlReader;
8
+ const mockFs = fs as jest.Mocked<typeof fs>;
9
+
10
+ beforeEach(() => {
11
+ reader = new JsonlReader();
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ describe("getSupportedExtensions", () => {
16
+ it("should return jsonl and ndjson as supported extensions", () => {
17
+ expect(reader.getSupportedExtensions()).toEqual(["jsonl", "ndjson"]);
18
+ });
19
+ });
20
+
21
+ describe("canHandle", () => {
22
+ it("should return true for .jsonl and .ndjson files", () => {
23
+ expect(reader.canHandle("data.jsonl")).toBe(true);
24
+ expect(reader.canHandle("data.ndjson")).toBe(true);
25
+ expect(reader.canHandle("path/to/file.jsonl")).toBe(true);
26
+ });
27
+
28
+ it("should return false for non-jsonl files", () => {
29
+ expect(reader.canHandle("data.json")).toBe(false);
30
+ expect(reader.canHandle("data.csv")).toBe(false);
31
+ expect(reader.canHandle("data")).toBe(false);
32
+ });
33
+ });
34
+
35
+ describe("readFile", () => {
36
+ it("should read and parse JSONL file", async () => {
37
+ const line1 = { id: 1, name: "Item 1" };
38
+ const line2 = { id: 2, name: "Item 2" };
39
+ const jsonlContent = `${JSON.stringify(line1)}\n${JSON.stringify(line2)}`;
40
+ mockFs.readFile.mockResolvedValue(jsonlContent);
41
+
42
+ const result = await reader.readFile("data.jsonl");
43
+
44
+ expect(mockFs.readFile).toHaveBeenCalledWith("data.jsonl", "utf8");
45
+ expect(result).toEqual([line1, line2]);
46
+ });
47
+
48
+ it("should handle empty lines", async () => {
49
+ const line1 = { id: 1, name: "Item 1" };
50
+ const line2 = { id: 2, name: "Item 2" };
51
+ const jsonlContent = `${JSON.stringify(line1)}\n\n${JSON.stringify(
52
+ line2
53
+ )}\n`;
54
+ mockFs.readFile.mockResolvedValue(jsonlContent);
55
+
56
+ const result = await reader.readFile("data.jsonl");
57
+
58
+ expect(result).toEqual([line1, line2]);
59
+ });
60
+
61
+ it("should handle single line", async () => {
62
+ const line = { id: 1, name: "Item 1" };
63
+ mockFs.readFile.mockResolvedValue(JSON.stringify(line));
64
+
65
+ const result = await reader.readFile("data.jsonl");
66
+
67
+ expect(result).toEqual([line]);
68
+ });
69
+
70
+ it("should throw error for invalid JSON on specific line", async () => {
71
+ const line1 = { id: 1, name: "Item 1" };
72
+ const jsonlContent = `${JSON.stringify(line1)}\ninvalid json\n`;
73
+ mockFs.readFile.mockResolvedValue(jsonlContent);
74
+
75
+ await expect(reader.readFile("data.jsonl")).rejects.toThrow(
76
+ "Invalid JSON at line 2 in file: data.jsonl"
77
+ );
78
+ });
79
+
80
+ it("should handle empty file", async () => {
81
+ mockFs.readFile.mockResolvedValue("");
82
+
83
+ const result = await reader.readFile("data.jsonl");
84
+
85
+ expect(result).toEqual([]);
86
+ });
87
+
88
+ it("should handle file with only whitespace", async () => {
89
+ mockFs.readFile.mockResolvedValue("\n\n \n\t\n");
90
+
91
+ const result = await reader.readFile("data.jsonl");
92
+
93
+ expect(result).toEqual([]);
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,28 @@
1
+ import fs from "fs/promises";
2
+ import { DataReader, DataRow } from "./data-reader";
3
+
4
+ export class JsonlReader extends DataReader {
5
+ getSupportedExtensions(): string[] {
6
+ return ["jsonl", "ndjson"];
7
+ }
8
+
9
+ async readFile(filePath: string): Promise<DataRow[]> {
10
+ const content = await fs.readFile(filePath, "utf8");
11
+ const lines = content.split("\n").filter((line) => line.trim());
12
+
13
+ const results: DataRow[] = [];
14
+
15
+ for (let i = 0; i < lines.length; i++) {
16
+ try {
17
+ const data = JSON.parse(lines[i]);
18
+ results.push(data);
19
+ } catch (error) {
20
+ throw new Error(
21
+ `Invalid JSON at line ${i + 1} in file: ${filePath}. Error: ${error}`
22
+ );
23
+ }
24
+ }
25
+
26
+ return results;
27
+ }
28
+ }
@@ -0,0 +1,95 @@
1
+ import fs from "fs/promises";
2
+ import yaml from "js-yaml";
3
+ import { YamlReader } from "./yaml";
4
+
5
+ jest.mock("fs/promises");
6
+ jest.mock("js-yaml");
7
+
8
+ describe("YamlReader", () => {
9
+ let reader: YamlReader;
10
+ const mockFs = fs as jest.Mocked<typeof fs>;
11
+ const mockYaml = yaml as jest.Mocked<typeof yaml>;
12
+
13
+ beforeEach(() => {
14
+ reader = new YamlReader();
15
+ jest.clearAllMocks();
16
+ });
17
+
18
+ describe("getSupportedExtensions", () => {
19
+ it("should return yaml and yml as supported extensions", () => {
20
+ expect(reader.getSupportedExtensions()).toEqual(["yaml", "yml"]);
21
+ });
22
+ });
23
+
24
+ describe("canHandle", () => {
25
+ it("should return true for .yaml and .yml files", () => {
26
+ expect(reader.canHandle("data.yaml")).toBe(true);
27
+ expect(reader.canHandle("data.yml")).toBe(true);
28
+ expect(reader.canHandle("path/to/file.yaml")).toBe(true);
29
+ });
30
+
31
+ it("should return false for non-yaml files", () => {
32
+ expect(reader.canHandle("data.json")).toBe(false);
33
+ expect(reader.canHandle("data.csv")).toBe(false);
34
+ expect(reader.canHandle("data")).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe("readFile", () => {
39
+ it("should read and parse YAML array", async () => {
40
+ const mockData = [
41
+ { id: 1, name: "Item 1" },
42
+ { id: 2, name: "Item 2" },
43
+ ];
44
+ const yamlContent = "- id: 1\n name: Item 1\n- id: 2\n name: Item 2";
45
+ mockFs.readFile.mockResolvedValue(yamlContent);
46
+ mockYaml.load.mockReturnValue(mockData);
47
+
48
+ const result = await reader.readFile("data.yaml");
49
+
50
+ expect(mockFs.readFile).toHaveBeenCalledWith("data.yaml", "utf8");
51
+ expect(mockYaml.load).toHaveBeenCalledWith(yamlContent);
52
+ expect(result).toEqual(mockData);
53
+ });
54
+
55
+ it("should wrap single object in array", async () => {
56
+ const mockData = { id: 1, name: "Item 1" };
57
+ const yamlContent = "id: 1\nname: Item 1";
58
+ mockFs.readFile.mockResolvedValue(yamlContent);
59
+ mockYaml.load.mockReturnValue(mockData);
60
+
61
+ const result = await reader.readFile("data.yaml");
62
+
63
+ expect(result).toEqual([mockData]);
64
+ });
65
+
66
+ it("should throw error for invalid YAML", async () => {
67
+ mockFs.readFile.mockResolvedValue("invalid: yaml: content:");
68
+ mockYaml.load.mockImplementation(() => {
69
+ throw new Error("Invalid YAML");
70
+ });
71
+
72
+ await expect(reader.readFile("data.yaml")).rejects.toThrow(
73
+ "Invalid YAML"
74
+ );
75
+ });
76
+
77
+ it("should throw error for null data", async () => {
78
+ mockFs.readFile.mockResolvedValue("null");
79
+ mockYaml.load.mockReturnValue(null);
80
+
81
+ await expect(reader.readFile("data.yaml")).rejects.toThrow(
82
+ "Invalid YAML data structure in file: data.yaml. Expected array or object."
83
+ );
84
+ });
85
+
86
+ it("should throw error for primitive values", async () => {
87
+ mockFs.readFile.mockResolvedValue("string value");
88
+ mockYaml.load.mockReturnValue("string value");
89
+
90
+ await expect(reader.readFile("data.yaml")).rejects.toThrow(
91
+ "Invalid YAML data structure in file: data.yaml. Expected array or object."
92
+ );
93
+ });
94
+ });
95
+ });
@@ -0,0 +1,28 @@
1
+ import fs from "fs/promises";
2
+ import yaml from "js-yaml";
3
+ import { DataReader, DataRow } from "./data-reader";
4
+
5
+ export class YamlReader extends DataReader {
6
+ getSupportedExtensions(): string[] {
7
+ return ["yaml", "yml"];
8
+ }
9
+
10
+ async readFile(filePath: string): Promise<DataRow[]> {
11
+ const content = await fs.readFile(filePath, "utf8");
12
+ const data = yaml.load(content);
13
+
14
+ // If the data is already an array, return it
15
+ if (Array.isArray(data)) {
16
+ return data;
17
+ }
18
+
19
+ // If it's a single object, wrap it in an array
20
+ if (typeof data === "object" && data !== null) {
21
+ return [data];
22
+ }
23
+
24
+ throw new Error(
25
+ `Invalid YAML data structure in file: ${filePath}. Expected array or object.`
26
+ );
27
+ }
28
+ }
@@ -1,5 +0,0 @@
1
- export interface CsvRow {
2
- [key: string]: string;
3
- }
4
- export declare function readCsvFile(filePath: string): Promise<CsvRow[]>;
5
- //# sourceMappingURL=csv-reader.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"csv-reader.d.ts","sourceRoot":"","sources":["../src/csv-reader.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUrE"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=csv-reader.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"csv-reader.test.d.ts","sourceRoot":"","sources":["../src/csv-reader.test.ts"],"names":[],"mappings":""}
package/src/csv-reader.ts DELETED
@@ -1,18 +0,0 @@
1
- import fs from 'fs';
2
- import csv from 'csv-parser';
3
-
4
- export interface CsvRow {
5
- [key: string]: string;
6
- }
7
-
8
- export async function readCsvFile(filePath: string): Promise<CsvRow[]> {
9
- return new Promise((resolve, reject) => {
10
- const results: CsvRow[] = [];
11
-
12
- fs.createReadStream(filePath)
13
- .pipe(csv())
14
- .on('data', (data) => results.push(data))
15
- .on('end', () => resolve(results))
16
- .on('error', (error) => reject(error));
17
- });
18
- }