@jackchuka/gql-ingest 1.3.0 → 1.4.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/bin/cli.js +210 -34
- package/dist/mapper.d.ts +3 -0
- package/dist/mapper.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/mapper.test.ts +221 -0
- package/src/mapper.ts +133 -11
package/dist/mapper.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export declare class DataMapper {
|
|
|
18
18
|
private processRowsConcurrently;
|
|
19
19
|
private chunkArray;
|
|
20
20
|
private mapCsvRowToVariables;
|
|
21
|
+
private extractVariableTypes;
|
|
22
|
+
private extractTypeName;
|
|
23
|
+
private convertValue;
|
|
21
24
|
getMetrics(): MetricsCollector;
|
|
22
25
|
}
|
|
23
26
|
//# sourceMappingURL=mapper.d.ts.map
|
package/dist/mapper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"
|
|
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;IAiBvC,aAAa,CACjB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,wBAAwB,EACzC,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,IAAI,CAAC;YA8CF,uBAAuB;YAwCvB,uBAAuB;IA8ErC,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,oBAAoB;IA4B5B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,YAAY;IAsDpB,UAAU,IAAI,gBAAgB;CAG/B"}
|
package/package.json
CHANGED
package/src/mapper.test.ts
CHANGED
|
@@ -331,5 +331,226 @@ describe("DataMapper", () => {
|
|
|
331
331
|
const metrics = dataMapper.getMetrics();
|
|
332
332
|
expect(metrics).toBe(mockMetrics);
|
|
333
333
|
});
|
|
334
|
+
|
|
335
|
+
it("should convert numeric types from CSV strings to proper GraphQL types", async () => {
|
|
336
|
+
const mockConfig = {
|
|
337
|
+
csvFile: "data/products.csv",
|
|
338
|
+
graphqlFile: "graphql/products.graphql",
|
|
339
|
+
mapping: {
|
|
340
|
+
name: "product_name",
|
|
341
|
+
price: "product_price",
|
|
342
|
+
quantity: "product_quantity",
|
|
343
|
+
active: "product_active",
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const mockCsvData = [
|
|
348
|
+
{
|
|
349
|
+
product_name: "Widget",
|
|
350
|
+
product_price: "19.99",
|
|
351
|
+
product_quantity: "10",
|
|
352
|
+
product_active: "true",
|
|
353
|
+
},
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
const mockMutation = `
|
|
357
|
+
mutation CreateProduct($name: String!, $price: Float!, $quantity: Int!, $active: Boolean!) {
|
|
358
|
+
createProduct(input: { name: $name, price: $price, quantity: $quantity, active: $active }) {
|
|
359
|
+
id
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
mockFs.readFileSync
|
|
365
|
+
.mockReturnValueOnce(JSON.stringify(mockConfig))
|
|
366
|
+
.mockReturnValueOnce(mockMutation);
|
|
367
|
+
|
|
368
|
+
const { readCsvFile } = require("./csv-reader");
|
|
369
|
+
readCsvFile.mockResolvedValue(mockCsvData);
|
|
370
|
+
|
|
371
|
+
mockClient.executeMutation.mockResolvedValue({
|
|
372
|
+
createProduct: { id: "123" },
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await dataMapper.processEntity("configs/test/mappings/products.json");
|
|
376
|
+
|
|
377
|
+
expect(mockClient.executeMutation).toHaveBeenCalledWith(mockMutation, {
|
|
378
|
+
name: "Widget",
|
|
379
|
+
price: 19.99,
|
|
380
|
+
quantity: 10,
|
|
381
|
+
active: true,
|
|
382
|
+
}, undefined);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("should handle invalid numeric conversions gracefully", async () => {
|
|
386
|
+
const mockConfig = {
|
|
387
|
+
csvFile: "data/products.csv",
|
|
388
|
+
graphqlFile: "graphql/products.graphql",
|
|
389
|
+
mapping: {
|
|
390
|
+
name: "product_name",
|
|
391
|
+
price: "product_price",
|
|
392
|
+
quantity: "product_quantity",
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const mockCsvData = [
|
|
397
|
+
{
|
|
398
|
+
product_name: "Widget",
|
|
399
|
+
product_price: "invalid_price",
|
|
400
|
+
product_quantity: "invalid_quantity",
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const mockMutation = `
|
|
405
|
+
mutation CreateProduct($name: String!, $price: Float!, $quantity: Int!) {
|
|
406
|
+
createProduct(input: { name: $name, price: $price, quantity: $quantity }) {
|
|
407
|
+
id
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
`;
|
|
411
|
+
|
|
412
|
+
mockFs.readFileSync
|
|
413
|
+
.mockReturnValueOnce(JSON.stringify(mockConfig))
|
|
414
|
+
.mockReturnValueOnce(mockMutation);
|
|
415
|
+
|
|
416
|
+
const { readCsvFile } = require("./csv-reader");
|
|
417
|
+
readCsvFile.mockResolvedValue(mockCsvData);
|
|
418
|
+
|
|
419
|
+
mockClient.executeMutation.mockResolvedValue({
|
|
420
|
+
createProduct: { id: "123" },
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
424
|
+
|
|
425
|
+
await dataMapper.processEntity("configs/test/mappings/products.json");
|
|
426
|
+
|
|
427
|
+
expect(mockClient.executeMutation).toHaveBeenCalledWith(mockMutation, {
|
|
428
|
+
name: "Widget",
|
|
429
|
+
price: "invalid_price",
|
|
430
|
+
quantity: "invalid_quantity",
|
|
431
|
+
}, undefined);
|
|
432
|
+
|
|
433
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
434
|
+
'Warning: Cannot convert "invalid_price" to Float for variable $price. Expected a valid number. Using original value.'
|
|
435
|
+
);
|
|
436
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
437
|
+
'Warning: Cannot convert "invalid_quantity" to Int for variable $quantity. Expected a valid integer. Using original value.'
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
consoleSpy.mockRestore();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("should handle edge cases in numeric conversion safely", async () => {
|
|
444
|
+
const mockConfig = {
|
|
445
|
+
csvFile: "data/products.csv",
|
|
446
|
+
graphqlFile: "graphql/products.graphql",
|
|
447
|
+
mapping: {
|
|
448
|
+
int_field: "int_value",
|
|
449
|
+
float_field: "float_value",
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const mockCsvData = [
|
|
454
|
+
{
|
|
455
|
+
int_value: "1.5", // Float in Int field - should remain string
|
|
456
|
+
float_value: "Infinity", // Invalid float - should remain string
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
int_value: "not_a_number", // Invalid int - should remain string
|
|
460
|
+
float_value: "1.2.3", // Invalid number format - should remain string
|
|
461
|
+
},
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
const mockMutation = `
|
|
465
|
+
mutation CreateProduct($int_field: Int!, $float_field: Float!) {
|
|
466
|
+
createProduct(input: { int_field: $int_field, float_field: $float_field }) {
|
|
467
|
+
id
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
`;
|
|
471
|
+
|
|
472
|
+
mockFs.readFileSync
|
|
473
|
+
.mockReturnValueOnce(JSON.stringify(mockConfig))
|
|
474
|
+
.mockReturnValueOnce(mockMutation);
|
|
475
|
+
|
|
476
|
+
const { readCsvFile } = require("./csv-reader");
|
|
477
|
+
readCsvFile.mockResolvedValue(mockCsvData);
|
|
478
|
+
|
|
479
|
+
mockClient.executeMutation.mockResolvedValue({
|
|
480
|
+
createProduct: { id: "123" },
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
|
|
484
|
+
|
|
485
|
+
await dataMapper.processEntity("configs/test/mappings/products.json");
|
|
486
|
+
|
|
487
|
+
// Should keep invalid values as strings
|
|
488
|
+
expect(mockClient.executeMutation).toHaveBeenCalledWith(mockMutation, {
|
|
489
|
+
int_field: "1.5",
|
|
490
|
+
float_field: "Infinity",
|
|
491
|
+
}, undefined);
|
|
492
|
+
|
|
493
|
+
expect(mockClient.executeMutation).toHaveBeenCalledWith(mockMutation, {
|
|
494
|
+
int_field: "not_a_number",
|
|
495
|
+
float_field: "1.2.3",
|
|
496
|
+
}, undefined);
|
|
497
|
+
|
|
498
|
+
consoleSpy.mockRestore();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("should keep unknown scalar types as strings", async () => {
|
|
502
|
+
const mockConfig = {
|
|
503
|
+
csvFile: "data/products.csv",
|
|
504
|
+
graphqlFile: "graphql/products.graphql",
|
|
505
|
+
mapping: {
|
|
506
|
+
name: "product_name",
|
|
507
|
+
custom_field: "custom_value",
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const mockCsvData = [
|
|
512
|
+
{
|
|
513
|
+
product_name: "Widget",
|
|
514
|
+
custom_value: "123",
|
|
515
|
+
},
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
const mockMutation = `
|
|
519
|
+
mutation CreateProduct($name: String!, $custom_field: CustomScalar!) {
|
|
520
|
+
createProduct(input: { name: $name, custom_field: $custom_field }) {
|
|
521
|
+
id
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
`;
|
|
525
|
+
|
|
526
|
+
mockFs.readFileSync
|
|
527
|
+
.mockReturnValueOnce(JSON.stringify(mockConfig))
|
|
528
|
+
.mockReturnValueOnce(mockMutation);
|
|
529
|
+
|
|
530
|
+
const { readCsvFile } = require("./csv-reader");
|
|
531
|
+
readCsvFile.mockResolvedValue(mockCsvData);
|
|
532
|
+
|
|
533
|
+
mockClient.executeMutation.mockResolvedValue({
|
|
534
|
+
createProduct: { id: "123" },
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Create verbose mapper to test the logging
|
|
538
|
+
const verboseMapper = new DataMapper(mockClient, testBasePath, mockMetrics, true);
|
|
539
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
540
|
+
|
|
541
|
+
await verboseMapper.processEntity("configs/test/mappings/products.json");
|
|
542
|
+
|
|
543
|
+
// Should keep custom scalar as string
|
|
544
|
+
expect(mockClient.executeMutation).toHaveBeenCalledWith(mockMutation, {
|
|
545
|
+
name: "Widget",
|
|
546
|
+
custom_field: "123",
|
|
547
|
+
}, undefined);
|
|
548
|
+
|
|
549
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
550
|
+
'Unknown GraphQL type "CustomScalar" for variable $custom_field. Keeping value as string.'
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
consoleSpy.mockRestore();
|
|
554
|
+
});
|
|
334
555
|
});
|
|
335
556
|
});
|
package/src/mapper.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { parse, DocumentNode, VariableDefinitionNode } from "graphql";
|
|
3
4
|
import { readCsvFile, CsvRow } from "./csv-reader";
|
|
4
5
|
import { GraphQLClientWrapper } from "./graphql-client";
|
|
5
6
|
import { MetricsCollector } from "./metrics";
|
|
@@ -104,24 +105,33 @@ export class DataMapper {
|
|
|
104
105
|
retryConfig?: RetryConfig
|
|
105
106
|
): Promise<void> {
|
|
106
107
|
const totalRows = csvData.length;
|
|
107
|
-
|
|
108
|
+
const variableTypes = this.extractVariableTypes(mutation);
|
|
109
|
+
|
|
108
110
|
for (let i = 0; i < csvData.length; i++) {
|
|
109
111
|
const row = csvData[i];
|
|
110
|
-
const variables = this.mapCsvRowToVariables(row, mapping);
|
|
112
|
+
const variables = this.mapCsvRowToVariables(row, mapping, variableTypes);
|
|
111
113
|
|
|
112
114
|
try {
|
|
113
115
|
await this.client.executeMutation(mutation, variables, retryConfig);
|
|
114
116
|
this.metrics.recordSuccess(entityName);
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
// Show progress every 10% or at the end (only in non-verbose mode)
|
|
117
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
!this.verbose &&
|
|
121
|
+
((i + 1) % Math.max(1, Math.floor(totalRows / 10)) === 0 ||
|
|
122
|
+
i === totalRows - 1)
|
|
123
|
+
) {
|
|
118
124
|
const progress = (((i + 1) / totalRows) * 100).toFixed(1);
|
|
119
125
|
console.log(`📊 Progress: ${i + 1}/${totalRows} (${progress}%) ✓`);
|
|
120
126
|
}
|
|
121
127
|
} catch (error) {
|
|
122
128
|
this.metrics.recordFailure(entityName);
|
|
123
129
|
if (!this.verbose) {
|
|
124
|
-
console.error(
|
|
130
|
+
console.error(
|
|
131
|
+
`✗ Failed to create entity for row ${i + 1}:`,
|
|
132
|
+
row,
|
|
133
|
+
error
|
|
134
|
+
);
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
}
|
|
@@ -140,6 +150,9 @@ export class DataMapper {
|
|
|
140
150
|
`Processing ${csvData.length} rows with concurrency: ${concurrency}`
|
|
141
151
|
);
|
|
142
152
|
|
|
153
|
+
// Extract variable types once for all rows
|
|
154
|
+
const variableTypes = this.extractVariableTypes(mutation);
|
|
155
|
+
|
|
143
156
|
// Split data into chunks for concurrent processing
|
|
144
157
|
const chunks = this.chunkArray(csvData, concurrency);
|
|
145
158
|
let processedCount = 0;
|
|
@@ -148,10 +161,14 @@ export class DataMapper {
|
|
|
148
161
|
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
149
162
|
const chunk = chunks[chunkIndex];
|
|
150
163
|
const promises = chunk.map(async (row) => {
|
|
151
|
-
const variables = this.mapCsvRowToVariables(row, mapping);
|
|
164
|
+
const variables = this.mapCsvRowToVariables(row, mapping, variableTypes);
|
|
152
165
|
|
|
153
166
|
try {
|
|
154
|
-
const result = await this.client.executeMutation(
|
|
167
|
+
const result = await this.client.executeMutation(
|
|
168
|
+
mutation,
|
|
169
|
+
variables,
|
|
170
|
+
retryConfig
|
|
171
|
+
);
|
|
155
172
|
this.metrics.recordSuccess(entityName);
|
|
156
173
|
return { success: true, result, row };
|
|
157
174
|
} catch (error) {
|
|
@@ -166,7 +183,7 @@ export class DataMapper {
|
|
|
166
183
|
// Count successes and failures in this chunk
|
|
167
184
|
let chunkSuccesses = 0;
|
|
168
185
|
let chunkFailures = 0;
|
|
169
|
-
|
|
186
|
+
|
|
170
187
|
results.forEach((result) => {
|
|
171
188
|
if (result.status === "fulfilled") {
|
|
172
189
|
const { success, error, row } = result.value;
|
|
@@ -189,7 +206,11 @@ export class DataMapper {
|
|
|
189
206
|
// Show progress update (only in non-verbose mode)
|
|
190
207
|
if (!this.verbose) {
|
|
191
208
|
const progress = ((processedCount / totalRows) * 100).toFixed(1);
|
|
192
|
-
console.log(
|
|
209
|
+
console.log(
|
|
210
|
+
`📊 Progress: ${processedCount}/${totalRows} (${progress}%) - Chunk ${
|
|
211
|
+
chunkIndex + 1
|
|
212
|
+
}: ${chunkSuccesses} ✓, ${chunkFailures} ✗`
|
|
213
|
+
);
|
|
193
214
|
}
|
|
194
215
|
}
|
|
195
216
|
}
|
|
@@ -204,19 +225,120 @@ export class DataMapper {
|
|
|
204
225
|
|
|
205
226
|
private mapCsvRowToVariables(
|
|
206
227
|
row: CsvRow,
|
|
207
|
-
mapping: Record<string, string
|
|
228
|
+
mapping: Record<string, string>,
|
|
229
|
+
variableTypes: Record<string, string>
|
|
208
230
|
): Record<string, any> {
|
|
209
231
|
const variables: Record<string, any> = {};
|
|
210
232
|
|
|
211
233
|
for (const [graphqlVar, csvColumn] of Object.entries(mapping)) {
|
|
212
234
|
if (row[csvColumn] !== undefined) {
|
|
213
|
-
|
|
235
|
+
const rawValue = row[csvColumn];
|
|
236
|
+
const type = variableTypes[graphqlVar];
|
|
237
|
+
variables[graphqlVar] = this.convertValue(rawValue, type, graphqlVar);
|
|
214
238
|
}
|
|
215
239
|
}
|
|
216
240
|
|
|
217
241
|
return variables;
|
|
218
242
|
}
|
|
219
243
|
|
|
244
|
+
private extractVariableTypes(mutation: string): Record<string, string> {
|
|
245
|
+
const types: Record<string, string> = {};
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const document: DocumentNode = parse(mutation);
|
|
249
|
+
|
|
250
|
+
// Find the operation (mutation/query) and extract variable definitions
|
|
251
|
+
for (const definition of document.definitions) {
|
|
252
|
+
if (
|
|
253
|
+
definition.kind === "OperationDefinition" &&
|
|
254
|
+
definition.variableDefinitions
|
|
255
|
+
) {
|
|
256
|
+
for (const variableDef of definition.variableDefinitions) {
|
|
257
|
+
const varName = variableDef.variable.name.value;
|
|
258
|
+
const typeName = this.extractTypeName(variableDef);
|
|
259
|
+
if (typeName) {
|
|
260
|
+
types[varName] = typeName;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error("Error parsing GraphQL mutation:", error);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return types;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private extractTypeName(variableDef: VariableDefinitionNode): string | null {
|
|
273
|
+
const type = variableDef.type;
|
|
274
|
+
|
|
275
|
+
if (type.kind === "NonNullType") {
|
|
276
|
+
// Handle non-null types like String!
|
|
277
|
+
if (type.type.kind === "NamedType") {
|
|
278
|
+
return type.type.name.value;
|
|
279
|
+
}
|
|
280
|
+
} else if (type.kind === "NamedType") {
|
|
281
|
+
// Handle nullable types like String
|
|
282
|
+
return type.name.value;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private convertValue(value: string, type: string | undefined, varName: string): any {
|
|
289
|
+
if (!type) {
|
|
290
|
+
// No type information available, keep as string
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const trimmedValue = value.trim();
|
|
295
|
+
|
|
296
|
+
switch (type) {
|
|
297
|
+
case "Int":
|
|
298
|
+
const intValue = Number(trimmedValue);
|
|
299
|
+
// Validate that it's a valid integer (no decimals, NaN, or Infinity)
|
|
300
|
+
if (isNaN(intValue) || !isFinite(intValue) || !Number.isInteger(intValue)) {
|
|
301
|
+
console.warn(
|
|
302
|
+
`Warning: Cannot convert "${value}" to Int for variable $${varName}. Expected a valid integer. Using original value.`
|
|
303
|
+
);
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
return intValue;
|
|
307
|
+
|
|
308
|
+
case "Float":
|
|
309
|
+
const floatValue = Number(trimmedValue);
|
|
310
|
+
// Number() is more strict than parseFloat() - it requires the entire string to be valid
|
|
311
|
+
if (isNaN(floatValue) || !isFinite(floatValue)) {
|
|
312
|
+
console.warn(
|
|
313
|
+
`Warning: Cannot convert "${value}" to Float for variable $${varName}. Expected a valid number. Using original value.`
|
|
314
|
+
);
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
return floatValue;
|
|
318
|
+
|
|
319
|
+
case "Boolean":
|
|
320
|
+
const lowerValue = trimmedValue.toLowerCase();
|
|
321
|
+
if (lowerValue === "true" || lowerValue === "1") return true;
|
|
322
|
+
if (lowerValue === "false" || lowerValue === "0") return false;
|
|
323
|
+
console.warn(
|
|
324
|
+
`Warning: Cannot convert "${value}" to Boolean for variable $${varName}. Expected "true", "false", "1", or "0". Using original value.`
|
|
325
|
+
);
|
|
326
|
+
return value;
|
|
327
|
+
|
|
328
|
+
case "String":
|
|
329
|
+
return value;
|
|
330
|
+
|
|
331
|
+
default:
|
|
332
|
+
// Unknown scalar type - keep as string for safety
|
|
333
|
+
if (this.verbose) {
|
|
334
|
+
console.log(
|
|
335
|
+
`Unknown GraphQL type "${type}" for variable $${varName}. Keeping value as string.`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
220
342
|
getMetrics(): MetricsCollector {
|
|
221
343
|
return this.metrics;
|
|
222
344
|
}
|