@jackchuka/gql-ingest 1.5.0 → 2.0.1
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 +138 -6
- package/bin/cli.js +53 -52
- package/dist/mapper.d.ts +9 -4
- package/dist/mapper.d.ts.map +1 -1
- package/dist/readers/csv.d.ts +10 -0
- package/dist/readers/csv.d.ts.map +1 -0
- package/dist/readers/csv.test.d.ts +2 -0
- package/dist/readers/csv.test.d.ts.map +1 -0
- package/dist/readers/data-reader.d.ts +21 -0
- package/dist/readers/data-reader.d.ts.map +1 -0
- package/dist/readers/data-reader.test.d.ts +2 -0
- package/dist/readers/data-reader.test.d.ts.map +1 -0
- package/dist/readers/index.d.ts +6 -0
- package/dist/readers/index.d.ts.map +1 -0
- package/dist/readers/json.d.ts +6 -0
- package/dist/readers/json.d.ts.map +1 -0
- package/dist/readers/json.test.d.ts +2 -0
- package/dist/readers/json.test.d.ts.map +1 -0
- package/dist/readers/jsonl.d.ts +6 -0
- package/dist/readers/jsonl.d.ts.map +1 -0
- package/dist/readers/jsonl.test.d.ts +2 -0
- package/dist/readers/jsonl.test.d.ts.map +1 -0
- package/dist/readers/yaml.d.ts +6 -0
- package/dist/readers/yaml.d.ts.map +1 -0
- package/dist/readers/yaml.test.d.ts +2 -0
- package/dist/readers/yaml.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +10 -25
- package/src/graphql-client.test.ts +19 -4
- package/src/mapper.test.ts +115 -64
- package/src/mapper.ts +138 -33
- package/src/{csv-reader.test.ts → readers/csv.test.ts} +1 -1
- package/src/readers/csv.ts +29 -0
- package/src/readers/data-reader.test.ts +104 -0
- package/src/readers/data-reader.ts +61 -0
- package/src/readers/index.ts +18 -0
- package/src/readers/json.test.ts +80 -0
- package/src/readers/json.ts +27 -0
- package/src/readers/jsonl.test.ts +96 -0
- package/src/readers/jsonl.ts +28 -0
- package/src/readers/yaml.test.ts +95 -0
- package/src/readers/yaml.ts +28 -0
- package/dist/csv-reader.d.ts +0 -5
- package/dist/csv-reader.d.ts.map +0 -1
- package/dist/csv-reader.test.d.ts +0 -2
- package/dist/csv-reader.test.d.ts.map +0 -1
- package/src/csv-reader.ts +0 -18
|
@@ -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
|
+
}
|
package/dist/csv-reader.d.ts
DELETED
package/dist/csv-reader.d.ts.map
DELETED
|
@@ -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 +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
|
-
}
|