@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.56
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/CHANGELOG.md +12 -0
- package/README.md +11 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,1487 +1,1487 @@
|
|
|
1
|
-
# File Operations Error Handling Guide
|
|
2
|
-
|
|
3
|
-
> **Complete guide to handling errors when reading and parsing files from S3 and SFTP**
|
|
4
|
-
> Production-ready patterns for connection errors, file discovery, reading, and parsing failures
|
|
5
|
-
|
|
6
|
-
## Table of Contents
|
|
7
|
-
|
|
8
|
-
- [Overview](#overview)
|
|
9
|
-
- [Error Flow Diagram](#error-flow-diagram)
|
|
10
|
-
- [Connection Errors](#connection-errors)
|
|
11
|
-
- [Discovery Errors](#discovery-errors)
|
|
12
|
-
- [Reading Errors](#reading-errors)
|
|
13
|
-
- [Parsing Errors](#parsing-errors)
|
|
14
|
-
- [Complete Examples](#complete-examples)
|
|
15
|
-
- [Retry Strategies](#retry-strategies)
|
|
16
|
-
- [Troubleshooting Flowchart](#troubleshooting-flowchart)
|
|
17
|
-
- [Production Checklist](#production-checklist)
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Overview
|
|
22
|
-
|
|
23
|
-
When working with S3 or SFTP data sources, errors can occur at multiple stages:
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
┌─────────────────┐
|
|
27
|
-
│ 1. Connection │ ← Credentials, network, timeouts
|
|
28
|
-
├─────────────────┤
|
|
29
|
-
│ 2. Discovery │ ← File listing, permissions, paths
|
|
30
|
-
├─────────────────┤
|
|
31
|
-
│ 3. Reading │ ← Download, memory, streaming
|
|
32
|
-
├─────────────────┤
|
|
33
|
-
│ 4. Parsing │ ← CSV/XML/JSON/Parquet format errors
|
|
34
|
-
└─────────────────┘
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
This guide covers **all four stages** with production-ready error handling patterns.
|
|
38
|
-
|
|
39
|
-
### Key Error Types
|
|
40
|
-
|
|
41
|
-
| Error Class | Stage | Retryable? | Common Causes |
|
|
42
|
-
|-------------|-------|------------|---------------|
|
|
43
|
-
| `FileDiscoveryError` | Connection/Discovery | ✅ Usually | Invalid credentials, network timeout, permissions |
|
|
44
|
-
| `FileParsingError` | Parsing | ❌ No | Malformed XML/CSV/JSON, encoding issues |
|
|
45
|
-
| `IngestionError` (NETWORK_ERROR) | Connection/Reading | ✅ Yes | Network timeout, API unreachable |
|
|
46
|
-
| Memory errors | Reading | ⚠️ Sometimes | File too large, insufficient heap |
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Error Flow Diagram
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
┌──────────────────────────────────────────────────────────────┐
|
|
54
|
-
│ START: Read and Parse File from S3/SFTP │
|
|
55
|
-
└────────────────────┬─────────────────────────────────────────┘
|
|
56
|
-
│
|
|
57
|
-
▼
|
|
58
|
-
┌──────────────────────┐
|
|
59
|
-
│ Create Data Source │
|
|
60
|
-
│ (S3/SFTP) │
|
|
61
|
-
└──────┬───────────────┘
|
|
62
|
-
│
|
|
63
|
-
▼
|
|
64
|
-
┌──────────────────────┐
|
|
65
|
-
│ Validate Connection │
|
|
66
|
-
└──────┬───────────────┘
|
|
67
|
-
│
|
|
68
|
-
├─────────── ❌ FileDiscoveryError
|
|
69
|
-
│ → Invalid credentials
|
|
70
|
-
│ → Network timeout
|
|
71
|
-
│ → Permissions denied
|
|
72
|
-
│ → RETRY? Yes (if transient)
|
|
73
|
-
│
|
|
74
|
-
▼
|
|
75
|
-
┌──────────────────────┐
|
|
76
|
-
│ List Files │
|
|
77
|
-
│ (discovery) │
|
|
78
|
-
└──────┬───────────────┘
|
|
79
|
-
│
|
|
80
|
-
├─────────── ❌ FileDiscoveryError
|
|
81
|
-
│ → Bucket/path not found
|
|
82
|
-
│ → Access denied
|
|
83
|
-
│ → RETRY? Usually No (config error)
|
|
84
|
-
│
|
|
85
|
-
▼
|
|
86
|
-
┌──────────────────────┐
|
|
87
|
-
│ Download File │
|
|
88
|
-
│ (read content) │
|
|
89
|
-
└──────┬───────────────┘
|
|
90
|
-
│
|
|
91
|
-
├─────────── ❌ IngestionError (NETWORK_ERROR)
|
|
92
|
-
│ → Network timeout
|
|
93
|
-
│ → Presigned URL expired (S3)
|
|
94
|
-
│ → SFTP connection lost
|
|
95
|
-
│ → RETRY? Yes
|
|
96
|
-
│
|
|
97
|
-
├─────────── ❌ Memory Error
|
|
98
|
-
│ → File too large
|
|
99
|
-
│ → Insufficient heap
|
|
100
|
-
│ → RETRY? No (use streaming)
|
|
101
|
-
│
|
|
102
|
-
▼
|
|
103
|
-
┌──────────────────────┐
|
|
104
|
-
│ Parse Content │
|
|
105
|
-
│ (CSV/XML/JSON) │
|
|
106
|
-
└──────┬───────────────┘
|
|
107
|
-
│
|
|
108
|
-
├─────────── ❌ FileParsingError
|
|
109
|
-
│ → Malformed XML (missing tags)
|
|
110
|
-
│ → Invalid JSON (syntax error)
|
|
111
|
-
│ → CSV column mismatch
|
|
112
|
-
│ → Encoding error (non-UTF-8)
|
|
113
|
-
│ → RETRY? No (fix source data)
|
|
114
|
-
│
|
|
115
|
-
▼
|
|
116
|
-
┌──────────────────────┐
|
|
117
|
-
│ ✅ SUCCESS │
|
|
118
|
-
│ Parsed data ready │
|
|
119
|
-
└──────────────────────┘
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Connection Errors
|
|
125
|
-
|
|
126
|
-
### S3 Connection Errors
|
|
127
|
-
|
|
128
|
-
#### Error 1: Invalid Credentials
|
|
129
|
-
|
|
130
|
-
**Symptoms:**
|
|
131
|
-
```
|
|
132
|
-
FileDiscoveryError: Access Denied
|
|
133
|
-
code: FILE_ACCESS_DENIED
|
|
134
|
-
message: "The AWS Access Key Id you provided does not exist in our records"
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
**Causes:**
|
|
138
|
-
- Invalid `accessKeyId` or `secretAccessKey`
|
|
139
|
-
- Credentials expired (if using temporary credentials)
|
|
140
|
-
- Wrong AWS region specified
|
|
141
|
-
|
|
142
|
-
**Solution:**
|
|
143
|
-
```typescript
|
|
144
|
-
import {
|
|
145
|
-
S3DataSource,
|
|
146
|
-
FileDiscoveryError,
|
|
147
|
-
createConsoleLogger,
|
|
148
|
-
toStructuredLogger
|
|
149
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
150
|
-
|
|
151
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
152
|
-
logLevel: 'info'
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
async function createS3Source() {
|
|
156
|
-
try {
|
|
157
|
-
const s3Source = new S3DataSource(
|
|
158
|
-
{
|
|
159
|
-
type: 'S3_CSV',
|
|
160
|
-
s3Config: {
|
|
161
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
162
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
163
|
-
region: process.env.AWS_REGION || 'us-east-1',
|
|
164
|
-
bucket: process.env.S3_BUCKET,
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
logger
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// ✅ IMPORTANT: Validate connection before proceeding
|
|
171
|
-
const isValid = await s3Source.validateConnection();
|
|
172
|
-
if (!isValid) {
|
|
173
|
-
throw new Error('S3 connection validation failed');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return s3Source;
|
|
177
|
-
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (error instanceof FileDiscoveryError &&
|
|
180
|
-
error.code === 'FILE_ACCESS_DENIED') {
|
|
181
|
-
logger.error('S3 credentials invalid:', {
|
|
182
|
-
bucket: process.env.S3_BUCKET,
|
|
183
|
-
region: process.env.AWS_REGION,
|
|
184
|
-
// DO NOT log credentials!
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// Don't retry - configuration error
|
|
188
|
-
throw new Error('S3 credentials are invalid. Check AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY');
|
|
189
|
-
}
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
**Debugging Checklist:**
|
|
196
|
-
- [ ] Verify `AWS_ACCESS_KEY_ID` is correct
|
|
197
|
-
- [ ] Verify `AWS_SECRET_ACCESS_KEY` is correct
|
|
198
|
-
- [ ] Check if credentials are expired (temporary credentials)
|
|
199
|
-
- [ ] Verify AWS region matches bucket region
|
|
200
|
-
- [ ] Test credentials with AWS CLI: `aws s3 ls s3://your-bucket`
|
|
201
|
-
|
|
202
|
-
#### Error 2: Wrong Region
|
|
203
|
-
|
|
204
|
-
**Symptoms:**
|
|
205
|
-
```
|
|
206
|
-
FileDiscoveryError: The bucket is in this region: us-west-2.
|
|
207
|
-
Please use this region to retry the request
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**Solution:**
|
|
211
|
-
```typescript
|
|
212
|
-
// ❌ WRONG: Hardcoded region doesn't match bucket
|
|
213
|
-
const s3Source = new S3DataSource({
|
|
214
|
-
type: 'S3_CSV',
|
|
215
|
-
s3Config: {
|
|
216
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
217
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
218
|
-
region: 'us-east-1', // ❌ Bucket is in us-west-2
|
|
219
|
-
bucket: 'my-bucket',
|
|
220
|
-
},
|
|
221
|
-
}, logger);
|
|
222
|
-
|
|
223
|
-
// ✅ CORRECT: Use environment variable or bucket's actual region
|
|
224
|
-
const s3Source = new S3DataSource({
|
|
225
|
-
type: 'S3_CSV',
|
|
226
|
-
s3Config: {
|
|
227
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
228
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
229
|
-
region: process.env.AWS_REGION!, // ✅ Set to 'us-west-2'
|
|
230
|
-
bucket: 'my-bucket',
|
|
231
|
-
},
|
|
232
|
-
}, logger);
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
#### Error 3: Network Timeout (S3)
|
|
236
|
-
|
|
237
|
-
**Symptoms:**
|
|
238
|
-
```
|
|
239
|
-
IngestionError: Network request timed out
|
|
240
|
-
code: NETWORK_ERROR
|
|
241
|
-
isRetryable: true
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**Solution with Retry:**
|
|
245
|
-
```typescript
|
|
246
|
-
import { IngestionError, IngestionErrorCode } from '@fluentcommerce/fc-connect-sdk';
|
|
247
|
-
|
|
248
|
-
async function downloadWithRetry(
|
|
249
|
-
s3Source: S3DataSource,
|
|
250
|
-
filePath: string,
|
|
251
|
-
maxRetries = 3
|
|
252
|
-
) {
|
|
253
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
254
|
-
try {
|
|
255
|
-
const content = await s3Source.downloadFile(filePath);
|
|
256
|
-
return content; // ✅ Success
|
|
257
|
-
|
|
258
|
-
} catch (error) {
|
|
259
|
-
if (error instanceof IngestionError &&
|
|
260
|
-
error.code === IngestionErrorCode.NETWORK_ERROR &&
|
|
261
|
-
error.isRetryable() &&
|
|
262
|
-
attempt < maxRetries) {
|
|
263
|
-
|
|
264
|
-
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
|
|
265
|
-
logger.warn(`S3 network error - retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
|
|
266
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
throw error; // Not retryable or max retries exceeded
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### SFTP Connection Errors
|
|
277
|
-
|
|
278
|
-
#### Error 1: Authentication Failed
|
|
279
|
-
|
|
280
|
-
**Symptoms:**
|
|
281
|
-
```
|
|
282
|
-
FileDiscoveryError: All configured authentication methods failed
|
|
283
|
-
code: FILE_ACCESS_DENIED
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Causes:**
|
|
287
|
-
- Invalid SSH private key
|
|
288
|
-
- Wrong username
|
|
289
|
-
- Private key passphrase incorrect
|
|
290
|
-
- Host key verification failed
|
|
291
|
-
|
|
292
|
-
**Solution:**
|
|
293
|
-
```typescript
|
|
294
|
-
import { SftpDataSource, FileDiscoveryError } from '@fluentcommerce/fc-connect-sdk';
|
|
295
|
-
|
|
296
|
-
async function createSftpSource() {
|
|
297
|
-
try {
|
|
298
|
-
const sftpSource = new SftpDataSource(
|
|
299
|
-
{
|
|
300
|
-
type: 'SFTP_CSV',
|
|
301
|
-
connectionId: 'vendor-sftp',
|
|
302
|
-
settings: {
|
|
303
|
-
host: process.env.SFTP_HOST!,
|
|
304
|
-
port: parseInt(process.env.SFTP_PORT || '22'),
|
|
305
|
-
username: process.env.SFTP_USERNAME!,
|
|
306
|
-
privateKey: process.env.SFTP_PRIVATE_KEY!, // ✅ Use SSH key (not password)
|
|
307
|
-
passphrase: process.env.SFTP_PASSPHRASE, // If key is encrypted
|
|
308
|
-
remotePath: process.env.SFTP_REMOTE_PATH || '/data',
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
logger
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
// ✅ Validate connection
|
|
315
|
-
const isValid = await sftpSource.validateConnection();
|
|
316
|
-
if (!isValid) {
|
|
317
|
-
throw new Error('SFTP connection validation failed');
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return sftpSource;
|
|
321
|
-
|
|
322
|
-
} catch (error) {
|
|
323
|
-
if (error instanceof FileDiscoveryError &&
|
|
324
|
-
error.code === 'FILE_ACCESS_DENIED') {
|
|
325
|
-
logger.error('SFTP authentication failed:', {
|
|
326
|
-
host: process.env.SFTP_HOST,
|
|
327
|
-
username: process.env.SFTP_USERNAME,
|
|
328
|
-
// DO NOT log private key or passphrase!
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
// Check common issues
|
|
332
|
-
throw new Error(
|
|
333
|
-
'SFTP authentication failed. Check:\n' +
|
|
334
|
-
'1. SSH private key is correct\n' +
|
|
335
|
-
'2. Username is correct\n' +
|
|
336
|
-
'3. Passphrase is correct (if key is encrypted)\n' +
|
|
337
|
-
'4. Host key is trusted'
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
throw error;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
#### Error 2: Connection Timeout (SFTP)
|
|
346
|
-
|
|
347
|
-
**Symptoms:**
|
|
348
|
-
```
|
|
349
|
-
FileDiscoveryError: Connection timeout after 30000ms
|
|
350
|
-
code: FILE_DISCOVERY_FAILED
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**Causes:**
|
|
354
|
-
- SFTP server unreachable
|
|
355
|
-
- Firewall blocking port 22
|
|
356
|
-
- Network connectivity issues
|
|
357
|
-
- DNS resolution failure
|
|
358
|
-
|
|
359
|
-
**Solution:**
|
|
360
|
-
```typescript
|
|
361
|
-
async function testSftpConnectivity() {
|
|
362
|
-
const sftpConfig = {
|
|
363
|
-
type: 'SFTP_CSV' as const,
|
|
364
|
-
connectionId: 'test-sftp',
|
|
365
|
-
settings: {
|
|
366
|
-
host: process.env.SFTP_HOST!,
|
|
367
|
-
port: 22,
|
|
368
|
-
username: process.env.SFTP_USERNAME!,
|
|
369
|
-
privateKey: process.env.SFTP_PRIVATE_KEY!,
|
|
370
|
-
remotePath: '/data',
|
|
371
|
-
timeout: 30000, // 30 seconds
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const sftpSource = new SftpDataSource(sftpConfig, logger);
|
|
377
|
-
|
|
378
|
-
logger.info('Testing SFTP connectivity...');
|
|
379
|
-
const isValid = await sftpSource.validateConnection();
|
|
380
|
-
|
|
381
|
-
if (isValid) {
|
|
382
|
-
logger.info('✅ SFTP connection successful');
|
|
383
|
-
return sftpSource;
|
|
384
|
-
} else {
|
|
385
|
-
throw new Error('SFTP validation returned false');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
} catch (error) {
|
|
389
|
-
if (error instanceof FileDiscoveryError) {
|
|
390
|
-
logger.error('SFTP connection failed:', {
|
|
391
|
-
host: sftpConfig.settings.host,
|
|
392
|
-
port: sftpConfig.settings.port,
|
|
393
|
-
error: error.message,
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// Debugging steps
|
|
397
|
-
console.error('\n🔍 Debugging steps:');
|
|
398
|
-
console.error(`1. Test DNS: ping ${sftpConfig.settings.host}`);
|
|
399
|
-
console.error(`2. Test connectivity: telnet ${sftpConfig.settings.host} 22`);
|
|
400
|
-
console.error('3. Check firewall allows outbound port 22');
|
|
401
|
-
console.error('4. Verify SFTP server is running');
|
|
402
|
-
}
|
|
403
|
-
throw error;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
## Discovery Errors
|
|
411
|
-
|
|
412
|
-
### Error 1: File Not Found
|
|
413
|
-
|
|
414
|
-
**Symptoms:**
|
|
415
|
-
```
|
|
416
|
-
FileDiscoveryError: File not found
|
|
417
|
-
code: FILE_NOT_FOUND
|
|
418
|
-
path: "s3://my-bucket/inventory/missing.csv"
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
**Solution:**
|
|
422
|
-
```typescript
|
|
423
|
-
async function safeFileDownload(
|
|
424
|
-
dataSource: S3DataSource | SftpDataSource,
|
|
425
|
-
filePath: string
|
|
426
|
-
) {
|
|
427
|
-
try {
|
|
428
|
-
// Check if file exists first
|
|
429
|
-
const files = await dataSource.listFiles({ prefix: filePath });
|
|
430
|
-
|
|
431
|
-
if (files.length === 0) {
|
|
432
|
-
logger.warn(`File not found: ${filePath}`);
|
|
433
|
-
return null; // Return null instead of throwing
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// File exists - download it
|
|
437
|
-
const content = await dataSource.downloadFile(filePath);
|
|
438
|
-
return content;
|
|
439
|
-
|
|
440
|
-
} catch (error) {
|
|
441
|
-
if (error instanceof FileDiscoveryError &&
|
|
442
|
-
error.code === 'FILE_NOT_FOUND') {
|
|
443
|
-
logger.warn('File not found during download:', { path: filePath });
|
|
444
|
-
return null; // Graceful handling
|
|
445
|
-
}
|
|
446
|
-
throw error; // Re-throw other errors
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### Error 2: Access Denied (Permissions)
|
|
452
|
-
|
|
453
|
-
**Symptoms:**
|
|
454
|
-
```
|
|
455
|
-
FileDiscoveryError: Access Denied
|
|
456
|
-
code: FILE_ACCESS_DENIED
|
|
457
|
-
message: "User is not authorized to perform: s3:GetObject"
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
**IAM Policy Required (S3):**
|
|
461
|
-
```json
|
|
462
|
-
{
|
|
463
|
-
"Version": "2012-10-17",
|
|
464
|
-
"Statement": [
|
|
465
|
-
{
|
|
466
|
-
"Effect": "Allow",
|
|
467
|
-
"Action": [
|
|
468
|
-
"s3:ListBucket",
|
|
469
|
-
"s3:GetObject",
|
|
470
|
-
"s3:GetObjectVersion"
|
|
471
|
-
],
|
|
472
|
-
"Resource": [
|
|
473
|
-
"arn:aws:s3:::my-bucket",
|
|
474
|
-
"arn:aws:s3:::my-bucket/*"
|
|
475
|
-
]
|
|
476
|
-
}
|
|
477
|
-
]
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
**Solution:**
|
|
482
|
-
```typescript
|
|
483
|
-
async function validateS3Permissions(s3Source: S3DataSource) {
|
|
484
|
-
try {
|
|
485
|
-
logger.info('Validating S3 permissions...');
|
|
486
|
-
|
|
487
|
-
// Test 1: Can list bucket?
|
|
488
|
-
try {
|
|
489
|
-
await s3Source.listFiles({ prefix: '' });
|
|
490
|
-
logger.info('✅ s3:ListBucket permission OK');
|
|
491
|
-
} catch (error) {
|
|
492
|
-
logger.error('❌ s3:ListBucket permission DENIED');
|
|
493
|
-
throw error;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Test 2: Can read objects?
|
|
497
|
-
const files = await s3Source.listFiles({ prefix: '' });
|
|
498
|
-
if (files.length > 0) {
|
|
499
|
-
try {
|
|
500
|
-
await s3Source.downloadFile(files[0].path);
|
|
501
|
-
logger.info('✅ s3:GetObject permission OK');
|
|
502
|
-
} catch (error) {
|
|
503
|
-
logger.error('❌ s3:GetObject permission DENIED');
|
|
504
|
-
throw error;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
logger.info('✅ All S3 permissions validated');
|
|
509
|
-
|
|
510
|
-
} catch (error) {
|
|
511
|
-
if (error instanceof FileDiscoveryError &&
|
|
512
|
-
error.code === 'FILE_ACCESS_DENIED') {
|
|
513
|
-
console.error('\n❌ IAM Permissions Required:');
|
|
514
|
-
console.error(' - s3:ListBucket on arn:aws:s3:::my-bucket');
|
|
515
|
-
console.error(' - s3:GetObject on arn:aws:s3:::my-bucket/*');
|
|
516
|
-
console.error('\nAdd this IAM policy to your AWS user/role.');
|
|
517
|
-
}
|
|
518
|
-
throw error;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
---
|
|
524
|
-
|
|
525
|
-
## Reading Errors
|
|
526
|
-
|
|
527
|
-
### Error 1: Out of Memory (Large Files)
|
|
528
|
-
|
|
529
|
-
**Symptoms:**
|
|
530
|
-
```
|
|
531
|
-
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
**Causes:**
|
|
535
|
-
- File too large to load in memory
|
|
536
|
-
- Default Node.js heap size (512MB - 2GB)
|
|
537
|
-
- Multiple large files processed concurrently
|
|
538
|
-
|
|
539
|
-
**Solution 1: Increase Heap Size**
|
|
540
|
-
```bash
|
|
541
|
-
# Temporary fix - increase Node.js heap
|
|
542
|
-
node --max-old-space-size=4096 your-script.js # 4GB heap
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
**Solution 2: Stream Large Files (Best Practice)**
|
|
546
|
-
```typescript
|
|
547
|
-
import { S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
548
|
-
import { Readable } from 'stream';
|
|
549
|
-
|
|
550
|
-
async function processLargeFile(s3Source: S3DataSource, filePath: string) {
|
|
551
|
-
try {
|
|
552
|
-
// Option 1: Download in binary mode (Buffer)
|
|
553
|
-
const content = await s3Source.downloadFile(filePath, { encoding: 'binary' });
|
|
554
|
-
|
|
555
|
-
// Process in chunks
|
|
556
|
-
const buffer = content as Buffer;
|
|
557
|
-
const chunkSize = 1024 * 1024; // 1MB chunks
|
|
558
|
-
|
|
559
|
-
for (let offset = 0; offset < buffer.length; offset += chunkSize) {
|
|
560
|
-
const chunk = buffer.slice(offset, offset + chunkSize);
|
|
561
|
-
await processChunk(chunk);
|
|
562
|
-
|
|
563
|
-
// Allow garbage collection
|
|
564
|
-
if (global.gc) global.gc();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
} catch (error) {
|
|
568
|
-
if (error.message?.includes('heap out of memory')) {
|
|
569
|
-
logger.error('Out of memory processing large file:', {
|
|
570
|
-
file: filePath,
|
|
571
|
-
suggestion: 'Use chunked processing or increase heap size',
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
throw new Error(
|
|
575
|
-
'File too large for available memory. ' +
|
|
576
|
-
'Increase heap size with: node --max-old-space-size=4096'
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
throw error;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
async function processChunk(chunk: Buffer) {
|
|
584
|
-
// Process chunk here
|
|
585
|
-
logger.debug(`Processing chunk: ${chunk.length} bytes`);
|
|
586
|
-
}
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
### Error 2: Presigned URL Expired (S3)
|
|
590
|
-
|
|
591
|
-
**Symptoms:**
|
|
592
|
-
```
|
|
593
|
-
FileDiscoveryError: Request has expired
|
|
594
|
-
code: FILE_ACCESS_DENIED
|
|
595
|
-
statusCode: 403
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
**Cause:**
|
|
599
|
-
- Presigned URLs expire after 1 hour (SDK default)
|
|
600
|
-
- Long-running operations exceed URL lifetime
|
|
601
|
-
|
|
602
|
-
**Solution:**
|
|
603
|
-
```typescript
|
|
604
|
-
async function downloadWithUrlRefresh(s3Source: S3DataSource, filePath: string) {
|
|
605
|
-
const startTime = Date.now();
|
|
606
|
-
const URL_LIFETIME_MS = 55 * 60 * 1000; // 55 minutes (buffer before 1 hour expiry)
|
|
607
|
-
|
|
608
|
-
try {
|
|
609
|
-
const content = await s3Source.downloadFile(filePath);
|
|
610
|
-
return content;
|
|
611
|
-
|
|
612
|
-
} catch (error) {
|
|
613
|
-
const elapsed = Date.now() - startTime;
|
|
614
|
-
|
|
615
|
-
if (error instanceof FileDiscoveryError &&
|
|
616
|
-
error.code === 'FILE_ACCESS_DENIED' &&
|
|
617
|
-
error.message?.includes('expired') &&
|
|
618
|
-
elapsed > URL_LIFETIME_MS) {
|
|
619
|
-
|
|
620
|
-
logger.warn('Presigned URL expired - recreating data source');
|
|
621
|
-
|
|
622
|
-
// Recreate S3DataSource (generates new presigned URLs)
|
|
623
|
-
const newS3Source = new S3DataSource(s3Source.getConfig(), logger);
|
|
624
|
-
|
|
625
|
-
// Retry with new URL
|
|
626
|
-
return await newS3Source.downloadFile(filePath);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
---
|
|
635
|
-
|
|
636
|
-
## Parsing Errors
|
|
637
|
-
|
|
638
|
-
### Error 1: Malformed XML
|
|
639
|
-
|
|
640
|
-
**Symptoms:**
|
|
641
|
-
```
|
|
642
|
-
FileParsingError: Failed to parse XML: Unclosed tag 'customer-email'
|
|
643
|
-
fileName: "order.xml"
|
|
644
|
-
lineNumber: 5
|
|
645
|
-
code: PARSE_ERROR
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
**Example Malformed XML:**
|
|
649
|
-
```xml
|
|
650
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
651
|
-
<order>
|
|
652
|
-
<order-id>12345</order-id>
|
|
653
|
-
<customer>
|
|
654
|
-
<email>test@example.com
|
|
655
|
-
<!-- ❌ Missing closing tag for <email> and <customer> -->
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
**Solution:**
|
|
659
|
-
```typescript
|
|
660
|
-
import { XMLParserService, FileParsingError } from '@fluentcommerce/fc-connect-sdk';
|
|
661
|
-
|
|
662
|
-
async function parseXMLWithValidation(xmlContent: string, fileName: string) {
|
|
663
|
-
const parser = new XMLParserService();
|
|
664
|
-
|
|
665
|
-
try {
|
|
666
|
-
const parsed = await parser.parse(xmlContent);
|
|
667
|
-
return parsed;
|
|
668
|
-
|
|
669
|
-
} catch (error) {
|
|
670
|
-
if (error instanceof FileParsingError &&
|
|
671
|
-
error.code === 'PARSE_ERROR') {
|
|
672
|
-
logger.error('XML parsing failed:', {
|
|
673
|
-
fileName: error.fileName || fileName,
|
|
674
|
-
lineNumber: error.lineNumber,
|
|
675
|
-
message: error.message,
|
|
676
|
-
// Show first 200 chars of XML for debugging
|
|
677
|
-
xmlPreview: xmlContent.substring(0, 200),
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
throw new Error(
|
|
681
|
-
`Invalid XML in ${fileName}:\n` +
|
|
682
|
-
` Line ${error.lineNumber}: ${error.message}\n` +
|
|
683
|
-
` Fix: Ensure all XML tags are properly closed`
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
throw error;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
**Common XML Errors:**
|
|
692
|
-
| Error | Cause | Fix |
|
|
693
|
-
|-------|-------|-----|
|
|
694
|
-
| `Unclosed tag 'tagname'` | Missing `</tagname>` | Add closing tag |
|
|
695
|
-
| `Unexpected closing tag` | Extra `</tagname>` | Remove duplicate closing tag |
|
|
696
|
-
| `Invalid character` | Special chars not escaped | Use `&`, `<`, `>`, `"`, `'` |
|
|
697
|
-
| `Text not allowed here` | Text outside tags | Wrap text in tags |
|
|
698
|
-
|
|
699
|
-
### Error 2: Invalid JSON
|
|
700
|
-
|
|
701
|
-
**Symptoms:**
|
|
702
|
-
```
|
|
703
|
-
FileParsingError: Failed to parse JSON: Unexpected token a in JSON at position 45
|
|
704
|
-
fileName: "order.json"
|
|
705
|
-
code: PARSE_ERROR
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
**Example Invalid JSON:**
|
|
709
|
-
```json
|
|
710
|
-
{
|
|
711
|
-
"orderId": "12345",
|
|
712
|
-
"status": active, // ❌ Unquoted string
|
|
713
|
-
"items": [
|
|
714
|
-
{
|
|
715
|
-
"sku": "ABC-123",
|
|
716
|
-
"qty": 5, // ❌ Trailing comma
|
|
717
|
-
}
|
|
718
|
-
], // ❌ Trailing comma
|
|
719
|
-
}
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
**Solution:**
|
|
723
|
-
```typescript
|
|
724
|
-
import { JSONParserService, FileParsingError } from '@fluentcommerce/fc-connect-sdk';
|
|
725
|
-
|
|
726
|
-
async function parseJSONWithValidation(jsonContent: string, fileName: string) {
|
|
727
|
-
const parser = new JSONParserService();
|
|
728
|
-
|
|
729
|
-
try {
|
|
730
|
-
const parsed = await parser.parse(jsonContent);
|
|
731
|
-
return parsed;
|
|
732
|
-
|
|
733
|
-
} catch (error) {
|
|
734
|
-
if (error instanceof FileParsingError &&
|
|
735
|
-
error.code === 'PARSE_ERROR') {
|
|
736
|
-
|
|
737
|
-
// Extract position from error message
|
|
738
|
-
const positionMatch = error.message.match(/position (\d+)/);
|
|
739
|
-
const position = positionMatch ? parseInt(positionMatch[1]) : 0;
|
|
740
|
-
|
|
741
|
-
// Show context around error
|
|
742
|
-
const start = Math.max(0, position - 50);
|
|
743
|
-
const end = Math.min(jsonContent.length, position + 50);
|
|
744
|
-
const context = jsonContent.substring(start, end);
|
|
745
|
-
|
|
746
|
-
logger.error('JSON parsing failed:', {
|
|
747
|
-
fileName: fileName,
|
|
748
|
-
position: position,
|
|
749
|
-
context: context,
|
|
750
|
-
message: error.message,
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
throw new Error(
|
|
754
|
-
`Invalid JSON in ${fileName}:\n` +
|
|
755
|
-
` ${error.message}\n` +
|
|
756
|
-
` Context: ...${context}...`
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
throw error;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
**Common JSON Errors:**
|
|
765
|
-
| Error | Cause | Fix |
|
|
766
|
-
|-------|-------|-----|
|
|
767
|
-
| `Unexpected token` | Unquoted string, trailing comma | Quote strings, remove trailing commas |
|
|
768
|
-
| `Unexpected end of JSON` | Missing closing `}` or `]` | Add closing bracket |
|
|
769
|
-
| `Expected property name` | Missing quotes on key | Quote object keys: `"key": value` |
|
|
770
|
-
|
|
771
|
-
### Error 3: CSV Column Mismatch
|
|
772
|
-
|
|
773
|
-
**Symptoms:**
|
|
774
|
-
```
|
|
775
|
-
FileParsingError: CSV column count mismatch
|
|
776
|
-
fileName: "inventory.csv"
|
|
777
|
-
code: INVALID_CSV_FORMAT
|
|
778
|
-
expectedColumns: 5
|
|
779
|
-
actualColumns: 4
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
**Example Invalid CSV:**
|
|
783
|
-
```csv
|
|
784
|
-
sku,location,quantity,date,status
|
|
785
|
-
SKU-001,LOC-01,100,2024-01-01,ACTIVE
|
|
786
|
-
SKU-002,LOC-02,50,2024-01-02 ← Missing 'status' column
|
|
787
|
-
SKU-003,LOC-03,75,2024-01-03,ACTIVE
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
**Solution:**
|
|
791
|
-
```typescript
|
|
792
|
-
import { CSVParserService, FileParsingError } from '@fluentcommerce/fc-connect-sdk';
|
|
793
|
-
|
|
794
|
-
async function parseCSVWithValidation(
|
|
795
|
-
csvContent: string,
|
|
796
|
-
fileName: string,
|
|
797
|
-
expectedColumns?: string[]
|
|
798
|
-
) {
|
|
799
|
-
const parser = new CSVParserService();
|
|
800
|
-
|
|
801
|
-
try {
|
|
802
|
-
const parsed = await parser.parse(csvContent);
|
|
803
|
-
|
|
804
|
-
// Optional: Validate column names
|
|
805
|
-
if (expectedColumns && parsed.length > 0) {
|
|
806
|
-
const actualColumns = Object.keys(parsed[0]);
|
|
807
|
-
const missingColumns = expectedColumns.filter(col => !actualColumns.includes(col));
|
|
808
|
-
|
|
809
|
-
if (missingColumns.length > 0) {
|
|
810
|
-
throw new FileParsingError(
|
|
811
|
-
`CSV missing required columns: ${missingColumns.join(', ')}`,
|
|
812
|
-
'INVALID_CSV_FORMAT',
|
|
813
|
-
{ fileName, missingColumns, actualColumns }
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
return parsed;
|
|
819
|
-
|
|
820
|
-
} catch (error) {
|
|
821
|
-
if (error instanceof FileParsingError &&
|
|
822
|
-
error.code === 'INVALID_CSV_FORMAT') {
|
|
823
|
-
logger.error('CSV parsing failed:', {
|
|
824
|
-
fileName: fileName,
|
|
825
|
-
message: error.message,
|
|
826
|
-
context: error.context,
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
throw new Error(
|
|
830
|
-
`Invalid CSV in ${fileName}:\n` +
|
|
831
|
-
` ${error.message}\n` +
|
|
832
|
-
` Fix: Ensure all rows have the same number of columns`
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
throw error;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// Usage
|
|
840
|
-
const expectedColumns = ['sku', 'location', 'quantity', 'date', 'status'];
|
|
841
|
-
const parsed = await parseCSVWithValidation(csvContent, 'inventory.csv', expectedColumns);
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
### Error 4: Encoding Issues (Non-UTF-8)
|
|
845
|
-
|
|
846
|
-
**Symptoms:**
|
|
847
|
-
```
|
|
848
|
-
FileParsingError: Invalid UTF-8 sequence
|
|
849
|
-
code: ENCODING_ERROR
|
|
850
|
-
```
|
|
851
|
-
|
|
852
|
-
**Cause:**
|
|
853
|
-
- File encoded in ISO-8859-1, Windows-1252, or other non-UTF-8 encoding
|
|
854
|
-
- Special characters corrupted
|
|
855
|
-
|
|
856
|
-
**Solution:**
|
|
857
|
-
```typescript
|
|
858
|
-
import { XMLParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
859
|
-
import iconv from 'iconv-lite';
|
|
860
|
-
|
|
861
|
-
async function parseWithEncodingDetection(
|
|
862
|
-
fileContent: Buffer | string,
|
|
863
|
-
fileName: string
|
|
864
|
-
) {
|
|
865
|
-
const parser = new XMLParserService();
|
|
866
|
-
|
|
867
|
-
try {
|
|
868
|
-
// Try UTF-8 first (SDK default)
|
|
869
|
-
const content = typeof fileContent === 'string' ? fileContent : fileContent.toString('utf-8');
|
|
870
|
-
return await parser.parse(content);
|
|
871
|
-
|
|
872
|
-
} catch (error) {
|
|
873
|
-
if (error instanceof FileParsingError &&
|
|
874
|
-
error.code === 'ENCODING_ERROR') {
|
|
875
|
-
|
|
876
|
-
logger.warn('UTF-8 parsing failed - trying alternate encodings');
|
|
877
|
-
|
|
878
|
-
// Try common alternate encodings
|
|
879
|
-
const encodings = ['iso-8859-1', 'windows-1252', 'utf-16le'];
|
|
880
|
-
|
|
881
|
-
for (const encoding of encodings) {
|
|
882
|
-
try {
|
|
883
|
-
const buffer = typeof fileContent === 'string' ?
|
|
884
|
-
Buffer.from(fileContent, 'binary') : fileContent;
|
|
885
|
-
|
|
886
|
-
const converted = iconv.decode(buffer, encoding);
|
|
887
|
-
const parsed = await parser.parse(converted);
|
|
888
|
-
|
|
889
|
-
logger.info(`✅ Successfully parsed with ${encoding} encoding`);
|
|
890
|
-
return parsed;
|
|
891
|
-
|
|
892
|
-
} catch (e) {
|
|
893
|
-
continue; // Try next encoding
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// All encodings failed
|
|
898
|
-
throw new Error(
|
|
899
|
-
`Could not parse ${fileName} with any encoding. ` +
|
|
900
|
-
`Tried: UTF-8, ${encodings.join(', ')}`
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
throw error;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
**Note:** The SDK automatically handles UTF-8 BOM (Byte Order Mark) removal, so BOM presence is not an error.
|
|
909
|
-
|
|
910
|
-
---
|
|
911
|
-
|
|
912
|
-
## Complete Examples
|
|
913
|
-
|
|
914
|
-
### Example 1: Production-Ready S3 CSV Pipeline
|
|
915
|
-
|
|
916
|
-
```typescript
|
|
917
|
-
import {
|
|
918
|
-
S3DataSource,
|
|
919
|
-
CSVParserService,
|
|
920
|
-
FileDiscoveryError,
|
|
921
|
-
FileParsingError,
|
|
922
|
-
IngestionError,
|
|
923
|
-
IngestionErrorCode,
|
|
924
|
-
createConsoleLogger,
|
|
925
|
-
toStructuredLogger
|
|
926
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
927
|
-
|
|
928
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
929
|
-
logLevel: 'info'
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
async function processS3InventoryFiles() {
|
|
933
|
-
let s3Source: S3DataSource | null = null;
|
|
934
|
-
|
|
935
|
-
try {
|
|
936
|
-
// ────────────────────────────────────────────────────────
|
|
937
|
-
// STAGE 1: Connection
|
|
938
|
-
// ────────────────────────────────────────────────────────
|
|
939
|
-
logger.info('Creating S3 data source...');
|
|
940
|
-
|
|
941
|
-
s3Source = new S3DataSource(
|
|
942
|
-
{
|
|
943
|
-
type: 'S3_CSV',
|
|
944
|
-
s3Config: {
|
|
945
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
946
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
947
|
-
region: process.env.AWS_REGION!,
|
|
948
|
-
bucket: process.env.S3_BUCKET!,
|
|
949
|
-
},
|
|
950
|
-
},
|
|
951
|
-
logger
|
|
952
|
-
);
|
|
953
|
-
|
|
954
|
-
// Validate connection
|
|
955
|
-
const isValid = await s3Source.validateConnection();
|
|
956
|
-
if (!isValid) {
|
|
957
|
-
throw new Error('S3 connection validation failed');
|
|
958
|
-
}
|
|
959
|
-
logger.info('✅ S3 connection validated');
|
|
960
|
-
|
|
961
|
-
// ────────────────────────────────────────────────────────
|
|
962
|
-
// STAGE 2: Discovery
|
|
963
|
-
// ────────────────────────────────────────────────────────
|
|
964
|
-
logger.info('Discovering files...');
|
|
965
|
-
|
|
966
|
-
const files = await s3Source.listFiles({
|
|
967
|
-
prefix: 'inventory/',
|
|
968
|
-
pattern: /\.csv$/i,
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
if (files.length === 0) {
|
|
972
|
-
logger.warn('No CSV files found in inventory/ folder');
|
|
973
|
-
return;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
logger.info(`Found ${files.length} CSV files`);
|
|
977
|
-
|
|
978
|
-
// ────────────────────────────────────────────────────────
|
|
979
|
-
// STAGE 3: Reading & Parsing
|
|
980
|
-
// ────────────────────────────────────────────────────────
|
|
981
|
-
const parser = new CSVParserService();
|
|
982
|
-
const results = [];
|
|
983
|
-
|
|
984
|
-
for (const file of files) {
|
|
985
|
-
try {
|
|
986
|
-
logger.info(`Processing ${file.name}...`);
|
|
987
|
-
|
|
988
|
-
// Download file with retry
|
|
989
|
-
const content = await downloadWithRetry(s3Source, file.path, 3);
|
|
990
|
-
|
|
991
|
-
// Parse CSV
|
|
992
|
-
const parsed = await parser.parse(content as string);
|
|
993
|
-
|
|
994
|
-
logger.info(`✅ Parsed ${parsed.length} records from ${file.name}`);
|
|
995
|
-
results.push({ file: file.name, records: parsed });
|
|
996
|
-
|
|
997
|
-
} catch (error) {
|
|
998
|
-
if (error instanceof FileParsingError) {
|
|
999
|
-
logger.error(`❌ Failed to parse ${file.name}:`, {
|
|
1000
|
-
code: error.code,
|
|
1001
|
-
message: error.message,
|
|
1002
|
-
lineNumber: error.lineNumber,
|
|
1003
|
-
});
|
|
1004
|
-
// Continue with next file (don't fail entire batch)
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
throw error; // Re-throw other errors
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
logger.info(`✅ Successfully processed ${results.length}/${files.length} files`);
|
|
1012
|
-
return results;
|
|
1013
|
-
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
// ────────────────────────────────────────────────────────
|
|
1016
|
-
// STAGE 4: Error Handling
|
|
1017
|
-
// ────────────────────────────────────────────────────────
|
|
1018
|
-
if (error instanceof FileDiscoveryError) {
|
|
1019
|
-
logger.error('File discovery error:', {
|
|
1020
|
-
code: error.code,
|
|
1021
|
-
message: error.message,
|
|
1022
|
-
path: error.path,
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
if (error.code === 'FILE_ACCESS_DENIED') {
|
|
1026
|
-
console.error('\n❌ S3 Access Denied:');
|
|
1027
|
-
console.error(' 1. Check AWS credentials are correct');
|
|
1028
|
-
console.error(' 2. Verify IAM policy allows s3:ListBucket and s3:GetObject');
|
|
1029
|
-
console.error(' 3. Confirm bucket exists and region is correct');
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
} else if (error instanceof IngestionError) {
|
|
1033
|
-
logger.error('Ingestion error:', error.toJSON());
|
|
1034
|
-
|
|
1035
|
-
if (error.isRetryable()) {
|
|
1036
|
-
console.error('\n⚠️ Retryable error - consider running again');
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
} else {
|
|
1040
|
-
logger.error('Unexpected error:', error);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
throw error;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
// Helper: Download with retry (from earlier example)
|
|
1048
|
-
async function downloadWithRetry(
|
|
1049
|
-
s3Source: S3DataSource,
|
|
1050
|
-
filePath: string,
|
|
1051
|
-
maxRetries = 3
|
|
1052
|
-
) {
|
|
1053
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1054
|
-
try {
|
|
1055
|
-
return await s3Source.downloadFile(filePath);
|
|
1056
|
-
} catch (error) {
|
|
1057
|
-
if (error instanceof IngestionError &&
|
|
1058
|
-
error.code === IngestionErrorCode.NETWORK_ERROR &&
|
|
1059
|
-
error.isRetryable() &&
|
|
1060
|
-
attempt < maxRetries) {
|
|
1061
|
-
const delay = Math.pow(2, attempt) * 1000;
|
|
1062
|
-
logger.warn(`Retry attempt ${attempt}/${maxRetries} in ${delay}ms`);
|
|
1063
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1064
|
-
continue;
|
|
1065
|
-
}
|
|
1066
|
-
throw error;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
// Run the pipeline
|
|
1072
|
-
processS3InventoryFiles()
|
|
1073
|
-
.then(() => logger.info('Pipeline completed successfully'))
|
|
1074
|
-
.catch(error => {
|
|
1075
|
-
logger.error('Pipeline failed:', error);
|
|
1076
|
-
process.exit(1);
|
|
1077
|
-
});
|
|
1078
|
-
```
|
|
1079
|
-
|
|
1080
|
-
### Example 2: Production-Ready SFTP XML Pipeline
|
|
1081
|
-
|
|
1082
|
-
```typescript
|
|
1083
|
-
import {
|
|
1084
|
-
SftpDataSource,
|
|
1085
|
-
XMLParserService,
|
|
1086
|
-
FileDiscoveryError,
|
|
1087
|
-
FileParsingError,
|
|
1088
|
-
createConsoleLogger,
|
|
1089
|
-
toStructuredLogger
|
|
1090
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
1091
|
-
|
|
1092
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
1093
|
-
logLevel: 'info'
|
|
1094
|
-
});
|
|
1095
|
-
|
|
1096
|
-
async function processSftpOrderFiles() {
|
|
1097
|
-
let sftpSource: SftpDataSource | null = null;
|
|
1098
|
-
|
|
1099
|
-
try {
|
|
1100
|
-
// ────────────────────────────────────────────────────────
|
|
1101
|
-
// STAGE 1: Connection
|
|
1102
|
-
// ────────────────────────────────────────────────────────
|
|
1103
|
-
logger.info('Connecting to SFTP...');
|
|
1104
|
-
|
|
1105
|
-
sftpSource = new SftpDataSource(
|
|
1106
|
-
{
|
|
1107
|
-
type: 'SFTP_XML',
|
|
1108
|
-
connectionId: 'vendor-sftp',
|
|
1109
|
-
settings: {
|
|
1110
|
-
host: process.env.SFTP_HOST!,
|
|
1111
|
-
port: parseInt(process.env.SFTP_PORT || '22'),
|
|
1112
|
-
username: process.env.SFTP_USERNAME!,
|
|
1113
|
-
privateKey: process.env.SFTP_PRIVATE_KEY!,
|
|
1114
|
-
passphrase: process.env.SFTP_PASSPHRASE,
|
|
1115
|
-
remotePath: '/orders',
|
|
1116
|
-
},
|
|
1117
|
-
},
|
|
1118
|
-
logger
|
|
1119
|
-
);
|
|
1120
|
-
|
|
1121
|
-
// Validate connection
|
|
1122
|
-
const isValid = await sftpSource.validateConnection();
|
|
1123
|
-
if (!isValid) {
|
|
1124
|
-
throw new Error('SFTP connection validation failed');
|
|
1125
|
-
}
|
|
1126
|
-
logger.info('✅ SFTP connection validated');
|
|
1127
|
-
|
|
1128
|
-
// ────────────────────────────────────────────────────────
|
|
1129
|
-
// STAGE 2: Discovery
|
|
1130
|
-
// ────────────────────────────────────────────────────────
|
|
1131
|
-
logger.info('Listing XML files...');
|
|
1132
|
-
|
|
1133
|
-
const files = await sftpSource.listFiles({
|
|
1134
|
-
pattern: /\.xml$/i,
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
if (files.length === 0) {
|
|
1138
|
-
logger.info('No XML files found');
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
logger.info(`Found ${files.length} XML files`);
|
|
1143
|
-
|
|
1144
|
-
// ────────────────────────────────────────────────────────
|
|
1145
|
-
// STAGE 3: Reading & Parsing
|
|
1146
|
-
// ────────────────────────────────────────────────────────
|
|
1147
|
-
const parser = new XMLParserService();
|
|
1148
|
-
const results = [];
|
|
1149
|
-
|
|
1150
|
-
for (const file of files) {
|
|
1151
|
-
try {
|
|
1152
|
-
logger.info(`Processing ${file.name}...`);
|
|
1153
|
-
|
|
1154
|
-
// Download file
|
|
1155
|
-
const content = await sftpSource.downloadFile(file.path);
|
|
1156
|
-
|
|
1157
|
-
// Parse XML
|
|
1158
|
-
const parsed = await parser.parse(content as string);
|
|
1159
|
-
|
|
1160
|
-
logger.info(`✅ Parsed XML from ${file.name}`);
|
|
1161
|
-
results.push({ file: file.name, data: parsed });
|
|
1162
|
-
|
|
1163
|
-
// Archive processed file
|
|
1164
|
-
await sftpSource.archiveFile(file.path, `/archive/${file.name}`);
|
|
1165
|
-
logger.info(`Archived ${file.name}`);
|
|
1166
|
-
|
|
1167
|
-
} catch (error) {
|
|
1168
|
-
if (error instanceof FileParsingError) {
|
|
1169
|
-
logger.error(`❌ XML parsing failed for ${file.name}:`, {
|
|
1170
|
-
code: error.code,
|
|
1171
|
-
message: error.message,
|
|
1172
|
-
lineNumber: error.lineNumber,
|
|
1173
|
-
});
|
|
1174
|
-
|
|
1175
|
-
// Move to error folder
|
|
1176
|
-
await sftpSource.archiveFile(file.path, `/errors/${file.name}`);
|
|
1177
|
-
continue; // Process next file
|
|
1178
|
-
}
|
|
1179
|
-
throw error;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
logger.info(`✅ Processed ${results.length}/${files.length} files`);
|
|
1184
|
-
return results;
|
|
1185
|
-
|
|
1186
|
-
} catch (error) {
|
|
1187
|
-
if (error instanceof FileDiscoveryError) {
|
|
1188
|
-
logger.error('SFTP error:', {
|
|
1189
|
-
code: error.code,
|
|
1190
|
-
message: error.message,
|
|
1191
|
-
});
|
|
1192
|
-
|
|
1193
|
-
if (error.code === 'FILE_ACCESS_DENIED') {
|
|
1194
|
-
console.error('\n❌ SFTP Authentication Failed:');
|
|
1195
|
-
console.error(' 1. Verify SSH private key is correct');
|
|
1196
|
-
console.error(' 2. Check username is correct');
|
|
1197
|
-
console.error(' 3. Confirm passphrase (if key is encrypted)');
|
|
1198
|
-
console.error(' 4. Test connection: ssh username@host');
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
if (error.message?.includes('timeout')) {
|
|
1202
|
-
console.error('\n❌ SFTP Connection Timeout:');
|
|
1203
|
-
console.error(' 1. Check SFTP host is reachable');
|
|
1204
|
-
console.error(' 2. Verify firewall allows outbound port 22');
|
|
1205
|
-
console.error(' 3. Test connectivity: telnet host 22');
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
throw error;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
// Run the pipeline
|
|
1214
|
-
processSftpOrderFiles()
|
|
1215
|
-
.then(() => logger.info('SFTP pipeline completed'))
|
|
1216
|
-
.catch(error => {
|
|
1217
|
-
logger.error('SFTP pipeline failed:', error);
|
|
1218
|
-
process.exit(1);
|
|
1219
|
-
});
|
|
1220
|
-
```
|
|
1221
|
-
|
|
1222
|
-
---
|
|
1223
|
-
|
|
1224
|
-
## Retry Strategies
|
|
1225
|
-
|
|
1226
|
-
### When to Retry
|
|
1227
|
-
|
|
1228
|
-
```typescript
|
|
1229
|
-
function shouldRetry(error: unknown): boolean {
|
|
1230
|
-
// 1. Network errors - ALWAYS retry
|
|
1231
|
-
if (error instanceof IngestionError &&
|
|
1232
|
-
error.code === IngestionErrorCode.NETWORK_ERROR) {
|
|
1233
|
-
return true;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// 2. Timeout errors - ALWAYS retry
|
|
1237
|
-
if (error instanceof IngestionError &&
|
|
1238
|
-
error.code === IngestionErrorCode.TIMEOUT_ERROR) {
|
|
1239
|
-
return true;
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
// 3. File discovery errors - SOMETIMES retry
|
|
1243
|
-
if (error instanceof FileDiscoveryError) {
|
|
1244
|
-
// Retry if transient (network, timeout)
|
|
1245
|
-
if (error.message?.includes('timeout') ||
|
|
1246
|
-
error.message?.includes('ECONNREFUSED')) {
|
|
1247
|
-
return true;
|
|
1248
|
-
}
|
|
1249
|
-
// Don't retry permanent errors (access denied, not found)
|
|
1250
|
-
return false;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// 4. Parsing errors - NEVER retry
|
|
1254
|
-
if (error instanceof FileParsingError) {
|
|
1255
|
-
return false;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// 5. Unknown errors - NEVER retry
|
|
1259
|
-
return false;
|
|
1260
|
-
}
|
|
1261
|
-
```
|
|
1262
|
-
|
|
1263
|
-
### Exponential Backoff with Jitter
|
|
1264
|
-
|
|
1265
|
-
```typescript
|
|
1266
|
-
async function retryWithBackoff<T>(
|
|
1267
|
-
operation: () => Promise<T>,
|
|
1268
|
-
maxRetries = 3,
|
|
1269
|
-
baseDelayMs = 1000
|
|
1270
|
-
): Promise<T> {
|
|
1271
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1272
|
-
try {
|
|
1273
|
-
return await operation(); // ✅ Success
|
|
1274
|
-
|
|
1275
|
-
} catch (error) {
|
|
1276
|
-
const isLastAttempt = attempt === maxRetries;
|
|
1277
|
-
const shouldRetryError = shouldRetry(error);
|
|
1278
|
-
|
|
1279
|
-
if (!shouldRetryError || isLastAttempt) {
|
|
1280
|
-
throw error; // Give up
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// Calculate delay with exponential backoff + jitter
|
|
1284
|
-
const exponentialDelay = Math.pow(2, attempt) * baseDelayMs;
|
|
1285
|
-
const jitter = Math.random() * 1000; // Random 0-1000ms
|
|
1286
|
-
const delay = exponentialDelay + jitter;
|
|
1287
|
-
|
|
1288
|
-
logger.warn(`Attempt ${attempt}/${maxRetries} failed - retrying in ${Math.round(delay)}ms`, {
|
|
1289
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1290
|
-
});
|
|
1291
|
-
|
|
1292
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
throw new Error('Unreachable'); // TypeScript exhaustiveness check
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
// Usage
|
|
1300
|
-
const content = await retryWithBackoff(
|
|
1301
|
-
() => s3Source.downloadFile('inventory.csv'),
|
|
1302
|
-
3, // max 3 retries
|
|
1303
|
-
1000 // start with 1 second
|
|
1304
|
-
);
|
|
1305
|
-
```
|
|
1306
|
-
|
|
1307
|
-
---
|
|
1308
|
-
|
|
1309
|
-
## Troubleshooting Flowchart
|
|
1310
|
-
|
|
1311
|
-
```
|
|
1312
|
-
┌─────────────────────────────────────────────┐
|
|
1313
|
-
│ Error occurred during file operation │
|
|
1314
|
-
└────────────────┬────────────────────────────┘
|
|
1315
|
-
│
|
|
1316
|
-
▼
|
|
1317
|
-
┌───────────────┐
|
|
1318
|
-
│ Error type? │
|
|
1319
|
-
└───┬───────────┘
|
|
1320
|
-
│
|
|
1321
|
-
┌───────┼───────┬────────┬──────────┐
|
|
1322
|
-
│ │ │ │ │
|
|
1323
|
-
▼ ▼ ▼ ▼ ▼
|
|
1324
|
-
┌─────────────────────────────────────────────┐
|
|
1325
|
-
│ FileDiscoveryError │
|
|
1326
|
-
├─────────────────────────────────────────────┤
|
|
1327
|
-
│ Code: FILE_ACCESS_DENIED │
|
|
1328
|
-
│ ├─ Check credentials │
|
|
1329
|
-
│ ├─ Verify IAM/SSH permissions │
|
|
1330
|
-
│ └─ Confirm bucket/host exists │
|
|
1331
|
-
│ │
|
|
1332
|
-
│ Code: FILE_NOT_FOUND │
|
|
1333
|
-
│ ├─ Verify file path is correct │
|
|
1334
|
-
│ ├─ Check prefix/pattern │
|
|
1335
|
-
│ └─ File may have been moved/deleted │
|
|
1336
|
-
│ │
|
|
1337
|
-
│ Code: FILE_DISCOVERY_FAILED │
|
|
1338
|
-
│ ├─ Check network connectivity │
|
|
1339
|
-
│ ├─ Test with ping/telnet │
|
|
1340
|
-
│ └─ Retry with exponential backoff │
|
|
1341
|
-
└─────────────────────────────────────────────┘
|
|
1342
|
-
|
|
1343
|
-
┌─────────────────────────────────────────────┐
|
|
1344
|
-
│ IngestionError (NETWORK_ERROR) │
|
|
1345
|
-
├─────────────────────────────────────────────┤
|
|
1346
|
-
│ Symptoms: Timeout, connection refused │
|
|
1347
|
-
│ ├─ ✅ RETRYABLE: Yes │
|
|
1348
|
-
│ ├─ Check network connectivity │
|
|
1349
|
-
│ ├─ Verify endpoint is reachable │
|
|
1350
|
-
│ └─ Retry with exponential backoff │
|
|
1351
|
-
└─────────────────────────────────────────────┘
|
|
1352
|
-
|
|
1353
|
-
┌─────────────────────────────────────────────┐
|
|
1354
|
-
│ FileParsingError │
|
|
1355
|
-
├─────────────────────────────────────────────┤
|
|
1356
|
-
│ Code: PARSE_ERROR │
|
|
1357
|
-
│ ├─ ❌ NOT RETRYABLE │
|
|
1358
|
-
│ ├─ Check file format (XML/JSON/CSV valid?) │
|
|
1359
|
-
│ ├─ Look at lineNumber in error │
|
|
1360
|
-
│ └─ Fix source data │
|
|
1361
|
-
│ │
|
|
1362
|
-
│ Code: ENCODING_ERROR │
|
|
1363
|
-
│ ├─ File may not be UTF-8 │
|
|
1364
|
-
│ ├─ Try alternate encodings │
|
|
1365
|
-
│ └─ Convert file to UTF-8 │
|
|
1366
|
-
└─────────────────────────────────────────────┘
|
|
1367
|
-
|
|
1368
|
-
┌─────────────────────────────────────────────┐
|
|
1369
|
-
│ Memory Error (heap out of memory) │
|
|
1370
|
-
├─────────────────────────────────────────────┤
|
|
1371
|
-
│ Symptoms: "JavaScript heap out of memory" │
|
|
1372
|
-
│ ├─ File too large for available memory │
|
|
1373
|
-
│ ├─ Solution 1: Increase heap size │
|
|
1374
|
-
│ │ node --max-old-space-size=4096 │
|
|
1375
|
-
│ ├─ Solution 2: Use chunked processing │
|
|
1376
|
-
│ │ Process file in 1MB chunks │
|
|
1377
|
-
│ └─ Solution 3: Stream instead of buffer │
|
|
1378
|
-
└─────────────────────────────────────────────┘
|
|
1379
|
-
```
|
|
1380
|
-
|
|
1381
|
-
---
|
|
1382
|
-
|
|
1383
|
-
## Production Checklist
|
|
1384
|
-
|
|
1385
|
-
### Pre-Deployment
|
|
1386
|
-
|
|
1387
|
-
**Connection Setup:**
|
|
1388
|
-
- [ ] S3/SFTP credentials stored in environment variables (not hardcoded)
|
|
1389
|
-
- [ ] `validateConnection()` called before processing files
|
|
1390
|
-
- [ ] IAM policies grant minimum required permissions (least privilege)
|
|
1391
|
-
- [ ] SFTP uses SSH keys (not passwords)
|
|
1392
|
-
- [ ] Connection timeout configured appropriately
|
|
1393
|
-
|
|
1394
|
-
**Error Handling:**
|
|
1395
|
-
- [ ] All file operations wrapped in try-catch blocks
|
|
1396
|
-
- [ ] Errors logged with structured data (`error.toJSON()`)
|
|
1397
|
-
- [ ] Retry logic implemented for transient errors
|
|
1398
|
-
- [ ] Exponential backoff with jitter configured
|
|
1399
|
-
- [ ] Max retry limit set (typically 3)
|
|
1400
|
-
- [ ] Circuit breaker pattern for repeated failures
|
|
1401
|
-
|
|
1402
|
-
**File Processing:**
|
|
1403
|
-
- [ ] File existence checked before download
|
|
1404
|
-
- [ ] Large files processed in chunks (if > 100MB)
|
|
1405
|
-
- [ ] Memory limits configured (`--max-old-space-size`)
|
|
1406
|
-
- [ ] Parsed data validated before use
|
|
1407
|
-
- [ ] Failed files moved to error folder
|
|
1408
|
-
|
|
1409
|
-
**Monitoring:**
|
|
1410
|
-
- [ ] Structured logging enabled
|
|
1411
|
-
- [ ] Error rates tracked and alerted
|
|
1412
|
-
- [ ] File processing metrics collected (count, size, duration)
|
|
1413
|
-
- [ ] Presigned URL expiry monitored (S3)
|
|
1414
|
-
|
|
1415
|
-
### Post-Deployment
|
|
1416
|
-
|
|
1417
|
-
**Validation:**
|
|
1418
|
-
- [ ] Successful file processing confirmed
|
|
1419
|
-
- [ ] No duplicate processing detected
|
|
1420
|
-
- [ ] Error handling working as expected
|
|
1421
|
-
- [ ] Retry logic functioning correctly
|
|
1422
|
-
- [ ] Memory usage stable
|
|
1423
|
-
|
|
1424
|
-
**Monitoring:**
|
|
1425
|
-
- [ ] Logs being collected and searchable
|
|
1426
|
-
- [ ] Alerts triggering appropriately
|
|
1427
|
-
- [ ] Metrics dashboards created
|
|
1428
|
-
- [ ] On-call runbooks updated
|
|
1429
|
-
|
|
1430
|
-
---
|
|
1431
|
-
|
|
1432
|
-
## Quick Reference
|
|
1433
|
-
|
|
1434
|
-
### Error Type Decision Tree
|
|
1435
|
-
|
|
1436
|
-
```
|
|
1437
|
-
Does file operation fail?
|
|
1438
|
-
│
|
|
1439
|
-
├─ Connection/authentication fails
|
|
1440
|
-
│ → FileDiscoveryError (FILE_ACCESS_DENIED)
|
|
1441
|
-
│ → Check credentials, IAM policies, SSH keys
|
|
1442
|
-
│
|
|
1443
|
-
├─ File not found
|
|
1444
|
-
│ → FileDiscoveryError (FILE_NOT_FOUND)
|
|
1445
|
-
│ → Verify path, check file exists
|
|
1446
|
-
│
|
|
1447
|
-
├─ Network timeout
|
|
1448
|
-
│ → IngestionError (NETWORK_ERROR)
|
|
1449
|
-
│ → RETRY with exponential backoff
|
|
1450
|
-
│
|
|
1451
|
-
├─ File downloaded but parsing fails
|
|
1452
|
-
│ → FileParsingError (PARSE_ERROR)
|
|
1453
|
-
│ → DON'T RETRY - fix source data
|
|
1454
|
-
│
|
|
1455
|
-
└─ Out of memory
|
|
1456
|
-
→ Memory Error
|
|
1457
|
-
→ Increase heap or use chunked processing
|
|
1458
|
-
```
|
|
1459
|
-
|
|
1460
|
-
### Common Error Codes
|
|
1461
|
-
|
|
1462
|
-
| Error Code | Retryable? | Action |
|
|
1463
|
-
|------------|------------|--------|
|
|
1464
|
-
| `FILE_ACCESS_DENIED` | ❌ No | Fix credentials/permissions |
|
|
1465
|
-
| `FILE_NOT_FOUND` | ❌ No | Verify file path |
|
|
1466
|
-
| `FILE_DISCOVERY_FAILED` | ✅ Yes | Retry with backoff |
|
|
1467
|
-
| `NETWORK_ERROR` | ✅ Yes | Retry with backoff |
|
|
1468
|
-
| `TIMEOUT_ERROR` | ✅ Yes | Retry with backoff |
|
|
1469
|
-
| `PARSE_ERROR` | ❌ No | Fix source data |
|
|
1470
|
-
| `ENCODING_ERROR` | ❌ No | Convert to UTF-8 |
|
|
1471
|
-
|
|
1472
|
-
---
|
|
1473
|
-
|
|
1474
|
-
## Related Documentation
|
|
1475
|
-
|
|
1476
|
-
- [Error Handling Guide](../../03-PATTERN-GUIDES/error-handling/error-handling-readme.md) - Core error handling patterns
|
|
1477
|
-
- [Data Sources Guide](./data-sources-readme.md) - S3/SFTP configuration and usage
|
|
1478
|
-
- [Parsers Guide](../parsers/) - CSV/XML/JSON/Parquet parsing details
|
|
1479
|
-
- [Troubleshooting Quick Reference](../../00-START-HERE/troubleshooting-quick-reference.md) - General debugging
|
|
1480
|
-
|
|
1481
|
-
---
|
|
1482
|
-
|
|
1483
|
-
**Need Help?**
|
|
1484
|
-
- 📖 Review [Complete Examples](#complete-examples)
|
|
1485
|
-
- 🔍 Check [Troubleshooting Flowchart](#troubleshooting-flowchart)
|
|
1486
|
-
- 📋 Use [Production Checklist](#production-checklist)
|
|
1487
|
-
- 🐛 Report issues at [GitHub](https://github.com/fluentcommerce/fc-connect-sdk/issues)
|
|
1
|
+
# File Operations Error Handling Guide
|
|
2
|
+
|
|
3
|
+
> **Complete guide to handling errors when reading and parsing files from S3 and SFTP**
|
|
4
|
+
> Production-ready patterns for connection errors, file discovery, reading, and parsing failures
|
|
5
|
+
|
|
6
|
+
## Table of Contents
|
|
7
|
+
|
|
8
|
+
- [Overview](#overview)
|
|
9
|
+
- [Error Flow Diagram](#error-flow-diagram)
|
|
10
|
+
- [Connection Errors](#connection-errors)
|
|
11
|
+
- [Discovery Errors](#discovery-errors)
|
|
12
|
+
- [Reading Errors](#reading-errors)
|
|
13
|
+
- [Parsing Errors](#parsing-errors)
|
|
14
|
+
- [Complete Examples](#complete-examples)
|
|
15
|
+
- [Retry Strategies](#retry-strategies)
|
|
16
|
+
- [Troubleshooting Flowchart](#troubleshooting-flowchart)
|
|
17
|
+
- [Production Checklist](#production-checklist)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
When working with S3 or SFTP data sources, errors can occur at multiple stages:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────┐
|
|
27
|
+
│ 1. Connection │ ← Credentials, network, timeouts
|
|
28
|
+
├─────────────────┤
|
|
29
|
+
│ 2. Discovery │ ← File listing, permissions, paths
|
|
30
|
+
├─────────────────┤
|
|
31
|
+
│ 3. Reading │ ← Download, memory, streaming
|
|
32
|
+
├─────────────────┤
|
|
33
|
+
│ 4. Parsing │ ← CSV/XML/JSON/Parquet format errors
|
|
34
|
+
└─────────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This guide covers **all four stages** with production-ready error handling patterns.
|
|
38
|
+
|
|
39
|
+
### Key Error Types
|
|
40
|
+
|
|
41
|
+
| Error Class | Stage | Retryable? | Common Causes |
|
|
42
|
+
|-------------|-------|------------|---------------|
|
|
43
|
+
| `FileDiscoveryError` | Connection/Discovery | ✅ Usually | Invalid credentials, network timeout, permissions |
|
|
44
|
+
| `FileParsingError` | Parsing | ❌ No | Malformed XML/CSV/JSON, encoding issues |
|
|
45
|
+
| `IngestionError` (NETWORK_ERROR) | Connection/Reading | ✅ Yes | Network timeout, API unreachable |
|
|
46
|
+
| Memory errors | Reading | ⚠️ Sometimes | File too large, insufficient heap |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Error Flow Diagram
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
54
|
+
│ START: Read and Parse File from S3/SFTP │
|
|
55
|
+
└────────────────────┬─────────────────────────────────────────┘
|
|
56
|
+
│
|
|
57
|
+
▼
|
|
58
|
+
┌──────────────────────┐
|
|
59
|
+
│ Create Data Source │
|
|
60
|
+
│ (S3/SFTP) │
|
|
61
|
+
└──────┬───────────────┘
|
|
62
|
+
│
|
|
63
|
+
▼
|
|
64
|
+
┌──────────────────────┐
|
|
65
|
+
│ Validate Connection │
|
|
66
|
+
└──────┬───────────────┘
|
|
67
|
+
│
|
|
68
|
+
├─────────── ❌ FileDiscoveryError
|
|
69
|
+
│ → Invalid credentials
|
|
70
|
+
│ → Network timeout
|
|
71
|
+
│ → Permissions denied
|
|
72
|
+
│ → RETRY? Yes (if transient)
|
|
73
|
+
│
|
|
74
|
+
▼
|
|
75
|
+
┌──────────────────────┐
|
|
76
|
+
│ List Files │
|
|
77
|
+
│ (discovery) │
|
|
78
|
+
└──────┬───────────────┘
|
|
79
|
+
│
|
|
80
|
+
├─────────── ❌ FileDiscoveryError
|
|
81
|
+
│ → Bucket/path not found
|
|
82
|
+
│ → Access denied
|
|
83
|
+
│ → RETRY? Usually No (config error)
|
|
84
|
+
│
|
|
85
|
+
▼
|
|
86
|
+
┌──────────────────────┐
|
|
87
|
+
│ Download File │
|
|
88
|
+
│ (read content) │
|
|
89
|
+
└──────┬───────────────┘
|
|
90
|
+
│
|
|
91
|
+
├─────────── ❌ IngestionError (NETWORK_ERROR)
|
|
92
|
+
│ → Network timeout
|
|
93
|
+
│ → Presigned URL expired (S3)
|
|
94
|
+
│ → SFTP connection lost
|
|
95
|
+
│ → RETRY? Yes
|
|
96
|
+
│
|
|
97
|
+
├─────────── ❌ Memory Error
|
|
98
|
+
│ → File too large
|
|
99
|
+
│ → Insufficient heap
|
|
100
|
+
│ → RETRY? No (use streaming)
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
┌──────────────────────┐
|
|
104
|
+
│ Parse Content │
|
|
105
|
+
│ (CSV/XML/JSON) │
|
|
106
|
+
└──────┬───────────────┘
|
|
107
|
+
│
|
|
108
|
+
├─────────── ❌ FileParsingError
|
|
109
|
+
│ → Malformed XML (missing tags)
|
|
110
|
+
│ → Invalid JSON (syntax error)
|
|
111
|
+
│ → CSV column mismatch
|
|
112
|
+
│ → Encoding error (non-UTF-8)
|
|
113
|
+
│ → RETRY? No (fix source data)
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
┌──────────────────────┐
|
|
117
|
+
│ ✅ SUCCESS │
|
|
118
|
+
│ Parsed data ready │
|
|
119
|
+
└──────────────────────┘
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Connection Errors
|
|
125
|
+
|
|
126
|
+
### S3 Connection Errors
|
|
127
|
+
|
|
128
|
+
#### Error 1: Invalid Credentials
|
|
129
|
+
|
|
130
|
+
**Symptoms:**
|
|
131
|
+
```
|
|
132
|
+
FileDiscoveryError: Access Denied
|
|
133
|
+
code: FILE_ACCESS_DENIED
|
|
134
|
+
message: "The AWS Access Key Id you provided does not exist in our records"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Causes:**
|
|
138
|
+
- Invalid `accessKeyId` or `secretAccessKey`
|
|
139
|
+
- Credentials expired (if using temporary credentials)
|
|
140
|
+
- Wrong AWS region specified
|
|
141
|
+
|
|
142
|
+
**Solution:**
|
|
143
|
+
```typescript
|
|
144
|
+
import {
|
|
145
|
+
S3DataSource,
|
|
146
|
+
FileDiscoveryError,
|
|
147
|
+
createConsoleLogger,
|
|
148
|
+
toStructuredLogger
|
|
149
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
150
|
+
|
|
151
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
152
|
+
logLevel: 'info'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
async function createS3Source() {
|
|
156
|
+
try {
|
|
157
|
+
const s3Source = new S3DataSource(
|
|
158
|
+
{
|
|
159
|
+
type: 'S3_CSV',
|
|
160
|
+
s3Config: {
|
|
161
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
162
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
163
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
164
|
+
bucket: process.env.S3_BUCKET,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
logger
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// ✅ IMPORTANT: Validate connection before proceeding
|
|
171
|
+
const isValid = await s3Source.validateConnection();
|
|
172
|
+
if (!isValid) {
|
|
173
|
+
throw new Error('S3 connection validation failed');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return s3Source;
|
|
177
|
+
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof FileDiscoveryError &&
|
|
180
|
+
error.code === 'FILE_ACCESS_DENIED') {
|
|
181
|
+
logger.error('S3 credentials invalid:', {
|
|
182
|
+
bucket: process.env.S3_BUCKET,
|
|
183
|
+
region: process.env.AWS_REGION,
|
|
184
|
+
// DO NOT log credentials!
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Don't retry - configuration error
|
|
188
|
+
throw new Error('S3 credentials are invalid. Check AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY');
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Debugging Checklist:**
|
|
196
|
+
- [ ] Verify `AWS_ACCESS_KEY_ID` is correct
|
|
197
|
+
- [ ] Verify `AWS_SECRET_ACCESS_KEY` is correct
|
|
198
|
+
- [ ] Check if credentials are expired (temporary credentials)
|
|
199
|
+
- [ ] Verify AWS region matches bucket region
|
|
200
|
+
- [ ] Test credentials with AWS CLI: `aws s3 ls s3://your-bucket`
|
|
201
|
+
|
|
202
|
+
#### Error 2: Wrong Region
|
|
203
|
+
|
|
204
|
+
**Symptoms:**
|
|
205
|
+
```
|
|
206
|
+
FileDiscoveryError: The bucket is in this region: us-west-2.
|
|
207
|
+
Please use this region to retry the request
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Solution:**
|
|
211
|
+
```typescript
|
|
212
|
+
// ❌ WRONG: Hardcoded region doesn't match bucket
|
|
213
|
+
const s3Source = new S3DataSource({
|
|
214
|
+
type: 'S3_CSV',
|
|
215
|
+
s3Config: {
|
|
216
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
217
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
218
|
+
region: 'us-east-1', // ❌ Bucket is in us-west-2
|
|
219
|
+
bucket: 'my-bucket',
|
|
220
|
+
},
|
|
221
|
+
}, logger);
|
|
222
|
+
|
|
223
|
+
// ✅ CORRECT: Use environment variable or bucket's actual region
|
|
224
|
+
const s3Source = new S3DataSource({
|
|
225
|
+
type: 'S3_CSV',
|
|
226
|
+
s3Config: {
|
|
227
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
228
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
229
|
+
region: process.env.AWS_REGION!, // ✅ Set to 'us-west-2'
|
|
230
|
+
bucket: 'my-bucket',
|
|
231
|
+
},
|
|
232
|
+
}, logger);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Error 3: Network Timeout (S3)
|
|
236
|
+
|
|
237
|
+
**Symptoms:**
|
|
238
|
+
```
|
|
239
|
+
IngestionError: Network request timed out
|
|
240
|
+
code: NETWORK_ERROR
|
|
241
|
+
isRetryable: true
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Solution with Retry:**
|
|
245
|
+
```typescript
|
|
246
|
+
import { IngestionError, IngestionErrorCode } from '@fluentcommerce/fc-connect-sdk';
|
|
247
|
+
|
|
248
|
+
async function downloadWithRetry(
|
|
249
|
+
s3Source: S3DataSource,
|
|
250
|
+
filePath: string,
|
|
251
|
+
maxRetries = 3
|
|
252
|
+
) {
|
|
253
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
254
|
+
try {
|
|
255
|
+
const content = await s3Source.downloadFile(filePath);
|
|
256
|
+
return content; // ✅ Success
|
|
257
|
+
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error instanceof IngestionError &&
|
|
260
|
+
error.code === IngestionErrorCode.NETWORK_ERROR &&
|
|
261
|
+
error.isRetryable() &&
|
|
262
|
+
attempt < maxRetries) {
|
|
263
|
+
|
|
264
|
+
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
|
|
265
|
+
logger.warn(`S3 network error - retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
|
|
266
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw error; // Not retryable or max retries exceeded
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### SFTP Connection Errors
|
|
277
|
+
|
|
278
|
+
#### Error 1: Authentication Failed
|
|
279
|
+
|
|
280
|
+
**Symptoms:**
|
|
281
|
+
```
|
|
282
|
+
FileDiscoveryError: All configured authentication methods failed
|
|
283
|
+
code: FILE_ACCESS_DENIED
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Causes:**
|
|
287
|
+
- Invalid SSH private key
|
|
288
|
+
- Wrong username
|
|
289
|
+
- Private key passphrase incorrect
|
|
290
|
+
- Host key verification failed
|
|
291
|
+
|
|
292
|
+
**Solution:**
|
|
293
|
+
```typescript
|
|
294
|
+
import { SftpDataSource, FileDiscoveryError } from '@fluentcommerce/fc-connect-sdk';
|
|
295
|
+
|
|
296
|
+
async function createSftpSource() {
|
|
297
|
+
try {
|
|
298
|
+
const sftpSource = new SftpDataSource(
|
|
299
|
+
{
|
|
300
|
+
type: 'SFTP_CSV',
|
|
301
|
+
connectionId: 'vendor-sftp',
|
|
302
|
+
settings: {
|
|
303
|
+
host: process.env.SFTP_HOST!,
|
|
304
|
+
port: parseInt(process.env.SFTP_PORT || '22'),
|
|
305
|
+
username: process.env.SFTP_USERNAME!,
|
|
306
|
+
privateKey: process.env.SFTP_PRIVATE_KEY!, // ✅ Use SSH key (not password)
|
|
307
|
+
passphrase: process.env.SFTP_PASSPHRASE, // If key is encrypted
|
|
308
|
+
remotePath: process.env.SFTP_REMOTE_PATH || '/data',
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
logger
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// ✅ Validate connection
|
|
315
|
+
const isValid = await sftpSource.validateConnection();
|
|
316
|
+
if (!isValid) {
|
|
317
|
+
throw new Error('SFTP connection validation failed');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return sftpSource;
|
|
321
|
+
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (error instanceof FileDiscoveryError &&
|
|
324
|
+
error.code === 'FILE_ACCESS_DENIED') {
|
|
325
|
+
logger.error('SFTP authentication failed:', {
|
|
326
|
+
host: process.env.SFTP_HOST,
|
|
327
|
+
username: process.env.SFTP_USERNAME,
|
|
328
|
+
// DO NOT log private key or passphrase!
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Check common issues
|
|
332
|
+
throw new Error(
|
|
333
|
+
'SFTP authentication failed. Check:\n' +
|
|
334
|
+
'1. SSH private key is correct\n' +
|
|
335
|
+
'2. Username is correct\n' +
|
|
336
|
+
'3. Passphrase is correct (if key is encrypted)\n' +
|
|
337
|
+
'4. Host key is trusted'
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Error 2: Connection Timeout (SFTP)
|
|
346
|
+
|
|
347
|
+
**Symptoms:**
|
|
348
|
+
```
|
|
349
|
+
FileDiscoveryError: Connection timeout after 30000ms
|
|
350
|
+
code: FILE_DISCOVERY_FAILED
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Causes:**
|
|
354
|
+
- SFTP server unreachable
|
|
355
|
+
- Firewall blocking port 22
|
|
356
|
+
- Network connectivity issues
|
|
357
|
+
- DNS resolution failure
|
|
358
|
+
|
|
359
|
+
**Solution:**
|
|
360
|
+
```typescript
|
|
361
|
+
async function testSftpConnectivity() {
|
|
362
|
+
const sftpConfig = {
|
|
363
|
+
type: 'SFTP_CSV' as const,
|
|
364
|
+
connectionId: 'test-sftp',
|
|
365
|
+
settings: {
|
|
366
|
+
host: process.env.SFTP_HOST!,
|
|
367
|
+
port: 22,
|
|
368
|
+
username: process.env.SFTP_USERNAME!,
|
|
369
|
+
privateKey: process.env.SFTP_PRIVATE_KEY!,
|
|
370
|
+
remotePath: '/data',
|
|
371
|
+
timeout: 30000, // 30 seconds
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const sftpSource = new SftpDataSource(sftpConfig, logger);
|
|
377
|
+
|
|
378
|
+
logger.info('Testing SFTP connectivity...');
|
|
379
|
+
const isValid = await sftpSource.validateConnection();
|
|
380
|
+
|
|
381
|
+
if (isValid) {
|
|
382
|
+
logger.info('✅ SFTP connection successful');
|
|
383
|
+
return sftpSource;
|
|
384
|
+
} else {
|
|
385
|
+
throw new Error('SFTP validation returned false');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (error instanceof FileDiscoveryError) {
|
|
390
|
+
logger.error('SFTP connection failed:', {
|
|
391
|
+
host: sftpConfig.settings.host,
|
|
392
|
+
port: sftpConfig.settings.port,
|
|
393
|
+
error: error.message,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Debugging steps
|
|
397
|
+
console.error('\n🔍 Debugging steps:');
|
|
398
|
+
console.error(`1. Test DNS: ping ${sftpConfig.settings.host}`);
|
|
399
|
+
console.error(`2. Test connectivity: telnet ${sftpConfig.settings.host} 22`);
|
|
400
|
+
console.error('3. Check firewall allows outbound port 22');
|
|
401
|
+
console.error('4. Verify SFTP server is running');
|
|
402
|
+
}
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Discovery Errors
|
|
411
|
+
|
|
412
|
+
### Error 1: File Not Found
|
|
413
|
+
|
|
414
|
+
**Symptoms:**
|
|
415
|
+
```
|
|
416
|
+
FileDiscoveryError: File not found
|
|
417
|
+
code: FILE_NOT_FOUND
|
|
418
|
+
path: "s3://my-bucket/inventory/missing.csv"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Solution:**
|
|
422
|
+
```typescript
|
|
423
|
+
async function safeFileDownload(
|
|
424
|
+
dataSource: S3DataSource | SftpDataSource,
|
|
425
|
+
filePath: string
|
|
426
|
+
) {
|
|
427
|
+
try {
|
|
428
|
+
// Check if file exists first
|
|
429
|
+
const files = await dataSource.listFiles({ prefix: filePath });
|
|
430
|
+
|
|
431
|
+
if (files.length === 0) {
|
|
432
|
+
logger.warn(`File not found: ${filePath}`);
|
|
433
|
+
return null; // Return null instead of throwing
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// File exists - download it
|
|
437
|
+
const content = await dataSource.downloadFile(filePath);
|
|
438
|
+
return content;
|
|
439
|
+
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (error instanceof FileDiscoveryError &&
|
|
442
|
+
error.code === 'FILE_NOT_FOUND') {
|
|
443
|
+
logger.warn('File not found during download:', { path: filePath });
|
|
444
|
+
return null; // Graceful handling
|
|
445
|
+
}
|
|
446
|
+
throw error; // Re-throw other errors
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Error 2: Access Denied (Permissions)
|
|
452
|
+
|
|
453
|
+
**Symptoms:**
|
|
454
|
+
```
|
|
455
|
+
FileDiscoveryError: Access Denied
|
|
456
|
+
code: FILE_ACCESS_DENIED
|
|
457
|
+
message: "User is not authorized to perform: s3:GetObject"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**IAM Policy Required (S3):**
|
|
461
|
+
```json
|
|
462
|
+
{
|
|
463
|
+
"Version": "2012-10-17",
|
|
464
|
+
"Statement": [
|
|
465
|
+
{
|
|
466
|
+
"Effect": "Allow",
|
|
467
|
+
"Action": [
|
|
468
|
+
"s3:ListBucket",
|
|
469
|
+
"s3:GetObject",
|
|
470
|
+
"s3:GetObjectVersion"
|
|
471
|
+
],
|
|
472
|
+
"Resource": [
|
|
473
|
+
"arn:aws:s3:::my-bucket",
|
|
474
|
+
"arn:aws:s3:::my-bucket/*"
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Solution:**
|
|
482
|
+
```typescript
|
|
483
|
+
async function validateS3Permissions(s3Source: S3DataSource) {
|
|
484
|
+
try {
|
|
485
|
+
logger.info('Validating S3 permissions...');
|
|
486
|
+
|
|
487
|
+
// Test 1: Can list bucket?
|
|
488
|
+
try {
|
|
489
|
+
await s3Source.listFiles({ prefix: '' });
|
|
490
|
+
logger.info('✅ s3:ListBucket permission OK');
|
|
491
|
+
} catch (error) {
|
|
492
|
+
logger.error('❌ s3:ListBucket permission DENIED');
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Test 2: Can read objects?
|
|
497
|
+
const files = await s3Source.listFiles({ prefix: '' });
|
|
498
|
+
if (files.length > 0) {
|
|
499
|
+
try {
|
|
500
|
+
await s3Source.downloadFile(files[0].path);
|
|
501
|
+
logger.info('✅ s3:GetObject permission OK');
|
|
502
|
+
} catch (error) {
|
|
503
|
+
logger.error('❌ s3:GetObject permission DENIED');
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
logger.info('✅ All S3 permissions validated');
|
|
509
|
+
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (error instanceof FileDiscoveryError &&
|
|
512
|
+
error.code === 'FILE_ACCESS_DENIED') {
|
|
513
|
+
console.error('\n❌ IAM Permissions Required:');
|
|
514
|
+
console.error(' - s3:ListBucket on arn:aws:s3:::my-bucket');
|
|
515
|
+
console.error(' - s3:GetObject on arn:aws:s3:::my-bucket/*');
|
|
516
|
+
console.error('\nAdd this IAM policy to your AWS user/role.');
|
|
517
|
+
}
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Reading Errors
|
|
526
|
+
|
|
527
|
+
### Error 1: Out of Memory (Large Files)
|
|
528
|
+
|
|
529
|
+
**Symptoms:**
|
|
530
|
+
```
|
|
531
|
+
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Causes:**
|
|
535
|
+
- File too large to load in memory
|
|
536
|
+
- Default Node.js heap size (512MB - 2GB)
|
|
537
|
+
- Multiple large files processed concurrently
|
|
538
|
+
|
|
539
|
+
**Solution 1: Increase Heap Size**
|
|
540
|
+
```bash
|
|
541
|
+
# Temporary fix - increase Node.js heap
|
|
542
|
+
node --max-old-space-size=4096 your-script.js # 4GB heap
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Solution 2: Stream Large Files (Best Practice)**
|
|
546
|
+
```typescript
|
|
547
|
+
import { S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
548
|
+
import { Readable } from 'stream';
|
|
549
|
+
|
|
550
|
+
async function processLargeFile(s3Source: S3DataSource, filePath: string) {
|
|
551
|
+
try {
|
|
552
|
+
// Option 1: Download in binary mode (Buffer)
|
|
553
|
+
const content = await s3Source.downloadFile(filePath, { encoding: 'binary' });
|
|
554
|
+
|
|
555
|
+
// Process in chunks
|
|
556
|
+
const buffer = content as Buffer;
|
|
557
|
+
const chunkSize = 1024 * 1024; // 1MB chunks
|
|
558
|
+
|
|
559
|
+
for (let offset = 0; offset < buffer.length; offset += chunkSize) {
|
|
560
|
+
const chunk = buffer.slice(offset, offset + chunkSize);
|
|
561
|
+
await processChunk(chunk);
|
|
562
|
+
|
|
563
|
+
// Allow garbage collection
|
|
564
|
+
if (global.gc) global.gc();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
} catch (error) {
|
|
568
|
+
if (error.message?.includes('heap out of memory')) {
|
|
569
|
+
logger.error('Out of memory processing large file:', {
|
|
570
|
+
file: filePath,
|
|
571
|
+
suggestion: 'Use chunked processing or increase heap size',
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
throw new Error(
|
|
575
|
+
'File too large for available memory. ' +
|
|
576
|
+
'Increase heap size with: node --max-old-space-size=4096'
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async function processChunk(chunk: Buffer) {
|
|
584
|
+
// Process chunk here
|
|
585
|
+
logger.debug(`Processing chunk: ${chunk.length} bytes`);
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Error 2: Presigned URL Expired (S3)
|
|
590
|
+
|
|
591
|
+
**Symptoms:**
|
|
592
|
+
```
|
|
593
|
+
FileDiscoveryError: Request has expired
|
|
594
|
+
code: FILE_ACCESS_DENIED
|
|
595
|
+
statusCode: 403
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Cause:**
|
|
599
|
+
- Presigned URLs expire after 1 hour (SDK default)
|
|
600
|
+
- Long-running operations exceed URL lifetime
|
|
601
|
+
|
|
602
|
+
**Solution:**
|
|
603
|
+
```typescript
|
|
604
|
+
async function downloadWithUrlRefresh(s3Source: S3DataSource, filePath: string) {
|
|
605
|
+
const startTime = Date.now();
|
|
606
|
+
const URL_LIFETIME_MS = 55 * 60 * 1000; // 55 minutes (buffer before 1 hour expiry)
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const content = await s3Source.downloadFile(filePath);
|
|
610
|
+
return content;
|
|
611
|
+
|
|
612
|
+
} catch (error) {
|
|
613
|
+
const elapsed = Date.now() - startTime;
|
|
614
|
+
|
|
615
|
+
if (error instanceof FileDiscoveryError &&
|
|
616
|
+
error.code === 'FILE_ACCESS_DENIED' &&
|
|
617
|
+
error.message?.includes('expired') &&
|
|
618
|
+
elapsed > URL_LIFETIME_MS) {
|
|
619
|
+
|
|
620
|
+
logger.warn('Presigned URL expired - recreating data source');
|
|
621
|
+
|
|
622
|
+
// Recreate S3DataSource (generates new presigned URLs)
|
|
623
|
+
const newS3Source = new S3DataSource(s3Source.getConfig(), logger);
|
|
624
|
+
|
|
625
|
+
// Retry with new URL
|
|
626
|
+
return await newS3Source.downloadFile(filePath);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Parsing Errors
|
|
637
|
+
|
|
638
|
+
### Error 1: Malformed XML
|
|
639
|
+
|
|
640
|
+
**Symptoms:**
|
|
641
|
+
```
|
|
642
|
+
FileParsingError: Failed to parse XML: Unclosed tag 'customer-email'
|
|
643
|
+
fileName: "order.xml"
|
|
644
|
+
lineNumber: 5
|
|
645
|
+
code: PARSE_ERROR
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Example Malformed XML:**
|
|
649
|
+
```xml
|
|
650
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
651
|
+
<order>
|
|
652
|
+
<order-id>12345</order-id>
|
|
653
|
+
<customer>
|
|
654
|
+
<email>test@example.com
|
|
655
|
+
<!-- ❌ Missing closing tag for <email> and <customer> -->
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Solution:**
|
|
659
|
+
```typescript
|
|
660
|
+
import { XMLParserService, FileParsingError } from '@fluentcommerce/fc-connect-sdk';
|
|
661
|
+
|
|
662
|
+
async function parseXMLWithValidation(xmlContent: string, fileName: string) {
|
|
663
|
+
const parser = new XMLParserService();
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const parsed = await parser.parse(xmlContent);
|
|
667
|
+
return parsed;
|
|
668
|
+
|
|
669
|
+
} catch (error) {
|
|
670
|
+
if (error instanceof FileParsingError &&
|
|
671
|
+
error.code === 'PARSE_ERROR') {
|
|
672
|
+
logger.error('XML parsing failed:', {
|
|
673
|
+
fileName: error.fileName || fileName,
|
|
674
|
+
lineNumber: error.lineNumber,
|
|
675
|
+
message: error.message,
|
|
676
|
+
// Show first 200 chars of XML for debugging
|
|
677
|
+
xmlPreview: xmlContent.substring(0, 200),
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
throw new Error(
|
|
681
|
+
`Invalid XML in ${fileName}:\n` +
|
|
682
|
+
` Line ${error.lineNumber}: ${error.message}\n` +
|
|
683
|
+
` Fix: Ensure all XML tags are properly closed`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
throw error;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**Common XML Errors:**
|
|
692
|
+
| Error | Cause | Fix |
|
|
693
|
+
|-------|-------|-----|
|
|
694
|
+
| `Unclosed tag 'tagname'` | Missing `</tagname>` | Add closing tag |
|
|
695
|
+
| `Unexpected closing tag` | Extra `</tagname>` | Remove duplicate closing tag |
|
|
696
|
+
| `Invalid character` | Special chars not escaped | Use `&`, `<`, `>`, `"`, `'` |
|
|
697
|
+
| `Text not allowed here` | Text outside tags | Wrap text in tags |
|
|
698
|
+
|
|
699
|
+
### Error 2: Invalid JSON
|
|
700
|
+
|
|
701
|
+
**Symptoms:**
|
|
702
|
+
```
|
|
703
|
+
FileParsingError: Failed to parse JSON: Unexpected token a in JSON at position 45
|
|
704
|
+
fileName: "order.json"
|
|
705
|
+
code: PARSE_ERROR
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
**Example Invalid JSON:**
|
|
709
|
+
```json
|
|
710
|
+
{
|
|
711
|
+
"orderId": "12345",
|
|
712
|
+
"status": active, // ❌ Unquoted string
|
|
713
|
+
"items": [
|
|
714
|
+
{
|
|
715
|
+
"sku": "ABC-123",
|
|
716
|
+
"qty": 5, // ❌ Trailing comma
|
|
717
|
+
}
|
|
718
|
+
], // ❌ Trailing comma
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**Solution:**
|
|
723
|
+
```typescript
|
|
724
|
+
import { JSONParserService, FileParsingError } from '@fluentcommerce/fc-connect-sdk';
|
|
725
|
+
|
|
726
|
+
async function parseJSONWithValidation(jsonContent: string, fileName: string) {
|
|
727
|
+
const parser = new JSONParserService();
|
|
728
|
+
|
|
729
|
+
try {
|
|
730
|
+
const parsed = await parser.parse(jsonContent);
|
|
731
|
+
return parsed;
|
|
732
|
+
|
|
733
|
+
} catch (error) {
|
|
734
|
+
if (error instanceof FileParsingError &&
|
|
735
|
+
error.code === 'PARSE_ERROR') {
|
|
736
|
+
|
|
737
|
+
// Extract position from error message
|
|
738
|
+
const positionMatch = error.message.match(/position (\d+)/);
|
|
739
|
+
const position = positionMatch ? parseInt(positionMatch[1]) : 0;
|
|
740
|
+
|
|
741
|
+
// Show context around error
|
|
742
|
+
const start = Math.max(0, position - 50);
|
|
743
|
+
const end = Math.min(jsonContent.length, position + 50);
|
|
744
|
+
const context = jsonContent.substring(start, end);
|
|
745
|
+
|
|
746
|
+
logger.error('JSON parsing failed:', {
|
|
747
|
+
fileName: fileName,
|
|
748
|
+
position: position,
|
|
749
|
+
context: context,
|
|
750
|
+
message: error.message,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
throw new Error(
|
|
754
|
+
`Invalid JSON in ${fileName}:\n` +
|
|
755
|
+
` ${error.message}\n` +
|
|
756
|
+
` Context: ...${context}...`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
throw error;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
**Common JSON Errors:**
|
|
765
|
+
| Error | Cause | Fix |
|
|
766
|
+
|-------|-------|-----|
|
|
767
|
+
| `Unexpected token` | Unquoted string, trailing comma | Quote strings, remove trailing commas |
|
|
768
|
+
| `Unexpected end of JSON` | Missing closing `}` or `]` | Add closing bracket |
|
|
769
|
+
| `Expected property name` | Missing quotes on key | Quote object keys: `"key": value` |
|
|
770
|
+
|
|
771
|
+
### Error 3: CSV Column Mismatch
|
|
772
|
+
|
|
773
|
+
**Symptoms:**
|
|
774
|
+
```
|
|
775
|
+
FileParsingError: CSV column count mismatch
|
|
776
|
+
fileName: "inventory.csv"
|
|
777
|
+
code: INVALID_CSV_FORMAT
|
|
778
|
+
expectedColumns: 5
|
|
779
|
+
actualColumns: 4
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**Example Invalid CSV:**
|
|
783
|
+
```csv
|
|
784
|
+
sku,location,quantity,date,status
|
|
785
|
+
SKU-001,LOC-01,100,2024-01-01,ACTIVE
|
|
786
|
+
SKU-002,LOC-02,50,2024-01-02 ← Missing 'status' column
|
|
787
|
+
SKU-003,LOC-03,75,2024-01-03,ACTIVE
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**Solution:**
|
|
791
|
+
```typescript
|
|
792
|
+
import { CSVParserService, FileParsingError } from '@fluentcommerce/fc-connect-sdk';
|
|
793
|
+
|
|
794
|
+
async function parseCSVWithValidation(
|
|
795
|
+
csvContent: string,
|
|
796
|
+
fileName: string,
|
|
797
|
+
expectedColumns?: string[]
|
|
798
|
+
) {
|
|
799
|
+
const parser = new CSVParserService();
|
|
800
|
+
|
|
801
|
+
try {
|
|
802
|
+
const parsed = await parser.parse(csvContent);
|
|
803
|
+
|
|
804
|
+
// Optional: Validate column names
|
|
805
|
+
if (expectedColumns && parsed.length > 0) {
|
|
806
|
+
const actualColumns = Object.keys(parsed[0]);
|
|
807
|
+
const missingColumns = expectedColumns.filter(col => !actualColumns.includes(col));
|
|
808
|
+
|
|
809
|
+
if (missingColumns.length > 0) {
|
|
810
|
+
throw new FileParsingError(
|
|
811
|
+
`CSV missing required columns: ${missingColumns.join(', ')}`,
|
|
812
|
+
'INVALID_CSV_FORMAT',
|
|
813
|
+
{ fileName, missingColumns, actualColumns }
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return parsed;
|
|
819
|
+
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (error instanceof FileParsingError &&
|
|
822
|
+
error.code === 'INVALID_CSV_FORMAT') {
|
|
823
|
+
logger.error('CSV parsing failed:', {
|
|
824
|
+
fileName: fileName,
|
|
825
|
+
message: error.message,
|
|
826
|
+
context: error.context,
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
throw new Error(
|
|
830
|
+
`Invalid CSV in ${fileName}:\n` +
|
|
831
|
+
` ${error.message}\n` +
|
|
832
|
+
` Fix: Ensure all rows have the same number of columns`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
throw error;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Usage
|
|
840
|
+
const expectedColumns = ['sku', 'location', 'quantity', 'date', 'status'];
|
|
841
|
+
const parsed = await parseCSVWithValidation(csvContent, 'inventory.csv', expectedColumns);
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### Error 4: Encoding Issues (Non-UTF-8)
|
|
845
|
+
|
|
846
|
+
**Symptoms:**
|
|
847
|
+
```
|
|
848
|
+
FileParsingError: Invalid UTF-8 sequence
|
|
849
|
+
code: ENCODING_ERROR
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**Cause:**
|
|
853
|
+
- File encoded in ISO-8859-1, Windows-1252, or other non-UTF-8 encoding
|
|
854
|
+
- Special characters corrupted
|
|
855
|
+
|
|
856
|
+
**Solution:**
|
|
857
|
+
```typescript
|
|
858
|
+
import { XMLParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
859
|
+
import iconv from 'iconv-lite';
|
|
860
|
+
|
|
861
|
+
async function parseWithEncodingDetection(
|
|
862
|
+
fileContent: Buffer | string,
|
|
863
|
+
fileName: string
|
|
864
|
+
) {
|
|
865
|
+
const parser = new XMLParserService();
|
|
866
|
+
|
|
867
|
+
try {
|
|
868
|
+
// Try UTF-8 first (SDK default)
|
|
869
|
+
const content = typeof fileContent === 'string' ? fileContent : fileContent.toString('utf-8');
|
|
870
|
+
return await parser.parse(content);
|
|
871
|
+
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (error instanceof FileParsingError &&
|
|
874
|
+
error.code === 'ENCODING_ERROR') {
|
|
875
|
+
|
|
876
|
+
logger.warn('UTF-8 parsing failed - trying alternate encodings');
|
|
877
|
+
|
|
878
|
+
// Try common alternate encodings
|
|
879
|
+
const encodings = ['iso-8859-1', 'windows-1252', 'utf-16le'];
|
|
880
|
+
|
|
881
|
+
for (const encoding of encodings) {
|
|
882
|
+
try {
|
|
883
|
+
const buffer = typeof fileContent === 'string' ?
|
|
884
|
+
Buffer.from(fileContent, 'binary') : fileContent;
|
|
885
|
+
|
|
886
|
+
const converted = iconv.decode(buffer, encoding);
|
|
887
|
+
const parsed = await parser.parse(converted);
|
|
888
|
+
|
|
889
|
+
logger.info(`✅ Successfully parsed with ${encoding} encoding`);
|
|
890
|
+
return parsed;
|
|
891
|
+
|
|
892
|
+
} catch (e) {
|
|
893
|
+
continue; // Try next encoding
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// All encodings failed
|
|
898
|
+
throw new Error(
|
|
899
|
+
`Could not parse ${fileName} with any encoding. ` +
|
|
900
|
+
`Tried: UTF-8, ${encodings.join(', ')}`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
throw error;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**Note:** The SDK automatically handles UTF-8 BOM (Byte Order Mark) removal, so BOM presence is not an error.
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## Complete Examples
|
|
913
|
+
|
|
914
|
+
### Example 1: Production-Ready S3 CSV Pipeline
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
import {
|
|
918
|
+
S3DataSource,
|
|
919
|
+
CSVParserService,
|
|
920
|
+
FileDiscoveryError,
|
|
921
|
+
FileParsingError,
|
|
922
|
+
IngestionError,
|
|
923
|
+
IngestionErrorCode,
|
|
924
|
+
createConsoleLogger,
|
|
925
|
+
toStructuredLogger
|
|
926
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
927
|
+
|
|
928
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
929
|
+
logLevel: 'info'
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
async function processS3InventoryFiles() {
|
|
933
|
+
let s3Source: S3DataSource | null = null;
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
// ────────────────────────────────────────────────────────
|
|
937
|
+
// STAGE 1: Connection
|
|
938
|
+
// ────────────────────────────────────────────────────────
|
|
939
|
+
logger.info('Creating S3 data source...');
|
|
940
|
+
|
|
941
|
+
s3Source = new S3DataSource(
|
|
942
|
+
{
|
|
943
|
+
type: 'S3_CSV',
|
|
944
|
+
s3Config: {
|
|
945
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
946
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
947
|
+
region: process.env.AWS_REGION!,
|
|
948
|
+
bucket: process.env.S3_BUCKET!,
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
logger
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
// Validate connection
|
|
955
|
+
const isValid = await s3Source.validateConnection();
|
|
956
|
+
if (!isValid) {
|
|
957
|
+
throw new Error('S3 connection validation failed');
|
|
958
|
+
}
|
|
959
|
+
logger.info('✅ S3 connection validated');
|
|
960
|
+
|
|
961
|
+
// ────────────────────────────────────────────────────────
|
|
962
|
+
// STAGE 2: Discovery
|
|
963
|
+
// ────────────────────────────────────────────────────────
|
|
964
|
+
logger.info('Discovering files...');
|
|
965
|
+
|
|
966
|
+
const files = await s3Source.listFiles({
|
|
967
|
+
prefix: 'inventory/',
|
|
968
|
+
pattern: /\.csv$/i,
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
if (files.length === 0) {
|
|
972
|
+
logger.warn('No CSV files found in inventory/ folder');
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
logger.info(`Found ${files.length} CSV files`);
|
|
977
|
+
|
|
978
|
+
// ────────────────────────────────────────────────────────
|
|
979
|
+
// STAGE 3: Reading & Parsing
|
|
980
|
+
// ────────────────────────────────────────────────────────
|
|
981
|
+
const parser = new CSVParserService();
|
|
982
|
+
const results = [];
|
|
983
|
+
|
|
984
|
+
for (const file of files) {
|
|
985
|
+
try {
|
|
986
|
+
logger.info(`Processing ${file.name}...`);
|
|
987
|
+
|
|
988
|
+
// Download file with retry
|
|
989
|
+
const content = await downloadWithRetry(s3Source, file.path, 3);
|
|
990
|
+
|
|
991
|
+
// Parse CSV
|
|
992
|
+
const parsed = await parser.parse(content as string);
|
|
993
|
+
|
|
994
|
+
logger.info(`✅ Parsed ${parsed.length} records from ${file.name}`);
|
|
995
|
+
results.push({ file: file.name, records: parsed });
|
|
996
|
+
|
|
997
|
+
} catch (error) {
|
|
998
|
+
if (error instanceof FileParsingError) {
|
|
999
|
+
logger.error(`❌ Failed to parse ${file.name}:`, {
|
|
1000
|
+
code: error.code,
|
|
1001
|
+
message: error.message,
|
|
1002
|
+
lineNumber: error.lineNumber,
|
|
1003
|
+
});
|
|
1004
|
+
// Continue with next file (don't fail entire batch)
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
throw error; // Re-throw other errors
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
logger.info(`✅ Successfully processed ${results.length}/${files.length} files`);
|
|
1012
|
+
return results;
|
|
1013
|
+
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
// ────────────────────────────────────────────────────────
|
|
1016
|
+
// STAGE 4: Error Handling
|
|
1017
|
+
// ────────────────────────────────────────────────────────
|
|
1018
|
+
if (error instanceof FileDiscoveryError) {
|
|
1019
|
+
logger.error('File discovery error:', {
|
|
1020
|
+
code: error.code,
|
|
1021
|
+
message: error.message,
|
|
1022
|
+
path: error.path,
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
if (error.code === 'FILE_ACCESS_DENIED') {
|
|
1026
|
+
console.error('\n❌ S3 Access Denied:');
|
|
1027
|
+
console.error(' 1. Check AWS credentials are correct');
|
|
1028
|
+
console.error(' 2. Verify IAM policy allows s3:ListBucket and s3:GetObject');
|
|
1029
|
+
console.error(' 3. Confirm bucket exists and region is correct');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
} else if (error instanceof IngestionError) {
|
|
1033
|
+
logger.error('Ingestion error:', error.toJSON());
|
|
1034
|
+
|
|
1035
|
+
if (error.isRetryable()) {
|
|
1036
|
+
console.error('\n⚠️ Retryable error - consider running again');
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
} else {
|
|
1040
|
+
logger.error('Unexpected error:', error);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Helper: Download with retry (from earlier example)
|
|
1048
|
+
async function downloadWithRetry(
|
|
1049
|
+
s3Source: S3DataSource,
|
|
1050
|
+
filePath: string,
|
|
1051
|
+
maxRetries = 3
|
|
1052
|
+
) {
|
|
1053
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1054
|
+
try {
|
|
1055
|
+
return await s3Source.downloadFile(filePath);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
if (error instanceof IngestionError &&
|
|
1058
|
+
error.code === IngestionErrorCode.NETWORK_ERROR &&
|
|
1059
|
+
error.isRetryable() &&
|
|
1060
|
+
attempt < maxRetries) {
|
|
1061
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
1062
|
+
logger.warn(`Retry attempt ${attempt}/${maxRetries} in ${delay}ms`);
|
|
1063
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
throw error;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Run the pipeline
|
|
1072
|
+
processS3InventoryFiles()
|
|
1073
|
+
.then(() => logger.info('Pipeline completed successfully'))
|
|
1074
|
+
.catch(error => {
|
|
1075
|
+
logger.error('Pipeline failed:', error);
|
|
1076
|
+
process.exit(1);
|
|
1077
|
+
});
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
### Example 2: Production-Ready SFTP XML Pipeline
|
|
1081
|
+
|
|
1082
|
+
```typescript
|
|
1083
|
+
import {
|
|
1084
|
+
SftpDataSource,
|
|
1085
|
+
XMLParserService,
|
|
1086
|
+
FileDiscoveryError,
|
|
1087
|
+
FileParsingError,
|
|
1088
|
+
createConsoleLogger,
|
|
1089
|
+
toStructuredLogger
|
|
1090
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
1091
|
+
|
|
1092
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
1093
|
+
logLevel: 'info'
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
async function processSftpOrderFiles() {
|
|
1097
|
+
let sftpSource: SftpDataSource | null = null;
|
|
1098
|
+
|
|
1099
|
+
try {
|
|
1100
|
+
// ────────────────────────────────────────────────────────
|
|
1101
|
+
// STAGE 1: Connection
|
|
1102
|
+
// ────────────────────────────────────────────────────────
|
|
1103
|
+
logger.info('Connecting to SFTP...');
|
|
1104
|
+
|
|
1105
|
+
sftpSource = new SftpDataSource(
|
|
1106
|
+
{
|
|
1107
|
+
type: 'SFTP_XML',
|
|
1108
|
+
connectionId: 'vendor-sftp',
|
|
1109
|
+
settings: {
|
|
1110
|
+
host: process.env.SFTP_HOST!,
|
|
1111
|
+
port: parseInt(process.env.SFTP_PORT || '22'),
|
|
1112
|
+
username: process.env.SFTP_USERNAME!,
|
|
1113
|
+
privateKey: process.env.SFTP_PRIVATE_KEY!,
|
|
1114
|
+
passphrase: process.env.SFTP_PASSPHRASE,
|
|
1115
|
+
remotePath: '/orders',
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
logger
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
// Validate connection
|
|
1122
|
+
const isValid = await sftpSource.validateConnection();
|
|
1123
|
+
if (!isValid) {
|
|
1124
|
+
throw new Error('SFTP connection validation failed');
|
|
1125
|
+
}
|
|
1126
|
+
logger.info('✅ SFTP connection validated');
|
|
1127
|
+
|
|
1128
|
+
// ────────────────────────────────────────────────────────
|
|
1129
|
+
// STAGE 2: Discovery
|
|
1130
|
+
// ────────────────────────────────────────────────────────
|
|
1131
|
+
logger.info('Listing XML files...');
|
|
1132
|
+
|
|
1133
|
+
const files = await sftpSource.listFiles({
|
|
1134
|
+
pattern: /\.xml$/i,
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
if (files.length === 0) {
|
|
1138
|
+
logger.info('No XML files found');
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
logger.info(`Found ${files.length} XML files`);
|
|
1143
|
+
|
|
1144
|
+
// ────────────────────────────────────────────────────────
|
|
1145
|
+
// STAGE 3: Reading & Parsing
|
|
1146
|
+
// ────────────────────────────────────────────────────────
|
|
1147
|
+
const parser = new XMLParserService();
|
|
1148
|
+
const results = [];
|
|
1149
|
+
|
|
1150
|
+
for (const file of files) {
|
|
1151
|
+
try {
|
|
1152
|
+
logger.info(`Processing ${file.name}...`);
|
|
1153
|
+
|
|
1154
|
+
// Download file
|
|
1155
|
+
const content = await sftpSource.downloadFile(file.path);
|
|
1156
|
+
|
|
1157
|
+
// Parse XML
|
|
1158
|
+
const parsed = await parser.parse(content as string);
|
|
1159
|
+
|
|
1160
|
+
logger.info(`✅ Parsed XML from ${file.name}`);
|
|
1161
|
+
results.push({ file: file.name, data: parsed });
|
|
1162
|
+
|
|
1163
|
+
// Archive processed file
|
|
1164
|
+
await sftpSource.archiveFile(file.path, `/archive/${file.name}`);
|
|
1165
|
+
logger.info(`Archived ${file.name}`);
|
|
1166
|
+
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
if (error instanceof FileParsingError) {
|
|
1169
|
+
logger.error(`❌ XML parsing failed for ${file.name}:`, {
|
|
1170
|
+
code: error.code,
|
|
1171
|
+
message: error.message,
|
|
1172
|
+
lineNumber: error.lineNumber,
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
// Move to error folder
|
|
1176
|
+
await sftpSource.archiveFile(file.path, `/errors/${file.name}`);
|
|
1177
|
+
continue; // Process next file
|
|
1178
|
+
}
|
|
1179
|
+
throw error;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
logger.info(`✅ Processed ${results.length}/${files.length} files`);
|
|
1184
|
+
return results;
|
|
1185
|
+
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
if (error instanceof FileDiscoveryError) {
|
|
1188
|
+
logger.error('SFTP error:', {
|
|
1189
|
+
code: error.code,
|
|
1190
|
+
message: error.message,
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
if (error.code === 'FILE_ACCESS_DENIED') {
|
|
1194
|
+
console.error('\n❌ SFTP Authentication Failed:');
|
|
1195
|
+
console.error(' 1. Verify SSH private key is correct');
|
|
1196
|
+
console.error(' 2. Check username is correct');
|
|
1197
|
+
console.error(' 3. Confirm passphrase (if key is encrypted)');
|
|
1198
|
+
console.error(' 4. Test connection: ssh username@host');
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (error.message?.includes('timeout')) {
|
|
1202
|
+
console.error('\n❌ SFTP Connection Timeout:');
|
|
1203
|
+
console.error(' 1. Check SFTP host is reachable');
|
|
1204
|
+
console.error(' 2. Verify firewall allows outbound port 22');
|
|
1205
|
+
console.error(' 3. Test connectivity: telnet host 22');
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Run the pipeline
|
|
1214
|
+
processSftpOrderFiles()
|
|
1215
|
+
.then(() => logger.info('SFTP pipeline completed'))
|
|
1216
|
+
.catch(error => {
|
|
1217
|
+
logger.error('SFTP pipeline failed:', error);
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
});
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
---
|
|
1223
|
+
|
|
1224
|
+
## Retry Strategies
|
|
1225
|
+
|
|
1226
|
+
### When to Retry
|
|
1227
|
+
|
|
1228
|
+
```typescript
|
|
1229
|
+
function shouldRetry(error: unknown): boolean {
|
|
1230
|
+
// 1. Network errors - ALWAYS retry
|
|
1231
|
+
if (error instanceof IngestionError &&
|
|
1232
|
+
error.code === IngestionErrorCode.NETWORK_ERROR) {
|
|
1233
|
+
return true;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// 2. Timeout errors - ALWAYS retry
|
|
1237
|
+
if (error instanceof IngestionError &&
|
|
1238
|
+
error.code === IngestionErrorCode.TIMEOUT_ERROR) {
|
|
1239
|
+
return true;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// 3. File discovery errors - SOMETIMES retry
|
|
1243
|
+
if (error instanceof FileDiscoveryError) {
|
|
1244
|
+
// Retry if transient (network, timeout)
|
|
1245
|
+
if (error.message?.includes('timeout') ||
|
|
1246
|
+
error.message?.includes('ECONNREFUSED')) {
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
// Don't retry permanent errors (access denied, not found)
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// 4. Parsing errors - NEVER retry
|
|
1254
|
+
if (error instanceof FileParsingError) {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// 5. Unknown errors - NEVER retry
|
|
1259
|
+
return false;
|
|
1260
|
+
}
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
### Exponential Backoff with Jitter
|
|
1264
|
+
|
|
1265
|
+
```typescript
|
|
1266
|
+
async function retryWithBackoff<T>(
|
|
1267
|
+
operation: () => Promise<T>,
|
|
1268
|
+
maxRetries = 3,
|
|
1269
|
+
baseDelayMs = 1000
|
|
1270
|
+
): Promise<T> {
|
|
1271
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1272
|
+
try {
|
|
1273
|
+
return await operation(); // ✅ Success
|
|
1274
|
+
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
const isLastAttempt = attempt === maxRetries;
|
|
1277
|
+
const shouldRetryError = shouldRetry(error);
|
|
1278
|
+
|
|
1279
|
+
if (!shouldRetryError || isLastAttempt) {
|
|
1280
|
+
throw error; // Give up
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Calculate delay with exponential backoff + jitter
|
|
1284
|
+
const exponentialDelay = Math.pow(2, attempt) * baseDelayMs;
|
|
1285
|
+
const jitter = Math.random() * 1000; // Random 0-1000ms
|
|
1286
|
+
const delay = exponentialDelay + jitter;
|
|
1287
|
+
|
|
1288
|
+
logger.warn(`Attempt ${attempt}/${maxRetries} failed - retrying in ${Math.round(delay)}ms`, {
|
|
1289
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
throw new Error('Unreachable'); // TypeScript exhaustiveness check
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Usage
|
|
1300
|
+
const content = await retryWithBackoff(
|
|
1301
|
+
() => s3Source.downloadFile('inventory.csv'),
|
|
1302
|
+
3, // max 3 retries
|
|
1303
|
+
1000 // start with 1 second
|
|
1304
|
+
);
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
---
|
|
1308
|
+
|
|
1309
|
+
## Troubleshooting Flowchart
|
|
1310
|
+
|
|
1311
|
+
```
|
|
1312
|
+
┌─────────────────────────────────────────────┐
|
|
1313
|
+
│ Error occurred during file operation │
|
|
1314
|
+
└────────────────┬────────────────────────────┘
|
|
1315
|
+
│
|
|
1316
|
+
▼
|
|
1317
|
+
┌───────────────┐
|
|
1318
|
+
│ Error type? │
|
|
1319
|
+
└───┬───────────┘
|
|
1320
|
+
│
|
|
1321
|
+
┌───────┼───────┬────────┬──────────┐
|
|
1322
|
+
│ │ │ │ │
|
|
1323
|
+
▼ ▼ ▼ ▼ ▼
|
|
1324
|
+
┌─────────────────────────────────────────────┐
|
|
1325
|
+
│ FileDiscoveryError │
|
|
1326
|
+
├─────────────────────────────────────────────┤
|
|
1327
|
+
│ Code: FILE_ACCESS_DENIED │
|
|
1328
|
+
│ ├─ Check credentials │
|
|
1329
|
+
│ ├─ Verify IAM/SSH permissions │
|
|
1330
|
+
│ └─ Confirm bucket/host exists │
|
|
1331
|
+
│ │
|
|
1332
|
+
│ Code: FILE_NOT_FOUND │
|
|
1333
|
+
│ ├─ Verify file path is correct │
|
|
1334
|
+
│ ├─ Check prefix/pattern │
|
|
1335
|
+
│ └─ File may have been moved/deleted │
|
|
1336
|
+
│ │
|
|
1337
|
+
│ Code: FILE_DISCOVERY_FAILED │
|
|
1338
|
+
│ ├─ Check network connectivity │
|
|
1339
|
+
│ ├─ Test with ping/telnet │
|
|
1340
|
+
│ └─ Retry with exponential backoff │
|
|
1341
|
+
└─────────────────────────────────────────────┘
|
|
1342
|
+
|
|
1343
|
+
┌─────────────────────────────────────────────┐
|
|
1344
|
+
│ IngestionError (NETWORK_ERROR) │
|
|
1345
|
+
├─────────────────────────────────────────────┤
|
|
1346
|
+
│ Symptoms: Timeout, connection refused │
|
|
1347
|
+
│ ├─ ✅ RETRYABLE: Yes │
|
|
1348
|
+
│ ├─ Check network connectivity │
|
|
1349
|
+
│ ├─ Verify endpoint is reachable │
|
|
1350
|
+
│ └─ Retry with exponential backoff │
|
|
1351
|
+
└─────────────────────────────────────────────┘
|
|
1352
|
+
|
|
1353
|
+
┌─────────────────────────────────────────────┐
|
|
1354
|
+
│ FileParsingError │
|
|
1355
|
+
├─────────────────────────────────────────────┤
|
|
1356
|
+
│ Code: PARSE_ERROR │
|
|
1357
|
+
│ ├─ ❌ NOT RETRYABLE │
|
|
1358
|
+
│ ├─ Check file format (XML/JSON/CSV valid?) │
|
|
1359
|
+
│ ├─ Look at lineNumber in error │
|
|
1360
|
+
│ └─ Fix source data │
|
|
1361
|
+
│ │
|
|
1362
|
+
│ Code: ENCODING_ERROR │
|
|
1363
|
+
│ ├─ File may not be UTF-8 │
|
|
1364
|
+
│ ├─ Try alternate encodings │
|
|
1365
|
+
│ └─ Convert file to UTF-8 │
|
|
1366
|
+
└─────────────────────────────────────────────┘
|
|
1367
|
+
|
|
1368
|
+
┌─────────────────────────────────────────────┐
|
|
1369
|
+
│ Memory Error (heap out of memory) │
|
|
1370
|
+
├─────────────────────────────────────────────┤
|
|
1371
|
+
│ Symptoms: "JavaScript heap out of memory" │
|
|
1372
|
+
│ ├─ File too large for available memory │
|
|
1373
|
+
│ ├─ Solution 1: Increase heap size │
|
|
1374
|
+
│ │ node --max-old-space-size=4096 │
|
|
1375
|
+
│ ├─ Solution 2: Use chunked processing │
|
|
1376
|
+
│ │ Process file in 1MB chunks │
|
|
1377
|
+
│ └─ Solution 3: Stream instead of buffer │
|
|
1378
|
+
└─────────────────────────────────────────────┘
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
---
|
|
1382
|
+
|
|
1383
|
+
## Production Checklist
|
|
1384
|
+
|
|
1385
|
+
### Pre-Deployment
|
|
1386
|
+
|
|
1387
|
+
**Connection Setup:**
|
|
1388
|
+
- [ ] S3/SFTP credentials stored in environment variables (not hardcoded)
|
|
1389
|
+
- [ ] `validateConnection()` called before processing files
|
|
1390
|
+
- [ ] IAM policies grant minimum required permissions (least privilege)
|
|
1391
|
+
- [ ] SFTP uses SSH keys (not passwords)
|
|
1392
|
+
- [ ] Connection timeout configured appropriately
|
|
1393
|
+
|
|
1394
|
+
**Error Handling:**
|
|
1395
|
+
- [ ] All file operations wrapped in try-catch blocks
|
|
1396
|
+
- [ ] Errors logged with structured data (`error.toJSON()`)
|
|
1397
|
+
- [ ] Retry logic implemented for transient errors
|
|
1398
|
+
- [ ] Exponential backoff with jitter configured
|
|
1399
|
+
- [ ] Max retry limit set (typically 3)
|
|
1400
|
+
- [ ] Circuit breaker pattern for repeated failures
|
|
1401
|
+
|
|
1402
|
+
**File Processing:**
|
|
1403
|
+
- [ ] File existence checked before download
|
|
1404
|
+
- [ ] Large files processed in chunks (if > 100MB)
|
|
1405
|
+
- [ ] Memory limits configured (`--max-old-space-size`)
|
|
1406
|
+
- [ ] Parsed data validated before use
|
|
1407
|
+
- [ ] Failed files moved to error folder
|
|
1408
|
+
|
|
1409
|
+
**Monitoring:**
|
|
1410
|
+
- [ ] Structured logging enabled
|
|
1411
|
+
- [ ] Error rates tracked and alerted
|
|
1412
|
+
- [ ] File processing metrics collected (count, size, duration)
|
|
1413
|
+
- [ ] Presigned URL expiry monitored (S3)
|
|
1414
|
+
|
|
1415
|
+
### Post-Deployment
|
|
1416
|
+
|
|
1417
|
+
**Validation:**
|
|
1418
|
+
- [ ] Successful file processing confirmed
|
|
1419
|
+
- [ ] No duplicate processing detected
|
|
1420
|
+
- [ ] Error handling working as expected
|
|
1421
|
+
- [ ] Retry logic functioning correctly
|
|
1422
|
+
- [ ] Memory usage stable
|
|
1423
|
+
|
|
1424
|
+
**Monitoring:**
|
|
1425
|
+
- [ ] Logs being collected and searchable
|
|
1426
|
+
- [ ] Alerts triggering appropriately
|
|
1427
|
+
- [ ] Metrics dashboards created
|
|
1428
|
+
- [ ] On-call runbooks updated
|
|
1429
|
+
|
|
1430
|
+
---
|
|
1431
|
+
|
|
1432
|
+
## Quick Reference
|
|
1433
|
+
|
|
1434
|
+
### Error Type Decision Tree
|
|
1435
|
+
|
|
1436
|
+
```
|
|
1437
|
+
Does file operation fail?
|
|
1438
|
+
│
|
|
1439
|
+
├─ Connection/authentication fails
|
|
1440
|
+
│ → FileDiscoveryError (FILE_ACCESS_DENIED)
|
|
1441
|
+
│ → Check credentials, IAM policies, SSH keys
|
|
1442
|
+
│
|
|
1443
|
+
├─ File not found
|
|
1444
|
+
│ → FileDiscoveryError (FILE_NOT_FOUND)
|
|
1445
|
+
│ → Verify path, check file exists
|
|
1446
|
+
│
|
|
1447
|
+
├─ Network timeout
|
|
1448
|
+
│ → IngestionError (NETWORK_ERROR)
|
|
1449
|
+
│ → RETRY with exponential backoff
|
|
1450
|
+
│
|
|
1451
|
+
├─ File downloaded but parsing fails
|
|
1452
|
+
│ → FileParsingError (PARSE_ERROR)
|
|
1453
|
+
│ → DON'T RETRY - fix source data
|
|
1454
|
+
│
|
|
1455
|
+
└─ Out of memory
|
|
1456
|
+
→ Memory Error
|
|
1457
|
+
→ Increase heap or use chunked processing
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
### Common Error Codes
|
|
1461
|
+
|
|
1462
|
+
| Error Code | Retryable? | Action |
|
|
1463
|
+
|------------|------------|--------|
|
|
1464
|
+
| `FILE_ACCESS_DENIED` | ❌ No | Fix credentials/permissions |
|
|
1465
|
+
| `FILE_NOT_FOUND` | ❌ No | Verify file path |
|
|
1466
|
+
| `FILE_DISCOVERY_FAILED` | ✅ Yes | Retry with backoff |
|
|
1467
|
+
| `NETWORK_ERROR` | ✅ Yes | Retry with backoff |
|
|
1468
|
+
| `TIMEOUT_ERROR` | ✅ Yes | Retry with backoff |
|
|
1469
|
+
| `PARSE_ERROR` | ❌ No | Fix source data |
|
|
1470
|
+
| `ENCODING_ERROR` | ❌ No | Convert to UTF-8 |
|
|
1471
|
+
|
|
1472
|
+
---
|
|
1473
|
+
|
|
1474
|
+
## Related Documentation
|
|
1475
|
+
|
|
1476
|
+
- [Error Handling Guide](../../03-PATTERN-GUIDES/error-handling/error-handling-readme.md) - Core error handling patterns
|
|
1477
|
+
- [Data Sources Guide](./data-sources-readme.md) - S3/SFTP configuration and usage
|
|
1478
|
+
- [Parsers Guide](../parsers/) - CSV/XML/JSON/Parquet parsing details
|
|
1479
|
+
- [Troubleshooting Quick Reference](../../00-START-HERE/troubleshooting-quick-reference.md) - General debugging
|
|
1480
|
+
|
|
1481
|
+
---
|
|
1482
|
+
|
|
1483
|
+
**Need Help?**
|
|
1484
|
+
- 📖 Review [Complete Examples](#complete-examples)
|
|
1485
|
+
- 🔍 Check [Troubleshooting Flowchart](#troubleshooting-flowchart)
|
|
1486
|
+
- 📋 Use [Production Checklist](#production-checklist)
|
|
1487
|
+
- 🐛 Report issues at [GitHub](https://github.com/fluentcommerce/fc-connect-sdk/issues)
|