@jackchuka/gql-ingest 4.0.0 → 4.2.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/README.md +48 -0
- package/dist/cli/index.js +61 -61
- package/dist/index.js +168 -27
- package/dist/index.js.map +2 -2
- package/dist/lib/gql-ingest.d.ts +1 -0
- package/dist/lib/gql-ingest.d.ts.map +1 -1
- package/dist/lib/mapper.d.ts +25 -1
- package/dist/lib/mapper.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14314,6 +14314,12 @@ var MetricsCollector = class {
|
|
|
14314
14314
|
};
|
|
14315
14315
|
|
|
14316
14316
|
// src/lib/mapper.ts
|
|
14317
|
+
var RefResolutionError = class extends Error {
|
|
14318
|
+
constructor(message) {
|
|
14319
|
+
super(message);
|
|
14320
|
+
this.name = "RefResolutionError";
|
|
14321
|
+
}
|
|
14322
|
+
};
|
|
14317
14323
|
var DataMapper = class {
|
|
14318
14324
|
constructor(client, basePath = process.cwd(), metrics, logger = noopLogger, formatOverride) {
|
|
14319
14325
|
this.client = client;
|
|
@@ -14331,7 +14337,7 @@ var DataMapper = class {
|
|
|
14331
14337
|
/**
|
|
14332
14338
|
* Process an entity with event callbacks and abort support
|
|
14333
14339
|
*/
|
|
14334
|
-
async processEntityWithEvents(configPath, parallelConfig, retryConfig, signal, callbacks) {
|
|
14340
|
+
async processEntityWithEvents(configPath, parallelConfig, retryConfig, signal, callbacks, outputStore) {
|
|
14335
14341
|
const entityStartTime = Date.now();
|
|
14336
14342
|
const configFullPath = path.resolve(this.basePath, configPath);
|
|
14337
14343
|
const config2 = JSON.parse(fs6.readFileSync(configFullPath, "utf8"));
|
|
@@ -14359,7 +14365,9 @@ var DataMapper = class {
|
|
|
14359
14365
|
parallelConfig,
|
|
14360
14366
|
retryConfig,
|
|
14361
14367
|
signal,
|
|
14362
|
-
callbacks
|
|
14368
|
+
callbacks,
|
|
14369
|
+
config2.outputCapture,
|
|
14370
|
+
outputStore
|
|
14363
14371
|
);
|
|
14364
14372
|
} else {
|
|
14365
14373
|
await this.processRowsSequentiallyWithEvents(
|
|
@@ -14369,7 +14377,9 @@ var DataMapper = class {
|
|
|
14369
14377
|
entityName,
|
|
14370
14378
|
retryConfig,
|
|
14371
14379
|
signal,
|
|
14372
|
-
callbacks
|
|
14380
|
+
callbacks,
|
|
14381
|
+
config2.outputCapture,
|
|
14382
|
+
outputStore
|
|
14373
14383
|
);
|
|
14374
14384
|
}
|
|
14375
14385
|
this.metrics.finishEntityProcessing(entityName);
|
|
@@ -14393,7 +14403,7 @@ var DataMapper = class {
|
|
|
14393
14403
|
durationMs: duration3
|
|
14394
14404
|
});
|
|
14395
14405
|
}
|
|
14396
|
-
async processRowsSequentiallyWithEvents(data, mutation, mapping, entityName, retryConfig, signal, callbacks) {
|
|
14406
|
+
async processRowsSequentiallyWithEvents(data, mutation, mapping, entityName, retryConfig, signal, callbacks, outputCapture, outputStore) {
|
|
14397
14407
|
const totalRows = data.length;
|
|
14398
14408
|
const variableTypes = this.extractVariableTypes(mutation);
|
|
14399
14409
|
for (let i = 0; i < data.length; i++) {
|
|
@@ -14402,11 +14412,12 @@ var DataMapper = class {
|
|
|
14402
14412
|
return;
|
|
14403
14413
|
}
|
|
14404
14414
|
const row = data[i];
|
|
14405
|
-
const variables = this.mapRowToVariables(row, mapping, variableTypes);
|
|
14406
14415
|
const rowStartTime = Date.now();
|
|
14407
14416
|
try {
|
|
14417
|
+
const variables = this.mapRowToVariables(row, mapping, variableTypes, outputStore);
|
|
14408
14418
|
const result = await this.client.executeMutation(mutation, variables, retryConfig, signal);
|
|
14409
14419
|
this.metrics.recordSuccess(entityName);
|
|
14420
|
+
this.captureOutput(row, result, entityName, outputCapture, outputStore);
|
|
14410
14421
|
callbacks?.onRowSuccess?.({
|
|
14411
14422
|
entityName,
|
|
14412
14423
|
rowIndex: i,
|
|
@@ -14435,7 +14446,7 @@ var DataMapper = class {
|
|
|
14435
14446
|
}
|
|
14436
14447
|
}
|
|
14437
14448
|
}
|
|
14438
|
-
async processRowsConcurrentlyWithEvents(data, mutation, mapping, entityName, parallelConfig, retryConfig, signal, callbacks) {
|
|
14449
|
+
async processRowsConcurrentlyWithEvents(data, mutation, mapping, entityName, parallelConfig, retryConfig, signal, callbacks, outputCapture, outputStore) {
|
|
14439
14450
|
const concurrency = parallelConfig.concurrency;
|
|
14440
14451
|
this.logger.info(`Processing ${data.length} rows with concurrency: ${concurrency}`);
|
|
14441
14452
|
const variableTypes = this.extractVariableTypes(mutation);
|
|
@@ -14451,9 +14462,9 @@ var DataMapper = class {
|
|
|
14451
14462
|
const chunkStartIndex = chunkIndex * concurrency;
|
|
14452
14463
|
const promises = chunk.map(async (row, index) => {
|
|
14453
14464
|
const rowIndex = chunkStartIndex + index;
|
|
14454
|
-
const variables = this.mapRowToVariables(row, mapping, variableTypes);
|
|
14455
14465
|
const rowStartTime = Date.now();
|
|
14456
14466
|
try {
|
|
14467
|
+
const variables = this.mapRowToVariables(row, mapping, variableTypes, outputStore);
|
|
14457
14468
|
const result = await this.client.executeMutation(
|
|
14458
14469
|
mutation,
|
|
14459
14470
|
variables,
|
|
@@ -14461,6 +14472,7 @@ var DataMapper = class {
|
|
|
14461
14472
|
signal
|
|
14462
14473
|
);
|
|
14463
14474
|
this.metrics.recordSuccess(entityName);
|
|
14475
|
+
this.captureOutput(row, result, entityName, outputCapture, outputStore);
|
|
14464
14476
|
callbacks?.onRowSuccess?.({
|
|
14465
14477
|
entityName,
|
|
14466
14478
|
rowIndex,
|
|
@@ -14516,13 +14528,13 @@ var DataMapper = class {
|
|
|
14516
14528
|
}
|
|
14517
14529
|
return chunks;
|
|
14518
14530
|
}
|
|
14519
|
-
mapRowToVariables(row, mapping, variableTypes) {
|
|
14531
|
+
mapRowToVariables(row, mapping, variableTypes, outputStore) {
|
|
14520
14532
|
const variables = {};
|
|
14521
14533
|
for (const [graphqlVar, mappingValue] of Object.entries(mapping)) {
|
|
14522
14534
|
if (mappingValue === "$") {
|
|
14523
14535
|
variables[graphqlVar] = row;
|
|
14524
14536
|
} else if (typeof mappingValue === "string" && mappingValue.startsWith("$.")) {
|
|
14525
|
-
const dataPath =
|
|
14537
|
+
const dataPath = this.stripJsonPathPrefix(mappingValue);
|
|
14526
14538
|
const value = this.getValueByPath(row, dataPath);
|
|
14527
14539
|
if (value !== void 0) {
|
|
14528
14540
|
const type = variableTypes[graphqlVar];
|
|
@@ -14532,8 +14544,15 @@ var DataMapper = class {
|
|
|
14532
14544
|
const rawValue = row[mappingValue];
|
|
14533
14545
|
const type = variableTypes[graphqlVar];
|
|
14534
14546
|
variables[graphqlVar] = this.convertValue(rawValue, type, graphqlVar);
|
|
14547
|
+
} else if (this.isRefMapping(mappingValue)) {
|
|
14548
|
+
variables[graphqlVar] = this.resolveRef(row, mappingValue, outputStore);
|
|
14535
14549
|
} else if (typeof mappingValue === "object" && mappingValue !== null) {
|
|
14536
|
-
variables[graphqlVar] = this.mapNestedObject(row, mappingValue, variableTypes);
|
|
14550
|
+
variables[graphqlVar] = this.mapNestedObject(row, mappingValue, variableTypes, outputStore);
|
|
14551
|
+
}
|
|
14552
|
+
}
|
|
14553
|
+
if (outputStore) {
|
|
14554
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
14555
|
+
variables[key] = this.resolveRefsInData(value, row, outputStore);
|
|
14537
14556
|
}
|
|
14538
14557
|
}
|
|
14539
14558
|
return variables;
|
|
@@ -14550,15 +14569,18 @@ var DataMapper = class {
|
|
|
14550
14569
|
}
|
|
14551
14570
|
return current;
|
|
14552
14571
|
}
|
|
14553
|
-
mapNestedObject(row, mappingObj, variableTypes) {
|
|
14572
|
+
mapNestedObject(row, mappingObj, variableTypes, outputStore) {
|
|
14554
14573
|
if (Array.isArray(mappingObj)) {
|
|
14555
|
-
return mappingObj.map((item) => this.mapNestedObject(row, item, variableTypes));
|
|
14574
|
+
return mappingObj.map((item) => this.mapNestedObject(row, item, variableTypes, outputStore));
|
|
14556
14575
|
}
|
|
14557
14576
|
if (typeof mappingObj === "object" && mappingObj !== null) {
|
|
14577
|
+
if (this.isRefMapping(mappingObj)) {
|
|
14578
|
+
return this.resolveRef(row, mappingObj, outputStore);
|
|
14579
|
+
}
|
|
14558
14580
|
const result = {};
|
|
14559
14581
|
for (const [key, value] of Object.entries(mappingObj)) {
|
|
14560
14582
|
if (typeof value === "string" && value.startsWith("$.")) {
|
|
14561
|
-
const dataPath =
|
|
14583
|
+
const dataPath = this.stripJsonPathPrefix(value);
|
|
14562
14584
|
let fieldValue = this.getValueByPath(row, dataPath);
|
|
14563
14585
|
if (key === "values" && typeof fieldValue === "string" && fieldValue.includes(",")) {
|
|
14564
14586
|
fieldValue = fieldValue.split(",").map((v) => v.trim());
|
|
@@ -14566,8 +14588,10 @@ var DataMapper = class {
|
|
|
14566
14588
|
result[key] = fieldValue;
|
|
14567
14589
|
} else if (typeof value === "string" && row[value] !== void 0) {
|
|
14568
14590
|
result[key] = row[value];
|
|
14591
|
+
} else if (this.isRefMapping(value)) {
|
|
14592
|
+
result[key] = this.resolveRef(row, value, outputStore);
|
|
14569
14593
|
} else if (typeof value === "object") {
|
|
14570
|
-
result[key] = this.mapNestedObject(row, value, variableTypes);
|
|
14594
|
+
result[key] = this.mapNestedObject(row, value, variableTypes, outputStore);
|
|
14571
14595
|
} else {
|
|
14572
14596
|
result[key] = value;
|
|
14573
14597
|
}
|
|
@@ -14576,6 +14600,113 @@ var DataMapper = class {
|
|
|
14576
14600
|
}
|
|
14577
14601
|
return mappingObj;
|
|
14578
14602
|
}
|
|
14603
|
+
/**
|
|
14604
|
+
* Recursively walk a resolved value and resolve any $ref objects found in data.
|
|
14605
|
+
* Unlike mapping-level $ref (where key is a JSONPath into the row), data-level
|
|
14606
|
+
* $ref uses key as a literal lookup value unless it starts with "$.".
|
|
14607
|
+
*/
|
|
14608
|
+
resolveRefsInData(value, row, outputStore) {
|
|
14609
|
+
if (Array.isArray(value)) {
|
|
14610
|
+
let changed = false;
|
|
14611
|
+
const mapped = value.map((item) => {
|
|
14612
|
+
const resolved = this.resolveRefsInData(item, row, outputStore);
|
|
14613
|
+
if (resolved !== item) changed = true;
|
|
14614
|
+
return resolved;
|
|
14615
|
+
});
|
|
14616
|
+
return changed ? mapped : value;
|
|
14617
|
+
}
|
|
14618
|
+
if (typeof value === "object" && value !== null) {
|
|
14619
|
+
if (this.isRefMapping(value)) {
|
|
14620
|
+
const keyStr = value.key.startsWith("$.") ? String(this.getValueByPath(row, this.stripJsonPathPrefix(value.key)) ?? value.key) : value.key;
|
|
14621
|
+
return this.lookupRef(outputStore, value.$ref, keyStr, value.field);
|
|
14622
|
+
}
|
|
14623
|
+
let changed = false;
|
|
14624
|
+
const result = {};
|
|
14625
|
+
for (const [k, v] of Object.entries(value)) {
|
|
14626
|
+
const resolved = this.resolveRefsInData(v, row, outputStore);
|
|
14627
|
+
if (resolved !== v) changed = true;
|
|
14628
|
+
result[k] = resolved;
|
|
14629
|
+
}
|
|
14630
|
+
return changed ? result : value;
|
|
14631
|
+
}
|
|
14632
|
+
return value;
|
|
14633
|
+
}
|
|
14634
|
+
stripJsonPathPrefix(jsonPath) {
|
|
14635
|
+
return jsonPath.startsWith("$.") ? jsonPath.substring(2) : jsonPath;
|
|
14636
|
+
}
|
|
14637
|
+
isRefMapping(value) {
|
|
14638
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "$ref" in value && "key" in value && "field" in value;
|
|
14639
|
+
}
|
|
14640
|
+
/** Resolve a mapping-level $ref — throws on any failure so the row is skipped. */
|
|
14641
|
+
resolveRef(row, ref, outputStore) {
|
|
14642
|
+
if (!outputStore) {
|
|
14643
|
+
throw new RefResolutionError(`$ref to "${ref.$ref}" found but no output store is available`);
|
|
14644
|
+
}
|
|
14645
|
+
const keyPath = this.stripJsonPathPrefix(ref.key);
|
|
14646
|
+
const lookupKeyValue = this.getValueByPath(row, keyPath);
|
|
14647
|
+
if (lookupKeyValue === void 0) {
|
|
14648
|
+
throw new RefResolutionError(`$ref lookup key "${ref.key}" not found in current row`);
|
|
14649
|
+
}
|
|
14650
|
+
const keyStr = String(lookupKeyValue);
|
|
14651
|
+
const value = this.lookupRef(outputStore, ref.$ref, keyStr, ref.field);
|
|
14652
|
+
if (value === void 0) {
|
|
14653
|
+
throw new RefResolutionError(
|
|
14654
|
+
`$ref resolution failed for "${ref.$ref}[${keyStr}].${ref.field}"`
|
|
14655
|
+
);
|
|
14656
|
+
}
|
|
14657
|
+
return value;
|
|
14658
|
+
}
|
|
14659
|
+
/**
|
|
14660
|
+
* Core ref lookup: entityName -> key -> field.
|
|
14661
|
+
* Returns undefined with a warning on miss (caller decides whether to throw).
|
|
14662
|
+
*/
|
|
14663
|
+
lookupRef(outputStore, entityName, keyStr, field) {
|
|
14664
|
+
const entityStore = outputStore.get(entityName);
|
|
14665
|
+
if (!entityStore) {
|
|
14666
|
+
this.logger.warn(`$ref entity "${entityName}" not found in output store`);
|
|
14667
|
+
return void 0;
|
|
14668
|
+
}
|
|
14669
|
+
const captured = entityStore.get(keyStr);
|
|
14670
|
+
if (!captured) {
|
|
14671
|
+
this.logger.warn(`$ref key "${keyStr}" not found in entity "${entityName}" output store`);
|
|
14672
|
+
return void 0;
|
|
14673
|
+
}
|
|
14674
|
+
const value = captured[field];
|
|
14675
|
+
if (value === void 0) {
|
|
14676
|
+
this.logger.warn(
|
|
14677
|
+
`$ref field "${field}" not found in captured output for "${entityName}[${keyStr}]"`
|
|
14678
|
+
);
|
|
14679
|
+
}
|
|
14680
|
+
return value;
|
|
14681
|
+
}
|
|
14682
|
+
captureOutput(row, result, entityName, outputCapture, outputStore) {
|
|
14683
|
+
if (!outputCapture || !outputStore) return;
|
|
14684
|
+
const keyPath = this.stripJsonPathPrefix(outputCapture.key);
|
|
14685
|
+
const keyValue = this.getValueByPath(row, keyPath);
|
|
14686
|
+
if (keyValue === void 0) {
|
|
14687
|
+
this.logger.warn(
|
|
14688
|
+
`outputCapture key "${outputCapture.key}" not found in input row for entity "${entityName}". Skipping capture.`
|
|
14689
|
+
);
|
|
14690
|
+
return;
|
|
14691
|
+
}
|
|
14692
|
+
const keyStr = String(keyValue);
|
|
14693
|
+
const captured = {};
|
|
14694
|
+
for (const [fieldName, responsePath] of Object.entries(outputCapture.fields)) {
|
|
14695
|
+
const dataPath = this.stripJsonPathPrefix(responsePath);
|
|
14696
|
+
captured[fieldName] = this.getValueByPath(result, dataPath);
|
|
14697
|
+
}
|
|
14698
|
+
let entityStore = outputStore.get(entityName);
|
|
14699
|
+
if (!entityStore) {
|
|
14700
|
+
entityStore = /* @__PURE__ */ new Map();
|
|
14701
|
+
outputStore.set(entityName, entityStore);
|
|
14702
|
+
}
|
|
14703
|
+
if (entityStore.has(keyStr)) {
|
|
14704
|
+
this.logger.warn(
|
|
14705
|
+
`outputCapture: duplicate key "${keyStr}" for entity "${entityName}" \u2014 overwriting previous value`
|
|
14706
|
+
);
|
|
14707
|
+
}
|
|
14708
|
+
entityStore.set(keyStr, captured);
|
|
14709
|
+
}
|
|
14579
14710
|
extractVariableTypes(mutation) {
|
|
14580
14711
|
const types = {};
|
|
14581
14712
|
try {
|
|
@@ -14742,6 +14873,7 @@ var DEFAULT_EVENT_OPTIONS = {
|
|
|
14742
14873
|
var GQLIngest = class extends EventEmitter {
|
|
14743
14874
|
constructor(options) {
|
|
14744
14875
|
super();
|
|
14876
|
+
this.outputStore = /* @__PURE__ */ new Map();
|
|
14745
14877
|
// Cancellation state
|
|
14746
14878
|
this.abortController = null;
|
|
14747
14879
|
this.isProcessing = false;
|
|
@@ -14884,6 +15016,7 @@ var GQLIngest = class extends EventEmitter {
|
|
|
14884
15016
|
return this.handleCancellation(signal.reason);
|
|
14885
15017
|
}
|
|
14886
15018
|
this.metrics = new MetricsCollector();
|
|
15019
|
+
this.outputStore = /* @__PURE__ */ new Map();
|
|
14887
15020
|
this.client = new GraphQLClientWrapper(
|
|
14888
15021
|
this.endpoint,
|
|
14889
15022
|
this.headers,
|
|
@@ -14961,7 +15094,8 @@ var GQLIngest = class extends EventEmitter {
|
|
|
14961
15094
|
this.mapper,
|
|
14962
15095
|
config2,
|
|
14963
15096
|
this.logger,
|
|
14964
|
-
signal
|
|
15097
|
+
signal,
|
|
15098
|
+
this.outputStore
|
|
14965
15099
|
);
|
|
14966
15100
|
if (signal.aborted) {
|
|
14967
15101
|
return this.handleCancellation(signal.reason);
|
|
@@ -15067,7 +15201,7 @@ var GQLIngest = class extends EventEmitter {
|
|
|
15067
15201
|
/**
|
|
15068
15202
|
* Process entities in dependency-aware waves with abort support
|
|
15069
15203
|
*/
|
|
15070
|
-
async processEntitiesInWaves(pathMap, resolver, mapper, config2, logger, signal) {
|
|
15204
|
+
async processEntitiesInWaves(pathMap, resolver, mapper, config2, logger, signal, outputStore) {
|
|
15071
15205
|
const waves = resolver.resolveExecutionOrder();
|
|
15072
15206
|
logger.info(`Processing ${waves.length} dependency waves...`);
|
|
15073
15207
|
for (const wave of waves) {
|
|
@@ -15092,18 +15226,25 @@ var GQLIngest = class extends EventEmitter {
|
|
|
15092
15226
|
try {
|
|
15093
15227
|
const entityConfig = getEntityConfig(entityName, config2, logger);
|
|
15094
15228
|
const retryConfig = getRetryConfig(entityName, config2);
|
|
15095
|
-
await mapper.processEntityWithEvents(
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
this.
|
|
15102
|
-
|
|
15229
|
+
await mapper.processEntityWithEvents(
|
|
15230
|
+
configPath,
|
|
15231
|
+
entityConfig,
|
|
15232
|
+
retryConfig,
|
|
15233
|
+
signal,
|
|
15234
|
+
{
|
|
15235
|
+
onEntityStart: (payload) => this.safeEmit("entityStart", {
|
|
15236
|
+
...payload,
|
|
15237
|
+
waveIndex: wave.wave
|
|
15238
|
+
}),
|
|
15239
|
+
onEntityComplete: (payload) => {
|
|
15240
|
+
this.entitiesCompleted++;
|
|
15241
|
+
this.safeEmit("entityComplete", payload);
|
|
15242
|
+
},
|
|
15243
|
+
onRowSuccess: this.eventOptions.emitRowEvents ? (payload) => this.safeEmit("rowSuccess", payload) : void 0,
|
|
15244
|
+
onRowFailure: this.eventOptions.emitRowEvents ? (payload) => this.safeEmit("rowFailure", payload) : void 0
|
|
15103
15245
|
},
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
});
|
|
15246
|
+
outputStore
|
|
15247
|
+
);
|
|
15107
15248
|
} catch (error48) {
|
|
15108
15249
|
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
15109
15250
|
logger.warn(`Warning: Could not process ${configPath}: ${message}`);
|