@jackchuka/gql-ingest 3.1.9 → 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/README.md CHANGED
@@ -28,7 +28,7 @@ A TypeScript library and CLI tool that reads data from multiple formats (CSV, JS
28
28
  npm install -g @jackchuka/gql-ingest
29
29
 
30
30
  # Or use with npx (no installation required)
31
- npx @jackchuka/gql-ingest --endpoint <url> --config <path>
31
+ npx @jackchuka/gql-ingest -e <url> [-c config.yaml] entity1/entity.json entity2/entity.json
32
32
  ```
33
33
 
34
34
  ### For Development
@@ -46,13 +46,13 @@ Initialize a new configuration and start ingesting data in minutes:
46
46
 
47
47
  ```bash
48
48
  # Create a new configuration directory
49
- gql-ingest init ./my-config
49
+ gql-ingest init ./my-project
50
50
 
51
51
  # Add a new entity
52
- gql-ingest add users -p ./my-config -f json --fields "id,name,email"
52
+ gql-ingest add users -p ./my-project -f json --fields "id,name,email"
53
53
 
54
54
  # Run ingestion
55
- gql-ingest -e https://your-api.com/graphql -c ./my-config
55
+ gql-ingest -e https://your-api.com/graphql ./my-project/users/entity.json
56
56
  ```
57
57
 
58
58
  ## Usage
@@ -75,11 +75,14 @@ Options:
75
75
 
76
76
  This creates:
77
77
 
78
- - `data/` - Data files directory
79
- - `graphql/` - GraphQL mutation files
80
- - `mappings/` - Mapping configuration files
81
- - `config.yaml` - Processing configuration
82
- - Example entity files (by default)
78
+ ```
79
+ my-project/
80
+ ├── config.yaml
81
+ └── example/
82
+ ├── entity.json # entity definition (name: "example")
83
+ ├── example.csv
84
+ └── example.graphql
85
+ ```
83
86
 
84
87
  #### Add Entity
85
88
 
@@ -101,15 +104,14 @@ Interactive mode prompts for format, fields, and mutation name. Use `--no-intera
101
104
 
102
105
  #### Run Ingestion
103
106
 
104
- Ingest data from configuration into GraphQL API:
107
+ Ingest data from entity files into GraphQL API:
105
108
 
106
109
  ```bash
107
- gql-ingest [options]
110
+ gql-ingest [options] <entity files...>
108
111
 
109
112
  Options:
110
113
  -e, --endpoint <url> GraphQL endpoint URL (required)
111
- -c, --config <path> Path to configuration directory (required)
112
- -n, --entities <list> Comma-separated list of entities to process
114
+ -c, --config <path> Path to config.yaml (optional, for orchestration settings)
113
115
  -h, --headers <headers> JSON string of headers
114
116
  -f, --format <format> Override data format detection
115
117
  -q, --quiet Suppress output
@@ -118,22 +120,22 @@ Options:
118
120
  ### CLI Examples
119
121
 
120
122
  ```bash
121
- # Basic usage
123
+ # Basic usage — pass entity files as positional arguments
122
124
  gql-ingest \
123
125
  -e https://your-graphql-api.com/graphql \
124
- -c ./examples/demo
126
+ ./examples/demo/items/entity.json
125
127
 
126
128
  # With authentication headers
127
129
  gql-ingest \
128
130
  -e https://your-graphql-api.com/graphql \
129
- -c ./examples/demo \
130
- -h '{"Authorization": "Bearer YOUR_TOKEN"}'
131
+ -h '{"Authorization": "Bearer YOUR_TOKEN"}' \
132
+ ./examples/demo/users/entity.json ./examples/demo/items/entity.json
131
133
 
132
- # Process specific entities only
134
+ # With optional config for orchestration (retry, parallelism, dependencies)
133
135
  gql-ingest \
134
136
  -e https://your-graphql-api.com/graphql \
135
- -c ./examples/demo \
136
- -n users,products
137
+ -c ./examples/demo/config.yaml \
138
+ ./examples/demo/users/entity.json ./examples/demo/items/entity.json
137
139
  ```
138
140
 
139
141
  ### Programmatic API
@@ -160,8 +162,8 @@ const client = new GQLIngest({
160
162
  logger: createConsoleLogger({ prefix: "my-app" }), // Optional: enable logging with prefix
161
163
  });
162
164
 
163
- // Ingest all data from a configuration
164
- const result = await client.ingest("./config");
165
+ // Ingest all data from entity files
166
+ const result = await client.ingest(["./users/entity.json"]);
165
167
 
166
168
  // Check if ingestion was successful
167
169
  if (result.success) {
@@ -172,15 +174,14 @@ if (result.success) {
172
174
  }
173
175
  ```
174
176
 
175
- #### Processing Specific Entities
177
+ #### Processing Multiple Entities
176
178
 
177
179
  ```typescript
178
- // Process only specific entities
179
- const result = await client.ingestEntities("./config", ["users", "products"]);
180
+ // Process multiple entity files
181
+ const result = await client.ingest(["./users/entity.json", "./products/entity.json"]);
180
182
 
181
- // Or using the ingest method with options
182
- const result = await client.ingest("./config", {
183
- entities: ["users", "products"],
183
+ // With options
184
+ const result = await client.ingest(["./users/entity.json", "./products/entity.json"], {
184
185
  format: "csv", // Optional: override format detection
185
186
  });
186
187
  ```
@@ -203,10 +204,10 @@ import {
203
204
  const logger = createConsoleLogger();
204
205
  const metrics = new MetricsCollector();
205
206
  const client = new GraphQLClientWrapper(endpoint, headers, metrics, logger);
206
- const mapper = new DataMapper(client, basePath, metrics, logger);
207
+ const mapper = new DataMapper(client, metrics, logger);
207
208
 
208
209
  // Load configuration
209
- const config = loadConfig("./config");
210
+ const config = loadConfig();
210
211
 
211
212
  // Process entities with custom logic
212
213
  // ... your custom implementation
@@ -217,8 +218,7 @@ const config = loadConfig("./config");
217
218
  **GQLIngest Class Methods:**
218
219
 
219
220
  - `constructor(options: GQLIngestOptions)` - Initialize the client
220
- - `ingest(configPath: string, options?: IngestOptions)` - Ingest data from a configuration
221
- - `ingestEntities(configPath: string, entities: string[])` - Process specific entities
221
+ - `ingest(entityPaths: string[], options?: IngestOptions)` - Ingest data from entity files
222
222
  - `getMetrics()` - Get current processing metrics
223
223
  - `getMetricsSummary()` - Get formatted metrics summary
224
224
  - `setLogger(logger: Logger)` - Set custom logger
@@ -258,14 +258,14 @@ client.on("cancelled", (p) => console.log(`Cancelled: ${p.reason}`));
258
258
  // Handle graceful shutdown
259
259
  process.on("SIGINT", () => client.cancel("User interrupted"));
260
260
 
261
- await client.ingest("./config");
261
+ await client.ingest(["./users/entity.json"]);
262
262
  ```
263
263
 
264
264
  **Available Events:**
265
265
 
266
266
  | Event | When Emitted | Key Payload Fields |
267
267
  | ---------------- | ------------------------ | ------------------------------------------------- |
268
- | `started` | Ingestion begins | `configPath`, `entityNames`, `totalWaves` |
268
+ | `started` | Ingestion begins | `entityNames`, `totalWaves` |
269
269
  | `progress` | Periodic interval | `progressPercent`, `successfulRows`, `failedRows` |
270
270
  | `entityStart` | Entity processing begins | `entityName`, `totalRows`, `waveIndex` |
271
271
  | `entityComplete` | Entity processing ends | `entityName`, `metrics`, `success` |
@@ -283,12 +283,12 @@ Cancel in-progress ingestion using the `cancel()` method or external AbortContro
283
283
  // Method 1: Using cancel()
284
284
  const client = new GQLIngest({ endpoint: "..." });
285
285
  process.on("SIGINT", () => client.cancel("User interrupted"));
286
- await client.ingest("./config");
286
+ await client.ingest(["./users/entity.json"]);
287
287
 
288
288
  // Method 2: Using external AbortController
289
289
  const controller = new AbortController();
290
290
  setTimeout(() => controller.abort("Timeout"), 60000);
291
- await client.ingest("./config", { signal: controller.signal });
291
+ await client.ingest(["./users/entity.json"], { signal: controller.signal });
292
292
  ```
293
293
 
294
294
  #### TypeScript Support
@@ -389,33 +389,31 @@ entityConfig:
389
389
 
390
390
  ## Selective Entity Processing
391
391
 
392
- The `--entities` flag allows you to process specific entities instead of all discovered mappings:
392
+ Pass only the entity files you want to process as positional arguments:
393
393
 
394
- - Process multiple entities: `--entities users,products,orders`
395
- - Process a single entity: `--entities items`
396
- - Entities are processed in dependency order automatically
394
+ - Process multiple entities: `gql-ingest -e <url> users/entity.json products/entity.json`
395
+ - Process a single entity: `gql-ingest -e <url> items/entity.json`
396
+ - Entities are processed in dependency order automatically when `-c config.yaml` is provided
397
397
  - Missing dependencies will trigger a warning but not prevent execution
398
398
 
399
- **Note**: When using `--entities` with entity dependencies defined in `config.yaml`, the tool will warn you about any missing dependencies but will still attempt to process the selected entities. Ensure dependent data exists in your GraphQL API before processing entities with unmet dependencies.
399
+ **Note**: When using entity dependencies defined in `config.yaml`, the tool will warn you about any missing dependencies but will still attempt to process the selected entities. Ensure dependent data exists in your GraphQL API before processing entities with unmet dependencies.
400
400
 
401
401
  ## Configuration
402
402
 
403
- The `--config` flag points to a configuration directory containing these necessary files:
403
+ Each entity is defined by an `entity.json` file colocated with its data and GraphQL mutation files. The entity name comes from the `name` field inside the file, not from the filename or directory name. Paths in the entity file resolve relative to the entity file's directory.
404
404
 
405
- - `mappings/` - JSON files that map CSV columns to GraphQL variables
406
- - `config.yaml` - _(Optional)_ Parallel processing and dependency configuration
407
-
408
- Each entity has three corresponding files across these directories with matching names.
405
+ The optional `-c` flag points to a `config.yaml` file for orchestration settings (retry, parallelism, dependencies).
409
406
 
410
407
  ### Example Configuration
411
408
 
412
- **examples/demo/mappings/items.json**:
409
+ **examples/demo/items/entity.json** (entity definition):
413
410
 
414
411
  ```json
415
412
  {
416
- "dataFile": "data/items.csv",
413
+ "name": "items",
414
+ "dataFile": "items.csv",
417
415
  "dataFormat": "csv",
418
- "graphqlFile": "graphql/items.graphql",
416
+ "graphqlFile": "items.graphql",
419
417
  "mapping": {
420
418
  "name": "item_name",
421
419
  "sku": "item_sku"
@@ -423,7 +421,7 @@ Each entity has three corresponding files across these directories with matching
423
421
  }
424
422
  ```
425
423
 
426
- **examples/demo/data/items.csv**:
424
+ **examples/demo/items/items.csv**:
427
425
 
428
426
  ```csv
429
427
  item_name,item_sku
@@ -431,7 +429,7 @@ Item1,item-1-sku
431
429
  Item2,item-2-sku
432
430
  ```
433
431
 
434
- **examples/demo/graphql/items.graphql**:
432
+ **examples/demo/items/items.graphql**:
435
433
 
436
434
  ```graphql
437
435
  mutation CreateItem($name: String!, $sku: String!) {
@@ -487,11 +485,11 @@ GQL Ingest now supports multiple data formats beyond CSV for more flexible data
487
485
  The tool automatically detects the format based on file extension, or you can specify it explicitly:
488
486
 
489
487
  ```bash
490
- # Auto-detect from mapping configuration
491
- gql-ingest --endpoint <url> --config ./config
488
+ # Auto-detect from entity file configuration
489
+ gql-ingest -e <url> ./items/entity.json
492
490
 
493
491
  # Force specific format
494
- gql-ingest --endpoint <url> --config ./config --format json
492
+ gql-ingest -e <url> --format json ./items/entity.json
495
493
  ```
496
494
 
497
495
  ### JSON/YAML Format Examples
@@ -500,7 +498,7 @@ gql-ingest --endpoint <url> --config ./config --format json
500
498
 
501
499
  For complex GraphQL mutations with nested input types, you can map the entire data object:
502
500
 
503
- **data/products.json**:
501
+ **products/products.json** (data file):
504
502
 
505
503
  ```json
506
504
  [
@@ -531,13 +529,14 @@ For complex GraphQL mutations with nested input types, you can map the entire da
531
529
  ]
532
530
  ```
533
531
 
534
- **mappings/products.json**:
532
+ **products/entity.json** (entity definition):
535
533
 
536
534
  ```json
537
535
  {
538
- "dataFile": "data/products.json",
536
+ "name": "products",
537
+ "dataFile": "products.json",
539
538
  "dataFormat": "json",
540
- "graphqlFile": "graphql/newProduct.graphql",
539
+ "graphqlFile": "newProduct.graphql",
541
540
  "mapping": {
542
541
  "input": "$" // Map entire object to input variable
543
542
  }
@@ -548,7 +547,7 @@ For complex GraphQL mutations with nested input types, you can map the entire da
548
547
 
549
548
  For transforming flat JSON into nested structures:
550
549
 
551
- **data/products-flat.json**:
550
+ **products/products-flat.json** (data file):
552
551
 
553
552
  ```json
554
553
  [
@@ -560,12 +559,13 @@ For transforming flat JSON into nested structures:
560
559
  ]
561
560
  ```
562
561
 
563
- **mappings/products-flat.json**:
562
+ **products/entity.json** (entity definition with path-based mapping):
564
563
 
565
564
  ```json
566
565
  {
567
- "dataFile": "data/products-flat.json",
568
- "graphqlFile": "graphql/newProduct.graphql",
566
+ "name": "products",
567
+ "dataFile": "products-flat.json",
568
+ "graphqlFile": "newProduct.graphql",
569
569
  "mapping": {
570
570
  "input": {
571
571
  "name": "$.product_name",
@@ -576,11 +576,59 @@ For transforming flat JSON into nested structures:
576
576
  }
577
577
  ```
578
578
 
579
+ #### Cross-Entity References
580
+
581
+ When one entity needs values produced by another (e.g., a server-generated ID), use `outputCapture` and `$ref`:
582
+
583
+ **users/entity.json** (captures the created ID):
584
+
585
+ ```json
586
+ {
587
+ "name": "users",
588
+ "dataFile": "users.csv",
589
+ "graphqlFile": "users.graphql",
590
+ "mapping": { "name": "user_name", "email": "user_email" },
591
+ "outputCapture": {
592
+ "key": "$.user_name",
593
+ "fields": { "id": "$.createUser.id" }
594
+ }
595
+ }
596
+ ```
597
+
598
+ - `outputCapture.key` — JSONPath into the input row, used as the lookup key
599
+ - `outputCapture.fields` — map of field names to JSONPaths into the mutation response
600
+
601
+ **items/entity.json** (references the captured user ID):
602
+
603
+ ```json
604
+ {
605
+ "name": "items",
606
+ "dataFile": "items.csv",
607
+ "graphqlFile": "items.graphql",
608
+ "mapping": {
609
+ "name": "item_name",
610
+ "sku": "item_sku",
611
+ "createdBy": { "$ref": "users", "key": "$.owner_name", "field": "id" }
612
+ }
613
+ }
614
+ ```
615
+
616
+ - `$ref` — the entity name that captured the value
617
+ - `key` — JSONPath into the current row to get the lookup key
618
+ - `field` — the captured field name to retrieve
619
+
620
+ The referenced entity must be processed first. Entity files are processed in the order they are passed, but you can use `entityDependencies` in `config.yaml` to make the ordering explicit:
621
+
622
+ ```yaml
623
+ entityDependencies:
624
+ items: ["users"]
625
+ ```
626
+
579
627
  ### YAML Format
580
628
 
581
629
  YAML provides a more readable alternative:
582
630
 
583
- **data/products.yaml**:
631
+ **products/products.yaml**:
584
632
 
585
633
  ```yaml
586
634
  - name: Premium T-Shirt
@@ -614,21 +662,22 @@ pnpm run test # Run test suite
614
662
 
615
663
  ## How It Works
616
664
 
617
- 1. **Discovery**: The tool scans the `mappings/` directory for `.json` files
618
- 2. **Dependency Resolution**: Analyzes `entityDependencies` to create execution waves
619
- 3. **Parallel Processing**: For each dependency wave:
665
+ 1. **Entity Loading**: The tool reads the entity JSON files passed as arguments
666
+ 2. **Path Resolution**: Data files and GraphQL mutations are resolved relative to each entity file's directory
667
+ 3. **Dependency Resolution**: If `-c config.yaml` is provided, analyzes `entityDependencies` to create execution waves
668
+ 4. **Parallel Processing**: For each dependency wave:
620
669
  - Processes up to `entityConcurrency` entities simultaneously
621
- - Within each entity, processes up to `concurrency` CSV rows concurrently
670
+ - Within each entity, processes up to `concurrency` rows concurrently
622
671
  - Waits for the entire wave to complete before starting the next wave
623
- 4. **GraphQL Execution**: For each CSV row:
672
+ 5. **GraphQL Execution**: For each data row:
624
673
  - Loads the GraphQL mutation definition
625
- - Maps CSV columns to GraphQL variables using the mapping configuration
674
+ - Maps data fields to GraphQL variables using the mapping configuration
626
675
  - Executes the mutation against the GraphQL endpoint
627
- 5. **Error Handling & Retries**:
676
+ 6. **Error Handling & Retries**:
628
677
  - Failed mutations are automatically retried with exponential backoff
629
678
  - Non-retryable errors (e.g., validation failures) are logged and skipped
630
679
  - Configurable retry policies per entity type
631
- 6. **Metrics & Monitoring**:
680
+ 7. **Metrics & Monitoring**:
632
681
  - Real-time progress tracking and success/failure rates
633
682
  - Retry attempt counts and success rates
634
683
  - Detailed per-entity performance breakdown
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoE1D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkE1D"}