• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

graphty-org / graphty-element / 16281745178

15 Jul 2025 01:24AM UTC coverage: 70.353% (-2.2%) from 72.536%
16281745178

push

github

apowers313
fix: build and optional argument fixes

735 of 934 branches covered (78.69%)

Branch coverage included in aggregate %.

7 of 8 new or added lines in 2 files covered. (87.5%)

878 existing lines in 24 files now uncovered.

4742 of 6851 relevant lines covered (69.22%)

311.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.06
/src/data/JsonDataSource.ts
1
import jmespath from "jmespath";
3!
2
import {z} from "zod/v4";
3✔
3
import * as z4 from "zod/v4/core";
4

5
// import {JSONParser} from "@streamparser/json";
6
import type {PartiallyOptional} from "../config/common";
7
import {DataSource, DataSourceChunk} from "./DataSource";
3✔
8

9
const JsonNodeConfig = z.strictObject({
3✔
10
    path: z.string().default("nodes"),
3✔
11
    schema: z.custom<z4.$ZodObject>().or(z.null()).default(null),
3✔
12
}).prefault({});
3✔
13

14
const JsonEdgeConfig = z.strictObject({
3✔
15
    path: z.string().default("edges"),
3✔
16
    schema: z.custom<z4.$ZodObject>().or(z.null()).default(null),
3✔
17
}).prefault({});
3✔
18

19
export const JsonDataSourceConfig = z.object({
3✔
20
    data: z.string(),
3✔
21
    node: JsonNodeConfig,
3✔
22
    edge: JsonEdgeConfig,
3✔
23
});
3✔
24

25
export type JsonDataSourceConfigType = z.infer<typeof JsonDataSourceConfig>;
26
export type JsonDataSourceConfigOpts = PartiallyOptional<JsonDataSourceConfigType, "node" | "edge">;
27

28
// @DataSource.register
29
export class JsonDataSource extends DataSource {
3✔
30
    static type = "json";
3✔
31
    url: string;
32
    opts: JsonDataSourceConfigType;
33

34
    constructor(anyOpts: object) {
3✔
35
        super();
23✔
36

37
        const opts = JsonDataSourceConfig.parse(anyOpts);
23✔
38
        this.opts = opts;
23✔
39
        if (opts.node.schema) {
23✔
40
            this.nodeSchema = opts.node.schema;
4✔
41
        }
4✔
42

43
        if (opts.edge.schema) {
23!
44
            this.edgeSchema = opts.edge.schema;
×
45
        }
×
46

47
        this.url = opts.data;
23✔
48
    }
23✔
49

50
    private async fetchWithRetry(url: string, retries = 3, timeout = 30000): Promise<Response> {
3✔
51
        for (let attempt = 0; attempt < retries; attempt++) {
21✔
52
            try {
21✔
53
                // Create an AbortController for timeout
54
                const controller = new AbortController();
21✔
55
                const timeoutId = setTimeout(() => {
21✔
UNCOV
56
                    controller.abort();
×
57
                }, timeout);
21✔
58

59
                try {
21✔
60
                    const response = await fetch(url, {signal: controller.signal});
21✔
61
                    clearTimeout(timeoutId);
20✔
62

63
                    if (!response.ok) {
21!
UNCOV
64
                        throw new Error(`HTTP error! status: ${response.status}`);
×
65
                    }
✔
66

67
                    return response;
20✔
68
                } catch (error) {
21!
UNCOV
69
                    clearTimeout(timeoutId);
×
70

UNCOV
71
                    if (error instanceof Error && error.name === "AbortError") {
×
UNCOV
72
                        throw new Error(`Request timeout after ${timeout}ms`);
×
UNCOV
73
                    }
×
74

UNCOV
75
                    throw error;
×
UNCOV
76
                }
×
77
            } catch (error) {
21!
UNCOV
78
                const isLastAttempt = attempt === retries - 1;
×
79

UNCOV
80
                if (isLastAttempt) {
×
UNCOV
81
                    throw new Error(`Failed to fetch data after ${retries} attempts: ${error instanceof Error ? error.message : String(error)}`);
×
UNCOV
82
                }
×
83

84
                // Exponential backoff: wait 1s, 2s, 4s...
UNCOV
85
                const delay = Math.pow(2, attempt) * 1000;
×
UNCOV
86
                await new Promise((resolve) => setTimeout(resolve, delay));
×
UNCOV
87
            }
×
88
        }
21!
89

90
        // This should never be reached due to the throw in the last attempt
UNCOV
91
        throw new Error("Unexpected error in fetchWithRetry");
×
92
    }
21✔
93

94
    async *sourceFetchData(): AsyncGenerator<DataSourceChunk, void, unknown> {
3✔
95
        let response: Response;
21✔
96
        let data: unknown;
21✔
97

98
        try {
21✔
99
            response = await this.fetchWithRetry(this.url);
21✔
100
        } catch (error) {
21!
UNCOV
101
            throw new Error(`Failed to fetch data from ${this.url}: ${error instanceof Error ? error.message : String(error)}`);
×
UNCOV
102
        }
✔
103

104
        if (!response.body) {
21!
UNCOV
105
            throw new Error("JSON response had no body");
×
UNCOV
106
        }
✔
107

108
        try {
20✔
109
            data = await response.json();
20✔
110
        } catch (error) {
21!
UNCOV
111
            throw new Error(`Failed to parse JSON response: ${error instanceof Error ? error.message : String(error)}`);
×
UNCOV
112
        }
✔
113

114
        let nodes: unknown;
20✔
115
        let edges: unknown;
20✔
116

117
        try {
20✔
118
            nodes = jmespath.search(data, this.opts.node.path);
20✔
119
        } catch (error) {
21!
UNCOV
120
            throw new Error(`Failed to extract nodes using path '${this.opts.node.path}': ${error instanceof Error ? error.message : String(error)}`);
×
UNCOV
121
        }
✔
122

123
        if (!Array.isArray(nodes)) {
21!
UNCOV
124
            throw new TypeError(`JsonDataProvider expected 'nodes' at path '${this.opts.node.path}' to be an array of objects, got ${typeof nodes}`);
×
UNCOV
125
        }
✔
126

127
        try {
20✔
128
            edges = jmespath.search(data, this.opts.edge.path);
20✔
129
        } catch (error) {
21!
UNCOV
130
            throw new Error(`Failed to extract edges using path '${this.opts.edge.path}': ${error instanceof Error ? error.message : String(error)}`);
×
UNCOV
131
        }
✔
132

133
        if (!Array.isArray(edges)) {
21!
UNCOV
134
            throw new TypeError(`JsonDataProvider expected 'edges' at path '${this.opts.edge.path}' to be an array of objects, got ${typeof edges}`);
×
UNCOV
135
        }
✔
136

137
        yield {nodes, edges};
20✔
138
    }
21✔
139
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc