@jackchuka/gql-ingest 4.0.0 → 4.1.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/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 = mappingValue.substring(2);
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,10 @@ 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);
14537
14551
  }
14538
14552
  }
14539
14553
  return variables;
@@ -14550,15 +14564,18 @@ var DataMapper = class {
14550
14564
  }
14551
14565
  return current;
14552
14566
  }
14553
- mapNestedObject(row, mappingObj, variableTypes) {
14567
+ mapNestedObject(row, mappingObj, variableTypes, outputStore) {
14554
14568
  if (Array.isArray(mappingObj)) {
14555
- return mappingObj.map((item) => this.mapNestedObject(row, item, variableTypes));
14569
+ return mappingObj.map((item) => this.mapNestedObject(row, item, variableTypes, outputStore));
14556
14570
  }
14557
14571
  if (typeof mappingObj === "object" && mappingObj !== null) {
14572
+ if (this.isRefMapping(mappingObj)) {
14573
+ return this.resolveRef(row, mappingObj, outputStore);
14574
+ }
14558
14575
  const result = {};
14559
14576
  for (const [key, value] of Object.entries(mappingObj)) {
14560
14577
  if (typeof value === "string" && value.startsWith("$.")) {
14561
- const dataPath = value.substring(2);
14578
+ const dataPath = this.stripJsonPathPrefix(value);
14562
14579
  let fieldValue = this.getValueByPath(row, dataPath);
14563
14580
  if (key === "values" && typeof fieldValue === "string" && fieldValue.includes(",")) {
14564
14581
  fieldValue = fieldValue.split(",").map((v) => v.trim());
@@ -14566,8 +14583,10 @@ var DataMapper = class {
14566
14583
  result[key] = fieldValue;
14567
14584
  } else if (typeof value === "string" && row[value] !== void 0) {
14568
14585
  result[key] = row[value];
14586
+ } else if (this.isRefMapping(value)) {
14587
+ result[key] = this.resolveRef(row, value, outputStore);
14569
14588
  } else if (typeof value === "object") {
14570
- result[key] = this.mapNestedObject(row, value, variableTypes);
14589
+ result[key] = this.mapNestedObject(row, value, variableTypes, outputStore);
14571
14590
  } else {
14572
14591
  result[key] = value;
14573
14592
  }
@@ -14576,6 +14595,68 @@ var DataMapper = class {
14576
14595
  }
14577
14596
  return mappingObj;
14578
14597
  }
14598
+ stripJsonPathPrefix(jsonPath) {
14599
+ return jsonPath.startsWith("$.") ? jsonPath.substring(2) : jsonPath;
14600
+ }
14601
+ isRefMapping(value) {
14602
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "$ref" in value && "key" in value && "field" in value;
14603
+ }
14604
+ resolveRef(row, ref, outputStore) {
14605
+ if (!outputStore) {
14606
+ throw new RefResolutionError(`$ref to "${ref.$ref}" found but no output store is available`);
14607
+ }
14608
+ const keyPath = this.stripJsonPathPrefix(ref.key);
14609
+ const lookupKeyValue = this.getValueByPath(row, keyPath);
14610
+ if (lookupKeyValue === void 0) {
14611
+ throw new RefResolutionError(`$ref lookup key "${ref.key}" not found in current row`);
14612
+ }
14613
+ const keyStr = String(lookupKeyValue);
14614
+ const entityStore = outputStore.get(ref.$ref);
14615
+ if (!entityStore) {
14616
+ throw new RefResolutionError(`$ref entity "${ref.$ref}" not found in output store`);
14617
+ }
14618
+ const captured = entityStore.get(keyStr);
14619
+ if (!captured) {
14620
+ throw new RefResolutionError(
14621
+ `$ref key "${keyStr}" not found in entity "${ref.$ref}" output store`
14622
+ );
14623
+ }
14624
+ const value = captured[ref.field];
14625
+ if (value === void 0) {
14626
+ throw new RefResolutionError(
14627
+ `$ref field "${ref.field}" not found in captured output for "${ref.$ref}[${keyStr}]"`
14628
+ );
14629
+ }
14630
+ return value;
14631
+ }
14632
+ captureOutput(row, result, entityName, outputCapture, outputStore) {
14633
+ if (!outputCapture || !outputStore) return;
14634
+ const keyPath = this.stripJsonPathPrefix(outputCapture.key);
14635
+ const keyValue = this.getValueByPath(row, keyPath);
14636
+ if (keyValue === void 0) {
14637
+ this.logger.warn(
14638
+ `outputCapture key "${outputCapture.key}" not found in input row for entity "${entityName}". Skipping capture.`
14639
+ );
14640
+ return;
14641
+ }
14642
+ const keyStr = String(keyValue);
14643
+ const captured = {};
14644
+ for (const [fieldName, responsePath] of Object.entries(outputCapture.fields)) {
14645
+ const dataPath = this.stripJsonPathPrefix(responsePath);
14646
+ captured[fieldName] = this.getValueByPath(result, dataPath);
14647
+ }
14648
+ let entityStore = outputStore.get(entityName);
14649
+ if (!entityStore) {
14650
+ entityStore = /* @__PURE__ */ new Map();
14651
+ outputStore.set(entityName, entityStore);
14652
+ }
14653
+ if (entityStore.has(keyStr)) {
14654
+ this.logger.warn(
14655
+ `outputCapture: duplicate key "${keyStr}" for entity "${entityName}" \u2014 overwriting previous value`
14656
+ );
14657
+ }
14658
+ entityStore.set(keyStr, captured);
14659
+ }
14579
14660
  extractVariableTypes(mutation) {
14580
14661
  const types = {};
14581
14662
  try {
@@ -14742,6 +14823,7 @@ var DEFAULT_EVENT_OPTIONS = {
14742
14823
  var GQLIngest = class extends EventEmitter {
14743
14824
  constructor(options) {
14744
14825
  super();
14826
+ this.outputStore = /* @__PURE__ */ new Map();
14745
14827
  // Cancellation state
14746
14828
  this.abortController = null;
14747
14829
  this.isProcessing = false;
@@ -14884,6 +14966,7 @@ var GQLIngest = class extends EventEmitter {
14884
14966
  return this.handleCancellation(signal.reason);
14885
14967
  }
14886
14968
  this.metrics = new MetricsCollector();
14969
+ this.outputStore = /* @__PURE__ */ new Map();
14887
14970
  this.client = new GraphQLClientWrapper(
14888
14971
  this.endpoint,
14889
14972
  this.headers,
@@ -14961,7 +15044,8 @@ var GQLIngest = class extends EventEmitter {
14961
15044
  this.mapper,
14962
15045
  config2,
14963
15046
  this.logger,
14964
- signal
15047
+ signal,
15048
+ this.outputStore
14965
15049
  );
14966
15050
  if (signal.aborted) {
14967
15051
  return this.handleCancellation(signal.reason);
@@ -15067,7 +15151,7 @@ var GQLIngest = class extends EventEmitter {
15067
15151
  /**
15068
15152
  * Process entities in dependency-aware waves with abort support
15069
15153
  */
15070
- async processEntitiesInWaves(pathMap, resolver, mapper, config2, logger, signal) {
15154
+ async processEntitiesInWaves(pathMap, resolver, mapper, config2, logger, signal, outputStore) {
15071
15155
  const waves = resolver.resolveExecutionOrder();
15072
15156
  logger.info(`Processing ${waves.length} dependency waves...`);
15073
15157
  for (const wave of waves) {
@@ -15092,18 +15176,25 @@ var GQLIngest = class extends EventEmitter {
15092
15176
  try {
15093
15177
  const entityConfig = getEntityConfig(entityName, config2, logger);
15094
15178
  const retryConfig = getRetryConfig(entityName, config2);
15095
- await mapper.processEntityWithEvents(configPath, entityConfig, retryConfig, signal, {
15096
- onEntityStart: (payload) => this.safeEmit("entityStart", {
15097
- ...payload,
15098
- waveIndex: wave.wave
15099
- }),
15100
- onEntityComplete: (payload) => {
15101
- this.entitiesCompleted++;
15102
- this.safeEmit("entityComplete", payload);
15179
+ await mapper.processEntityWithEvents(
15180
+ configPath,
15181
+ entityConfig,
15182
+ retryConfig,
15183
+ signal,
15184
+ {
15185
+ onEntityStart: (payload) => this.safeEmit("entityStart", {
15186
+ ...payload,
15187
+ waveIndex: wave.wave
15188
+ }),
15189
+ onEntityComplete: (payload) => {
15190
+ this.entitiesCompleted++;
15191
+ this.safeEmit("entityComplete", payload);
15192
+ },
15193
+ onRowSuccess: this.eventOptions.emitRowEvents ? (payload) => this.safeEmit("rowSuccess", payload) : void 0,
15194
+ onRowFailure: this.eventOptions.emitRowEvents ? (payload) => this.safeEmit("rowFailure", payload) : void 0
15103
15195
  },
15104
- onRowSuccess: this.eventOptions.emitRowEvents ? (payload) => this.safeEmit("rowSuccess", payload) : void 0,
15105
- onRowFailure: this.eventOptions.emitRowEvents ? (payload) => this.safeEmit("rowFailure", payload) : void 0
15106
- });
15196
+ outputStore
15197
+ );
15107
15198
  } catch (error48) {
15108
15199
  const message = error48 instanceof Error ? error48.message : String(error48);
15109
15200
  logger.warn(`Warning: Could not process ${configPath}: ${message}`);