@jackchuka/gql-ingest 1.4.0 ā 1.5.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/LICENSE +21 -0
- package/README.md +24 -0
- package/bin/cli.js +21 -19
- package/dist/dependency-resolver.d.ts +2 -1
- package/dist/dependency-resolver.d.ts.map +1 -1
- package/dist/mapper.d.ts +1 -1
- package/dist/mapper.d.ts.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +43 -7
- package/src/dependency-resolver.test.ts +15 -1
- package/src/dependency-resolver.ts +6 -2
- package/src/mapper.ts +44 -5
- package/src/metrics.ts +18 -10
|
@@ -8,7 +8,8 @@ export interface ExecutionWave {
|
|
|
8
8
|
export declare class DependencyResolver {
|
|
9
9
|
private dependencies;
|
|
10
10
|
private entities;
|
|
11
|
-
|
|
11
|
+
private allowPartialResolution;
|
|
12
|
+
constructor(entities: string[], dependencies?: DependencyGraph, allowPartialResolution?: boolean);
|
|
12
13
|
resolveExecutionOrder(): ExecutionWave[];
|
|
13
14
|
validateDependencies(): string[];
|
|
14
15
|
getDependents(entityName: string): string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../src/dependency-resolver.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAW;
|
|
1
|
+
{"version":3,"file":"dependency-resolver.d.ts","sourceRoot":"","sources":["../src/dependency-resolver.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,sBAAsB,CAAU;gBAE5B,QAAQ,EAAE,MAAM,EAAE,EAAE,YAAY,GAAE,eAAoB,EAAE,sBAAsB,GAAE,OAAe;IAM3G,qBAAqB,IAAI,aAAa,EAAE;IAgDxC,oBAAoB,IAAI,MAAM,EAAE;IAwBhC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAM3C,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;CAG9C"}
|
package/dist/mapper.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare class DataMapper {
|
|
|
12
12
|
private metrics;
|
|
13
13
|
private verbose;
|
|
14
14
|
constructor(client: GraphQLClientWrapper, basePath?: string, metrics?: MetricsCollector, verbose?: boolean);
|
|
15
|
-
discoverMappings(configDir: string): string[];
|
|
15
|
+
discoverMappings(configDir: string, entityFilter?: string[]): string[];
|
|
16
16
|
processEntity(configPath: string, parallelConfig?: ParallelProcessingConfig, retryConfig?: RetryConfig): Promise<void>;
|
|
17
17
|
private processRowsSequentially;
|
|
18
18
|
private processRowsConcurrently;
|
package/dist/mapper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAU;gBAGvB,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,GAAE,MAAsB,EAChC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,OAAO,GAAE,OAAe;IAQ1B,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,OAAO,CAAU;gBAGvB,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,GAAE,MAAsB,EAChC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,OAAO,GAAE,OAAe;IAQ1B,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IA4ChE,aAAa,CACjB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,wBAAwB,EACzC,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,IAAI,CAAC;YA8CF,uBAAuB;YAwCvB,uBAAuB;IAkFrC,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,oBAAoB;IA4B5B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,YAAY;IA8DpB,UAAU,IAAI,gBAAgB;CAG/B"}
|
package/dist/metrics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAoB;;IAgBnC,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAW/C,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASvC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASvC,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOhD,gBAAgB,IAAI,iBAAiB;IAKrC,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI/D,iBAAiB,IAAI,MAAM;IAI3B,cAAc,IAAI,MAAM;IAKxB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI7C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK1C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK1C,yBAAyB,IAAI,MAAM;IAMnC,aAAa,IAAI,MAAM;IAKvB,eAAe,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAoB;;IAgBnC,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAW/C,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASvC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASvC,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOhD,gBAAgB,IAAI,iBAAiB;IAKrC,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI/D,iBAAiB,IAAI,MAAM;IAI3B,cAAc,IAAI,MAAM;IAKxB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI7C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK1C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK1C,yBAAyB,IAAI,MAAM;IAMnC,aAAa,IAAI,MAAM;IAKvB,eAAe,IAAI,MAAM;CA2C1B"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -31,6 +31,10 @@ program
|
|
|
31
31
|
"-c, --config <path>",
|
|
32
32
|
"Path to configuration directory (containing data/, graphql/, mappings/ subdirectories)"
|
|
33
33
|
)
|
|
34
|
+
.option(
|
|
35
|
+
"-n, --entities <entities>",
|
|
36
|
+
"Comma-separated list of specific entities to process (e.g., users,products)"
|
|
37
|
+
)
|
|
34
38
|
.option(
|
|
35
39
|
"-h, --headers <headers>",
|
|
36
40
|
"JSON string of headers to include in requests"
|
|
@@ -65,29 +69,61 @@ program
|
|
|
65
69
|
options.verbose
|
|
66
70
|
);
|
|
67
71
|
|
|
72
|
+
// Parse entities filter if provided
|
|
73
|
+
const entityFilter = options.entities
|
|
74
|
+
? options.entities.split(",").map((e: string) => e.trim())
|
|
75
|
+
: undefined;
|
|
76
|
+
|
|
68
77
|
// Discover all mapping files dynamically
|
|
69
|
-
const mappingPaths = mapper.discoverMappings(
|
|
78
|
+
const mappingPaths = mapper.discoverMappings(
|
|
79
|
+
options.config,
|
|
80
|
+
entityFilter
|
|
81
|
+
);
|
|
70
82
|
|
|
71
83
|
if (mappingPaths.length === 0) {
|
|
72
|
-
|
|
84
|
+
const filterMsg = entityFilter
|
|
85
|
+
? ` matching entities: ${entityFilter.join(", ")}`
|
|
86
|
+
: "";
|
|
87
|
+
console.warn(
|
|
88
|
+
`No mapping files found in ${options.config}/mappings${filterMsg}`
|
|
89
|
+
);
|
|
73
90
|
return;
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
// Extract entity names from mapping paths
|
|
77
94
|
const entityNames = mappingPaths.map((path) => basename(path, ".json"));
|
|
78
95
|
|
|
79
|
-
//
|
|
96
|
+
// Filter dependencies to only include those relevant to selected entities
|
|
97
|
+
const relevantDependencies: Record<string, string[]> = {};
|
|
98
|
+
if (config.entityDependencies) {
|
|
99
|
+
for (const entity of entityNames) {
|
|
100
|
+
if (config.entityDependencies[entity]) {
|
|
101
|
+
relevantDependencies[entity] = config.entityDependencies[entity];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Setup dependency resolver with filtered dependencies
|
|
80
107
|
const resolver = new DependencyResolver(
|
|
81
108
|
entityNames,
|
|
82
|
-
|
|
109
|
+
relevantDependencies,
|
|
110
|
+
!!entityFilter // Allow partial resolution when using --entities
|
|
83
111
|
);
|
|
84
112
|
|
|
85
113
|
// Validate dependencies
|
|
86
114
|
const validationErrors = resolver.validateDependencies();
|
|
87
115
|
if (validationErrors.length > 0) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
if (entityFilter) {
|
|
117
|
+
// When using --entities flag, show warnings instead of errors
|
|
118
|
+
console.warn("\nā ļø Warning: Dependency validation issues:");
|
|
119
|
+
validationErrors.forEach((error) => console.warn(` - ${error}`));
|
|
120
|
+
console.warn("This may cause errors if the dependent data doesn't already exist.\n");
|
|
121
|
+
} else {
|
|
122
|
+
// Strict validation when processing all entities
|
|
123
|
+
console.error("Dependency validation errors:");
|
|
124
|
+
validationErrors.forEach((error) => console.error(` - ${error}`));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
91
127
|
}
|
|
92
128
|
|
|
93
129
|
// Process entities in dependency-aware waves
|
|
@@ -88,12 +88,26 @@ describe("DependencyResolver", () => {
|
|
|
88
88
|
a: ["missing"],
|
|
89
89
|
b: ["a"],
|
|
90
90
|
};
|
|
91
|
-
const resolver = new DependencyResolver(entities, dependencies);
|
|
91
|
+
const resolver = new DependencyResolver(entities, dependencies, false);
|
|
92
92
|
|
|
93
93
|
expect(() => resolver.resolveExecutionOrder()).toThrow(
|
|
94
94
|
"Circular dependency detected or missing dependencies for entities: a, b"
|
|
95
95
|
);
|
|
96
96
|
});
|
|
97
|
+
|
|
98
|
+
it("should allow entities with dependencies not in the entity list when partial resolution is enabled", () => {
|
|
99
|
+
const entities = ["a", "b"];
|
|
100
|
+
const dependencies = {
|
|
101
|
+
a: ["missing"],
|
|
102
|
+
b: ["a"],
|
|
103
|
+
};
|
|
104
|
+
const resolver = new DependencyResolver(entities, dependencies, true);
|
|
105
|
+
|
|
106
|
+
const waves = resolver.resolveExecutionOrder();
|
|
107
|
+
expect(waves).toHaveLength(2);
|
|
108
|
+
expect(waves[0].entities).toEqual(["a"]);
|
|
109
|
+
expect(waves[1].entities).toEqual(["b"]);
|
|
110
|
+
});
|
|
97
111
|
});
|
|
98
112
|
|
|
99
113
|
describe("validateDependencies", () => {
|
|
@@ -10,10 +10,12 @@ export interface ExecutionWave {
|
|
|
10
10
|
export class DependencyResolver {
|
|
11
11
|
private dependencies: DependencyGraph;
|
|
12
12
|
private entities: string[];
|
|
13
|
+
private allowPartialResolution: boolean;
|
|
13
14
|
|
|
14
|
-
constructor(entities: string[], dependencies: DependencyGraph = {}) {
|
|
15
|
+
constructor(entities: string[], dependencies: DependencyGraph = {}, allowPartialResolution: boolean = false) {
|
|
15
16
|
this.entities = entities;
|
|
16
17
|
this.dependencies = dependencies;
|
|
18
|
+
this.allowPartialResolution = allowPartialResolution;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
resolveExecutionOrder(): ExecutionWave[] {
|
|
@@ -31,7 +33,9 @@ export class DependencyResolver {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const deps = this.dependencies[entity] || [];
|
|
34
|
-
const canProcess = deps.every((dep) =>
|
|
36
|
+
const canProcess = deps.every((dep) =>
|
|
37
|
+
processed.has(dep) || (this.allowPartialResolution && !this.entities.includes(dep))
|
|
38
|
+
);
|
|
35
39
|
|
|
36
40
|
if (canProcess) {
|
|
37
41
|
currentWave.push(entity);
|
package/src/mapper.ts
CHANGED
|
@@ -30,12 +30,39 @@ export class DataMapper {
|
|
|
30
30
|
this.verbose = verbose;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
discoverMappings(configDir: string): string[] {
|
|
33
|
+
discoverMappings(configDir: string, entityFilter?: string[]): string[] {
|
|
34
34
|
const mappingsPath = path.resolve(this.basePath, configDir, "mappings");
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
const files = fs.readdirSync(mappingsPath);
|
|
38
|
-
|
|
38
|
+
let jsonFiles = files.filter((file) => file.endsWith(".json"));
|
|
39
|
+
|
|
40
|
+
// Apply entity filter if provided
|
|
41
|
+
if (entityFilter && entityFilter.length > 0) {
|
|
42
|
+
const requestedEntities = new Set(entityFilter);
|
|
43
|
+
const foundEntities = new Set<string>();
|
|
44
|
+
|
|
45
|
+
jsonFiles = jsonFiles.filter((file) => {
|
|
46
|
+
const entityName = path.basename(file, ".json");
|
|
47
|
+
if (requestedEntities.has(entityName)) {
|
|
48
|
+
foundEntities.add(entityName);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Check for requested entities that were not found
|
|
55
|
+
const notFound = entityFilter.filter((e) => !foundEntities.has(e));
|
|
56
|
+
if (notFound.length > 0) {
|
|
57
|
+
console.warn(
|
|
58
|
+
`Warning: The following entities were not found in mappings: ${notFound.join(
|
|
59
|
+
", "
|
|
60
|
+
)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
jsonFiles.sort(); // Alphabetical order for consistent processing
|
|
39
66
|
|
|
40
67
|
console.log(
|
|
41
68
|
`Discovered ${jsonFiles.length} mapping files: ${jsonFiles.join(", ")}`
|
|
@@ -161,7 +188,11 @@ export class DataMapper {
|
|
|
161
188
|
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
162
189
|
const chunk = chunks[chunkIndex];
|
|
163
190
|
const promises = chunk.map(async (row) => {
|
|
164
|
-
const variables = this.mapCsvRowToVariables(
|
|
191
|
+
const variables = this.mapCsvRowToVariables(
|
|
192
|
+
row,
|
|
193
|
+
mapping,
|
|
194
|
+
variableTypes
|
|
195
|
+
);
|
|
165
196
|
|
|
166
197
|
try {
|
|
167
198
|
const result = await this.client.executeMutation(
|
|
@@ -285,7 +316,11 @@ export class DataMapper {
|
|
|
285
316
|
return null;
|
|
286
317
|
}
|
|
287
318
|
|
|
288
|
-
private convertValue(
|
|
319
|
+
private convertValue(
|
|
320
|
+
value: string,
|
|
321
|
+
type: string | undefined,
|
|
322
|
+
varName: string
|
|
323
|
+
): any {
|
|
289
324
|
if (!type) {
|
|
290
325
|
// No type information available, keep as string
|
|
291
326
|
return value;
|
|
@@ -297,7 +332,11 @@ export class DataMapper {
|
|
|
297
332
|
case "Int":
|
|
298
333
|
const intValue = Number(trimmedValue);
|
|
299
334
|
// Validate that it's a valid integer (no decimals, NaN, or Infinity)
|
|
300
|
-
if (
|
|
335
|
+
if (
|
|
336
|
+
isNaN(intValue) ||
|
|
337
|
+
!isFinite(intValue) ||
|
|
338
|
+
!Number.isInteger(intValue)
|
|
339
|
+
) {
|
|
301
340
|
console.warn(
|
|
302
341
|
`Warning: Cannot convert "${value}" to Int for variable $${varName}. Expected a valid integer. Using original value.`
|
|
303
342
|
);
|
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 >
|
|
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 =
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
}
|