@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55

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.
Files changed (495) hide show
  1. package/CHANGELOG.md +30 -2
  2. package/README.md +39 -0
  3. package/dist/cjs/auth/index.d.ts +3 -0
  4. package/dist/cjs/auth/index.js +13 -0
  5. package/dist/cjs/auth/profile-loader.d.ts +18 -0
  6. package/dist/cjs/auth/profile-loader.js +208 -0
  7. package/dist/cjs/client-factory.d.ts +4 -0
  8. package/dist/cjs/client-factory.js +10 -0
  9. package/dist/cjs/clients/fluent-client.js +13 -6
  10. package/dist/cjs/index.d.ts +3 -1
  11. package/dist/cjs/index.js +8 -2
  12. package/dist/cjs/utils/pagination-helpers.js +38 -2
  13. package/dist/cjs/versori/fluent-versori-client.js +11 -5
  14. package/dist/esm/auth/index.d.ts +3 -0
  15. package/dist/esm/auth/index.js +2 -0
  16. package/dist/esm/auth/profile-loader.d.ts +18 -0
  17. package/dist/esm/auth/profile-loader.js +169 -0
  18. package/dist/esm/client-factory.d.ts +4 -0
  19. package/dist/esm/client-factory.js +9 -0
  20. package/dist/esm/clients/fluent-client.js +13 -6
  21. package/dist/esm/index.d.ts +3 -1
  22. package/dist/esm/index.js +2 -1
  23. package/dist/esm/utils/pagination-helpers.js +38 -2
  24. package/dist/esm/versori/fluent-versori-client.js +11 -5
  25. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/tsconfig.types.tsbuildinfo +1 -1
  28. package/dist/types/auth/index.d.ts +3 -0
  29. package/dist/types/auth/profile-loader.d.ts +18 -0
  30. package/dist/types/client-factory.d.ts +4 -0
  31. package/dist/types/index.d.ts +3 -1
  32. package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
  33. package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
  34. package/docs/00-START-HERE/cli-documentation-index.md +202 -202
  35. package/docs/00-START-HERE/cli-quick-reference.md +252 -252
  36. package/docs/00-START-HERE/decision-tree.md +552 -552
  37. package/docs/00-START-HERE/getting-started.md +1070 -1070
  38. package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
  39. package/docs/00-START-HERE/readme.md +237 -237
  40. package/docs/00-START-HERE/retailerid-configuration.md +404 -404
  41. package/docs/00-START-HERE/sdk-philosophy.md +794 -794
  42. package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
  43. package/docs/01-TEMPLATES/faq.md +686 -686
  44. package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
  45. package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
  46. package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
  47. package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
  48. package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
  49. package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
  50. package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
  51. package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
  52. package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
  53. package/docs/01-TEMPLATES/readme.md +957 -957
  54. package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
  55. package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
  56. package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
  57. package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
  58. package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
  59. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
  60. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
  61. package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
  62. package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
  63. package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
  64. package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
  65. package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
  66. package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
  67. package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
  68. package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
  69. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
  70. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
  71. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
  72. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
  73. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
  74. package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
  75. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
  76. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
  77. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
  78. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
  79. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
  80. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
  81. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
  82. package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
  83. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
  84. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
  85. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
  86. package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
  87. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
  88. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
  89. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
  90. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
  91. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
  92. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
  93. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
  94. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
  95. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
  96. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
  97. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
  98. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
  99. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
  100. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
  101. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
  102. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
  103. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
  104. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
  105. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
  106. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
  107. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
  108. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
  109. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
  110. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
  111. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
  112. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
  113. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
  114. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
  115. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
  116. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
  117. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
  118. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
  119. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
  120. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
  121. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
  122. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
  123. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
  124. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
  125. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
  126. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
  127. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
  128. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
  129. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
  130. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
  131. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
  132. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
  133. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
  134. package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
  135. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
  136. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
  137. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
  138. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
  139. package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
  140. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
  141. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
  142. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
  143. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
  144. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
  145. package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
  146. package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
  147. package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
  148. package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
  149. package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
  150. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -482
  151. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
  152. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
  153. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
  154. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
  155. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
  156. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
  157. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
  158. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
  159. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
  160. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
  161. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
  162. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
  163. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
  164. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
  165. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
  166. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
  167. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
  168. package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
  169. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
  170. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
  171. package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
  172. package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
  173. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
  174. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
  175. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
  176. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
  177. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
  178. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
  179. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
  180. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
  181. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
  182. package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
  183. package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
  184. package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
  185. package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
  186. package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
  187. package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
  188. package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
  189. package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
  190. package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
  191. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
  192. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
  193. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
  194. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
  195. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
  196. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
  197. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
  198. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
  199. package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
  200. package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
  201. package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
  202. package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
  203. package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
  204. package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
  205. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
  206. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
  207. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
  208. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
  209. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
  210. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
  211. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
  212. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
  213. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
  214. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
  215. package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
  216. package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
  217. package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
  218. package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
  219. package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
  220. package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
  221. package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
  222. package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
  223. package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
  224. package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
  225. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
  226. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
  227. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
  228. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
  229. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
  230. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
  231. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
  232. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
  233. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
  234. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
  235. package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
  236. package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
  237. package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
  238. package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
  239. package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
  240. package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
  241. package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
  242. package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
  243. package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
  244. package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
  245. package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
  246. package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
  247. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
  248. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
  249. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
  250. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
  251. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
  252. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
  253. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
  254. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
  255. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
  256. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
  257. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
  258. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
  259. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
  260. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
  261. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
  262. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
  263. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
  264. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
  265. package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
  266. package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
  267. package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
  268. package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
  269. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
  270. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
  271. package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
  272. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
  273. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
  274. package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
  275. package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
  276. package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
  277. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
  278. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
  279. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
  280. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
  281. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
  282. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
  283. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
  284. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
  285. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
  286. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
  287. package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
  288. package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
  289. package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
  290. package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
  291. package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
  292. package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
  293. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
  294. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
  295. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
  296. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
  297. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
  298. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
  299. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
  300. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
  301. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
  302. package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
  303. package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
  304. package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
  305. package/docs/02-CORE-GUIDES/readme.md +194 -194
  306. package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
  307. package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
  308. package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
  309. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
  310. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
  311. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
  312. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
  313. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
  314. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
  315. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
  316. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
  317. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
  318. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
  319. package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
  320. package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
  321. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
  322. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
  323. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
  324. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
  325. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
  326. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
  327. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
  328. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
  329. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
  330. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
  331. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
  332. package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
  333. package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
  334. package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
  335. package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
  336. package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
  337. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
  338. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
  339. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
  340. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
  341. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
  342. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
  343. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
  344. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
  345. package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
  346. package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
  347. package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
  348. package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
  349. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
  350. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
  351. package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
  352. package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
  353. package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
  354. package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
  355. package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
  356. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
  357. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
  358. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
  359. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
  360. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
  361. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
  362. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
  363. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
  364. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
  365. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
  366. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
  367. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
  368. package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
  369. package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
  370. package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
  371. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
  372. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
  373. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
  374. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
  375. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
  376. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
  377. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
  378. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
  379. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
  380. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
  381. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
  382. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
  383. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
  384. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
  385. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
  386. package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
  387. package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
  388. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
  389. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
  390. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
  391. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
  392. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
  393. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
  394. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
  395. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
  396. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
  397. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
  398. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
  399. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
  400. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
  401. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
  402. package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
  403. package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
  404. package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
  405. package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
  406. package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
  407. package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
  408. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
  409. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
  410. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
  411. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
  412. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
  413. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
  414. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
  415. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
  416. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
  417. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
  418. package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
  419. package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
  420. package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
  421. package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
  422. package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
  423. package/docs/03-PATTERN-GUIDES/readme.md +159 -159
  424. package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
  425. package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
  426. package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
  427. package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
  428. package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
  429. package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
  430. package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
  431. package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
  432. package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
  433. package/docs/04-REFERENCE/architecture/readme.md +279 -279
  434. package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
  435. package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
  436. package/docs/04-REFERENCE/platforms/readme.md +135 -135
  437. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
  438. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
  439. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
  440. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
  441. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
  442. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
  443. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
  444. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
  445. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
  446. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
  447. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
  448. package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
  449. package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
  450. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
  451. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
  452. package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
  453. package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
  454. package/docs/04-REFERENCE/readme.md +148 -148
  455. package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
  456. package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
  457. package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
  458. package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
  459. package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
  460. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
  461. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
  462. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
  463. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
  464. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
  465. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
  466. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
  467. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
  468. package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
  469. package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
  470. package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
  471. package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
  472. package/docs/04-REFERENCE/schema/readme.md +141 -141
  473. package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
  474. package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
  475. package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
  476. package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
  477. package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
  478. package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
  479. package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
  480. package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
  481. package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
  482. package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
  483. package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
  484. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
  485. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
  486. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
  487. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
  488. package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
  489. package/docs/04-REFERENCE/testing/readme.md +86 -86
  490. package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
  491. package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
  492. package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
  493. package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
  494. package/docs/template-loading-matrix.md +242 -242
  495. package/package.json +5 -3
@@ -1,1893 +1,1893 @@
1
- # Module 9: Best Practices
2
-
3
- [← Back to Ingestion Guide](../ingestion-readme.md)
4
-
5
- **Module 9 of 9** | **Level**: All Levels | **Time**: 30 minutes
6
-
7
- ---
8
-
9
- ## Overview
10
-
11
- This module covers production-ready patterns for data ingestion workflows. Learn comprehensive error handling strategies, monitoring approaches, security best practices, scheduling patterns, data quality validation, and complete production deployment patterns.
12
-
13
- New in this version:
14
-
15
- - Preflight validation before runs (PreflightValidator)
16
- - Job lifecycle tracking (JobTracker)
17
- - Partial batch failure recovery (PartialBatchRecovery)
18
- - Versori file-level deduplication (VersoriFileTracker)
19
-
20
- ## Learning Objectives
21
-
22
- By the end of this module, you will:
23
-
24
- - ✅ Implement robust error handling with retry logic and circuit breakers
25
- - ✅ Set up comprehensive monitoring, logging, and alerting
26
- - ✅ Apply security best practices for credentials and data
27
- - ✅ Design effective scheduling and trigger strategies
28
- - ✅ Validate data quality before ingestion
29
- - ✅ Build production-ready ingestion pipelines
30
- - ✅ Troubleshoot common issues effectively
31
- - ✅ Follow deployment checklists for success
32
-
33
- ---
34
-
35
- ## Error Handling
36
-
37
- ### Retry Strategy with Exponential Backoff
38
-
39
- ```typescript
40
- async function retryWithBackoff<T>(
41
- operation: () => Promise<T>,
42
- maxRetries: number = 3
43
- ): Promise<T> {
44
- let lastError: Error;
45
- let delay = 1000; // Start with 1 second
46
-
47
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
48
- try {
49
- return await operation();
50
- } catch (error) {
51
- lastError = error;
52
-
53
- if (!isRetryable(error) || attempt === maxRetries) {
54
- throw error;
55
- }
56
-
57
- console.log(\`Retry \${attempt}/\${maxRetries} after \${delay}ms\`);
58
- await sleep(delay);
59
- delay *= 2; // Exponential backoff
60
- }
61
- }
62
-
63
- throw lastError;
64
- }
65
-
66
- function isRetryable(error: any): boolean {
67
- const retryableCodes = [
68
- 'RATE_LIMIT_ERROR',
69
- 'NETWORK_ERROR',
70
- 'TIMEOUT_ERROR',
71
- 'SERVICE_UNAVAILABLE'
72
- ];
73
-
74
- return (
75
- retryableCodes.includes(error.code) ||
76
- error.message.includes('timeout') ||
77
- error.message.includes('ECONNRESET')
78
- );
79
- }
80
- ```
81
-
82
- ### SDK-Specific Error Handling
83
-
84
- The SDK throws specific error types that require different handling strategies:
85
-
86
- ```typescript
87
- import {
88
- FileParsingError,
89
- MappingError,
90
- BatchAPIError,
91
- StateError,
92
- createConsoleLogger,
93
- toStructuredLogger,
94
- } from '@fluentcommerce/fc-connect-sdk';
95
-
96
- async function handleSDKErrors(fileKey: string) {
97
- try {
98
- await processFile(fileKey);
99
- } catch (error) {
100
- if (error instanceof FileParsingError) {
101
- // File format issues - log and skip
102
- console.error(`Invalid file format: ${fileKey}`, {
103
- line: error.line,
104
- column: error.column,
105
- message: error.message,
106
- });
107
-
108
- // Move to error bucket for manual review
109
- await moveToErrorBucket(fileKey, error);
110
- return { status: 'skipped', reason: 'parsing_error' };
111
- } else if (error instanceof MappingError) {
112
- // Field mapping failures - log details
113
- console.error(`Field mapping failed: ${fileKey}`, {
114
- invalidFields: error.invalidFields,
115
- recordIndex: error.recordIndex,
116
- errors: error.errors,
117
- });
118
-
119
- // Save error report
120
- await saveErrorReport(fileKey, error);
121
- return { status: 'failed', reason: 'mapping_error' };
122
- } else if (error instanceof BatchAPIError) {
123
- // Batch API rejections - check if retryable
124
- if (error.isRetryable) {
125
- console.warn(`Retryable batch error: ${error.code}`);
126
- throw error; // Will be caught by retry logic
127
- } else {
128
- console.error(`Permanent batch error: ${error.code}`, {
129
- jobId: error.jobId,
130
- batchId: error.batchId,
131
- details: error.details,
132
- });
133
-
134
- // Save to dead letter queue
135
- await saveToDLQ(fileKey, error);
136
- return { status: 'failed', reason: 'batch_api_error' };
137
- }
138
- } else if (error instanceof StateError) {
139
- // State management issues - critical
140
- console.error(`State management error: ${error.message}`);
141
-
142
- // Alert operations team
143
- await sendAlert('CRITICAL: State management failure', error);
144
- throw error; // Don't continue if state is broken
145
- } else {
146
- // Unknown error - log and alert
147
- console.error(`Unexpected error processing ${fileKey}:`, error);
148
- await sendAlert('Unknown ingestion error', error);
149
- throw error;
150
- }
151
- }
152
- }
153
- ```
154
-
155
- ### Circuit Breaker Pattern
156
-
157
- Prevent cascading failures when external services are down:
158
-
159
- ```typescript
160
- interface CircuitBreakerConfig {
161
- failureThreshold: number;
162
- resetTimeoutMs: number;
163
- monitoringWindowMs: number;
164
- }
165
-
166
- enum CircuitState {
167
- CLOSED = 'CLOSED', // Normal operation
168
- OPEN = 'OPEN', // Blocking requests
169
- HALF_OPEN = 'HALF_OPEN', // Testing recovery
170
- }
171
-
172
- class CircuitBreaker {
173
- private state: CircuitState = CircuitState.CLOSED;
174
- private failureCount: number = 0;
175
- private lastFailureTime: number = 0;
176
- private failures: number[] = [];
177
-
178
- constructor(private config: CircuitBreakerConfig) {}
179
-
180
- async execute<T>(operation: () => Promise<T>): Promise<T> {
181
- // Check if circuit should reset
182
- if (this.shouldAttemptReset()) {
183
- this.state = CircuitState.HALF_OPEN;
184
- }
185
-
186
- // Block if circuit is open
187
- if (this.state === CircuitState.OPEN) {
188
- throw new Error(
189
- `Circuit breaker OPEN. Last failure: ${new Date(this.lastFailureTime).toISOString()}`
190
- );
191
- }
192
-
193
- try {
194
- const result = await operation();
195
-
196
- // Success - reset if in half-open state
197
- if (this.state === CircuitState.HALF_OPEN) {
198
- console.log('Circuit breaker: Service recovered, closing circuit');
199
- this.reset();
200
- }
201
-
202
- return result;
203
- } catch (error) {
204
- this.recordFailure();
205
-
206
- // Open circuit if threshold exceeded
207
- if (this.failureCount >= this.config.failureThreshold) {
208
- this.state = CircuitState.OPEN;
209
- this.lastFailureTime = Date.now();
210
- console.error(`Circuit breaker OPENED after ${this.failureCount} failures`);
211
- }
212
-
213
- throw error;
214
- }
215
- }
216
-
217
- private recordFailure(): void {
218
- const now = Date.now();
219
- this.failures.push(now);
220
-
221
- // Remove old failures outside monitoring window
222
- this.failures = this.failures.filter(time => now - time < this.config.monitoringWindowMs);
223
-
224
- this.failureCount = this.failures.length;
225
- }
226
-
227
- private shouldAttemptReset(): boolean {
228
- if (this.state !== CircuitState.OPEN) {
229
- return false;
230
- }
231
-
232
- const timeSinceLastFailure = Date.now() - this.lastFailureTime;
233
- return timeSinceLastFailure >= this.config.resetTimeoutMs;
234
- }
235
-
236
- private reset(): void {
237
- this.state = CircuitState.CLOSED;
238
- this.failureCount = 0;
239
- this.failures = [];
240
- }
241
-
242
- getState(): CircuitState {
243
- return this.state;
244
- }
245
- }
246
-
247
- // Usage
248
- const fluentAPICircuit = new CircuitBreaker({
249
- failureThreshold: 5, // Open after 5 failures
250
- resetTimeoutMs: 60000, // Try again after 1 minute
251
- monitoringWindowMs: 120000, // Track failures over 2 minutes
252
- });
253
-
254
- async function processWithCircuitBreaker(fileKey: string) {
255
- try {
256
- await fluentAPICircuit.execute(async () => {
257
- return await processFile(fileKey);
258
- });
259
- } catch (error) {
260
- if (error.message.includes('Circuit breaker OPEN')) {
261
- console.log('Service temporarily unavailable, requeueing file');
262
- await requeueFile(fileKey);
263
- } else {
264
- throw error;
265
- }
266
- }
267
- }
268
- ```
269
-
270
- ### Dead Letter Queue Pattern
271
-
272
- Store failed records for later processing:
273
-
274
- ```typescript
275
- import { S3DataSource } from '@fluentcommerce/fc-connect-sdk';
276
-
277
- interface DLQRecord {
278
- originalFile: string;
279
- failedAt: string;
280
- errorType: string;
281
- errorMessage: string;
282
- recordData: any;
283
- retryCount: number;
284
- metadata: Record<string, any>;
285
- }
286
-
287
- class DeadLetterQueue {
288
- constructor(
289
- private s3: S3DataSource,
290
- private dlqBucket: string,
291
- private dlqPrefix: string = 'dlq/'
292
- ) {}
293
-
294
- /**
295
- * Save failed record to DLQ
296
- */
297
- async save(record: DLQRecord): Promise<void> {
298
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
299
- const key = `${this.dlqPrefix}${record.errorType}/${timestamp}-${record.originalFile}`;
300
-
301
- await this.s3.uploadFile(key, JSON.stringify(record, null, 2));
302
-
303
- console.log(`Saved to DLQ: ${key}`);
304
- }
305
-
306
- /**
307
- * Retry processing records from DLQ
308
- */
309
- async retryAll(
310
- processor: (record: any) => Promise<void>
311
- ): Promise<{ successful: number; failed: number }> {
312
- const files = await this.s3.listFiles({ prefix: this.dlqPrefix });
313
- let successful = 0;
314
- let failed = 0;
315
-
316
- for (const file of files) {
317
- try {
318
- const content = await this.s3.downloadFile(file.path);
319
- const dlqRecord: DLQRecord = JSON.parse(content);
320
-
321
- // Attempt reprocessing
322
- await processor(dlqRecord.recordData);
323
-
324
- // Success - delete from DLQ (Note: deleteObject method may not exist - use uploadFile with empty content or check SDK)
325
- // await this.s3.deleteObject(this.dlqBucket, file.path);
326
- successful++;
327
- } catch (error) {
328
- console.error(`DLQ retry failed for ${file.path}:`, error);
329
- failed++;
330
-
331
- // Update retry count
332
- await this.incrementRetryCount(file.path);
333
- }
334
- }
335
-
336
- return { successful, failed };
337
- }
338
-
339
- /**
340
- * Increment retry count in DLQ record
341
- */
342
- private async incrementRetryCount(key: string): Promise<void> {
343
- try {
344
- const content = await this.s3.downloadFile(key);
345
- const record: DLQRecord = JSON.parse(content);
346
-
347
- record.retryCount = (record.retryCount || 0) + 1;
348
- record.metadata.lastRetryAttempt = new Date().toISOString();
349
-
350
- await this.s3.uploadFile(key, JSON.stringify(record, null, 2));
351
- } catch (error) {
352
- console.error(`Failed to update retry count for ${key}:`, error);
353
- }
354
- }
355
- }
356
-
357
- // Usage
358
- const dlq = new DeadLetterQueue(s3DataSource, 'my-dlq-bucket');
359
-
360
- async function processFileWithDLQ(fileKey: string) {
361
- try {
362
- const records = await parseFile(fileKey);
363
-
364
- for (const [index, record] of records.entries()) {
365
- try {
366
- await processRecord(record);
367
- } catch (error) {
368
- // Save failed record to DLQ
369
- await dlq.save({
370
- originalFile: fileKey,
371
- failedAt: new Date().toISOString(),
372
- errorType: error.constructor.name,
373
- errorMessage: error.message,
374
- recordData: record,
375
- retryCount: 0,
376
- metadata: {
377
- recordIndex: index,
378
- totalRecords: records.length,
379
- },
380
- });
381
- }
382
- }
383
- } catch (error) {
384
- console.error(`Complete file failure: ${fileKey}`, error);
385
- throw error;
386
- }
387
- }
388
- ```
389
-
390
- ### Graceful Degradation
391
-
392
- ```typescript
393
- async function processFileWithFallback(fileKey: string) {
394
- try {
395
- // Primary processing path
396
- await processFileNormally(fileKey);
397
- } catch (error) {
398
- console.error(\`Primary processing failed: \${error.message}\`);
399
-
400
- try {
401
- // Fallback: Process with reduced batch size
402
- await processFileWithSmallerBatches(fileKey);
403
- } catch (fallbackError) {
404
- console.error(\`Fallback processing failed: \${fallbackError.message}\`);
405
-
406
- // Last resort: Save to dead letter queue
407
- await saveToDeadLetterQueue(fileKey, error);
408
- throw new Error(\`File processing failed completely: \${fileKey}\`);
409
- }
410
- }
411
- }
412
- ```
413
-
414
- ## Validation Best Practices
415
-
416
- ### Pre-Flight Validation
417
-
418
- ```typescript
419
- async function validateBeforeIngestion(records: any[]): Promise<ValidationResult> {
420
- const errors = [];
421
- const warnings = [];
422
-
423
- // Schema validation
424
- for (const [index, record] of records.entries()) {
425
- // Required fields
426
- if (!record.ref) {
427
- errors.push(\`Row \${index}: Missing required field 'ref'\`);
428
- }
429
-
430
- if (!record.productRef) {
431
- errors.push(\`Row \${index}: Missing required field 'productRef'\`);
432
- }
433
-
434
- if (!record.locationRef) {
435
- errors.push(\`Row \${index}: Missing required field 'locationRef'\`);
436
- }
437
-
438
- if (record.qty === undefined || record.qty === null) {
439
- errors.push(\`Row \${index}: Missing required field 'qty'\`);
440
- }
441
-
442
- // Type validation
443
- if (typeof record.qty !== 'number') {
444
- errors.push(\`Row \${index}: 'qty' must be a number\`);
445
- }
446
-
447
- // Business rules
448
- if (record.qty < 0) {
449
- errors.push(\`Row \${index}: Quantity cannot be negative\`);
450
- }
451
-
452
- if (record.qty > 1000000) {
453
- warnings.push(\`Row \${index}: Unusually large quantity\`);
454
- }
455
- }
456
-
457
- return {
458
- isValid: errors.length === 0,
459
- errors,
460
- warnings
461
- };
462
- }
463
-
464
- // Usage
465
- const validation = await validateBeforeIngestion(records);
466
-
467
- if (!validation.isValid) {
468
- console.error('Validation errors:', validation.errors);
469
- throw new Error('Data validation failed');
470
- }
471
-
472
- if (validation.warnings.length > 0) {
473
- console.warn('Validation warnings:', validation.warnings);
474
- }
475
- ```
476
-
477
- ## Scheduling and Triggers
478
-
479
- ###Cron Scheduling Patterns
480
-
481
- Different scheduling strategies for different ingestion scenarios:
482
-
483
- ```typescript
484
- // Daily midnight sync (00:00 UTC)
485
- const dailyMidnightCron = '0 0 * * *';
486
-
487
- // Every 6 hours
488
- const every6HoursCron = '0 */6 * * *';
489
-
490
- // Every hour during business hours (9 AM - 5 PM, Mon-Fri)
491
- const businessHoursCron = '0 9-17 * * 1-5';
492
-
493
- // Every 15 minutes
494
- const every15MinutesCron = '*/15 * * * *';
495
-
496
- // Weekly Sunday at 2 AM
497
- const weeklySundayCron = '0 2 * * 0';
498
- ```
499
-
500
- **Recommended schedules by use case:**
501
-
502
- | Use Case | Schedule | Cron Expression | Rationale |
503
- | ----------------------- | ------------- | ---------------- | ---------------------------- |
504
- | **WMS Daily Sync** | Once daily | `0 2 * * *` | Process overnight updates |
505
- | **3PL Cycle Counts** | Every 6 hours | `0 */6 * * *` | Keep inventory fresh |
506
- | **Real-time Updates** | Every 15 min | `*/15 * * * *` | Near real-time sync |
507
- | **Weekly Full Sync** | Sunday 2 AM | `0 2 * * 0` | Comprehensive reconciliation |
508
- | **Business Hours Only** | 9 AM - 5 PM | `0 9-17 * * 1-5` | During operational hours |
509
-
510
- ### Trigger Mechanisms
511
-
512
- Different ways to trigger ingestion workflows:
513
-
514
- #### 1. S3 Event Triggers
515
-
516
- ```typescript
517
- // Versori workflow triggered by S3 event
518
- export const s3TriggeredIngestion = webhook('s3-inventory-upload', {
519
- response: { mode: 'sync' },
520
- })
521
- .then(
522
- fn('parse-s3-event', async ({ data }) => {
523
- const event = JSON.parse(data);
524
- const bucket = event.Records[0].s3.bucket.name;
525
- const key = decodeURIComponent(event.Records[0].s3.object.key);
526
-
527
- return { bucket, key };
528
- })
529
- )
530
- .then(
531
- fn('process-file', async ({ data, connections, log, openKv }) => {
532
- const client = await createClient({ connections, log, openKv });
533
-
534
- // Initialize logger and state management
535
- const logger = toStructuredLogger(log, {
536
- service: 'ingestion',
537
- correlationId: generateCorrelationId()
538
- });
539
-
540
- const stateService = new StateService(logger);
541
- const kvAdapter = new VersoriKVAdapter(openKv());
542
-
543
- const s3 = new S3DataSource(
544
- {
545
- type: 'S3_CSV',
546
- connectionId: 's3-triggered',
547
- name: 'S3 Triggered Ingestion',
548
- s3Config: { region: 'us-east-1', bucket: data.bucket },
549
- },
550
- logger
551
- );
552
-
553
- // Check if already processed
554
- if (await stateService.isFileProcessed(kvAdapter, data.key)) {
555
- log.info(`File already processed: ${data.key}`);
556
- return { status: 'skipped', reason: 'already_processed' };
557
- }
558
-
559
- // Process file
560
- await processInventoryFile(client, s3, data.bucket, data.key);
561
-
562
- // Mark as processed
563
- await stateService.updateSyncState(kvAdapter, [{
564
- fileName: data.key,
565
- lastModified: new Date().toISOString(),
566
- }]);
567
-
568
- return { status: 'success', file: data.key };
569
- })
570
- )
571
- .catch(({ data, log }) => {
572
- log.error('S3 triggered ingestion failed:', data);
573
- return { status: 'error', error: data };
574
- });
575
- ```
576
-
577
- #### 2. SFTP Polling
578
-
579
- ```typescript
580
- import { SftpDataSource, StateService, VersoriKVAdapter, createConsoleLogger, toStructuredLogger } from '@fluentcommerce/fc-connect-sdk';
581
-
582
- async function pollSftpDirectory(kv: any) {
583
- const logger = toStructuredLogger(createConsoleLogger(), {
584
- service: 'ingestion',
585
- correlationId: generateCorrelationId()
586
- });
587
-
588
- const stateService = new StateService(logger);
589
- const kvAdapter = new VersoriKVAdapter(kv);
590
-
591
- const sftp = new SftpDataSource({
592
- type: 'SFTP_CSV',
593
- connectionId: 'sftp-polling',
594
- name: 'SFTP Polling',
595
- settings: {
596
- host: process.env.SFTP_HOST!,
597
- port: 22,
598
- username: process.env.SFTP_USER!,
599
- privateKey: fs.readFileSync('/path/to/private/key'),
600
- }
601
- }, logger);
602
-
603
- const remoteDir = '/inbound/inventory/';
604
- const files = await sftp.listFiles(remoteDir);
605
-
606
- for (const file of files) {
607
- if (await stateService.isFileProcessed(kvAdapter, file.name)) {
608
- continue; // Skip already processed
609
- }
610
-
611
- // Download and process
612
- const localPath = `/tmp/${file.name}`;
613
- await sftp.downloadFile(file.path, localPath); // Use file.path (full path) for download
614
-
615
- await processLocalFile(localPath);
616
-
617
- // Mark as processed
618
- await stateService.updateSyncState(kvAdapter, [{
619
- fileName: file.name,
620
- lastModified: new Date().toISOString(),
621
- }]);
622
-
623
- // Optionally move to processed folder
624
- await sftp.moveFile(`${remoteDir}${file.name}`, `/processed/${file.name}`);
625
- }
626
- }
627
- ```
628
-
629
- #### 3. Webhook Triggers
630
-
631
- ```typescript
632
- // Manual or external system triggered ingestion
633
- export const manualIngestion = webhook('manual-trigger', {
634
- response: { mode: 'sync' },
635
- })
636
- .then(
637
- fn('validate-request', ({ data }) => {
638
- const { bucket, prefix, batchSize = 2000 } = JSON.parse(data);
639
-
640
- if (!bucket || !prefix) {
641
- throw new Error('Missing required parameters: bucket, prefix');
642
- }
643
-
644
- return { bucket, prefix, batchSize };
645
- })
646
- )
647
- .then(
648
- fn('run-ingestion', async ({ data, connections, log }) => {
649
- const client = await createClient(ctx); // Auto-detects Versori context
650
- const s3 = new S3DataSource(
651
- {
652
- type: 'S3_CSV',
653
- s3Config: { region: 'us-east-1' },
654
- },
655
- log
656
- );
657
-
658
- const files = await s3.listFiles({ prefix: data.prefix });
659
-
660
- log.info(`Found ${files.length} files to process`);
661
-
662
- const results = await processFilesInParallel(client, s3, data.bucket, files, {
663
- batchSize: data.batchSize,
664
- });
665
-
666
- return {
667
- filesProcessed: results.successful,
668
- filesFailed: results.failed,
669
- totalRecords: results.totalRecords,
670
- };
671
- })
672
- );
673
- ```
674
-
675
- ### Backfill Strategies
676
-
677
- Handle missed files or catch-up processing:
678
-
679
- ```typescript
680
- async function backfillMissedFiles(startDate: Date, endDate: Date, kv: any): Promise<void> {
681
- const logger = toStructuredLogger(createConsoleLogger(), {
682
- service: 'backfill',
683
- correlationId: generateCorrelationId()
684
- });
685
-
686
- const stateService = new StateService(logger);
687
- const kvAdapter = new VersoriKVAdapter(kv);
688
-
689
- const s3 = new S3DataSource(
690
- {
691
- type: 'S3_CSV',
692
- connectionId: 's3-backfill',
693
- name: 'S3 Backfill',
694
- s3Config: s3Config,
695
- },
696
- logger
697
- );
698
-
699
- console.log(`Backfilling files from ${startDate} to ${endDate}`);
700
-
701
- // List all files in date range
702
- const allFiles = await s3.listFiles({ prefix: 'data/' });
703
-
704
- const filesInRange = allFiles.filter(file => {
705
- const fileDate = extractDateFromKey(file.path);
706
- return fileDate >= startDate && fileDate <= endDate;
707
- });
708
-
709
- console.log(`Found ${filesInRange.length} files in date range`);
710
-
711
- // Check processing status
712
- const unprocessedFiles = [];
713
- for (const file of filesInRange) {
714
- if (!(await stateService.isFileProcessed(kvAdapter, file.path))) {
715
- unprocessedFiles.push(file);
716
- }
717
- }
718
-
719
- console.log(`${unprocessedFiles.length} files need processing`);
720
-
721
- // Process with lower concurrency to avoid overwhelming system
722
- await processFilesInParallel(unprocessedFiles, 2); // Only 2 concurrent
723
- }
724
-
725
- function extractDateFromKey(key: string): Date {
726
- // Extract date from key like "data/inventory-2025-01-15.csv"
727
- const match = key.match(/(\d{4}-\d{2}-\d{2})/);
728
- return match ? new Date(match[1]) : new Date(0);
729
- }
730
- ```
731
-
732
- ## Data Quality and Validation
733
-
734
- ### Pre-Ingestion Data Quality Checks
735
-
736
- ```typescript
737
- interface DataQualityRule {
738
- name: string;
739
- validate: (record: any) => { isValid: boolean; error?: string };
740
- severity: 'error' | 'warning';
741
- }
742
-
743
- class DataQualityValidator {
744
- private rules: DataQualityRule[] = [];
745
-
746
- addRule(rule: DataQualityRule): void {
747
- this.rules.push(rule);
748
- }
749
-
750
- validate(records: any[]): {
751
- passed: any[];
752
- failed: Array<{ record: any; errors: string[] }>;
753
- warnings: Array<{ record: any; warnings: string[] }>;
754
- } {
755
- const passed: any[] = [];
756
- const failed: Array<{ record: any; errors: string[] }> = [];
757
- const warnings: Array<{ record: any; warnings: string[] }> = [];
758
-
759
- for (const record of records) {
760
- const errors: string[] = [];
761
- const warns: string[] = [];
762
-
763
- for (const rule of this.rules) {
764
- const result = rule.validate(record);
765
-
766
- if (!result.isValid) {
767
- if (rule.severity === 'error') {
768
- errors.push(`${rule.name}: ${result.error}`);
769
- } else {
770
- warns.push(`${rule.name}: ${result.error}`);
771
- }
772
- }
773
- }
774
-
775
- if (errors.length > 0) {
776
- failed.push({ record, errors });
777
- } else {
778
- passed.push(record);
779
-
780
- if (warns.length > 0) {
781
- warnings.push({ record, warnings: warns });
782
- }
783
- }
784
- }
785
-
786
- return { passed, failed, warnings };
787
- }
788
- }
789
-
790
- // Define quality rules
791
- const qualityValidator = new DataQualityValidator();
792
-
793
- qualityValidator.addRule({
794
- name: 'Required Fields',
795
- severity: 'error',
796
- validate: record => {
797
- const required = ['sku', 'warehouse', 'quantity'];
798
- const missing = required.filter(field => !record[field]);
799
-
800
- if (missing.length > 0) {
801
- return {
802
- isValid: false,
803
- error: `Missing required fields: ${missing.join(', ')}`,
804
- };
805
- }
806
-
807
- return { isValid: true };
808
- },
809
- });
810
-
811
- qualityValidator.addRule({
812
- name: 'Quantity Range',
813
- severity: 'error',
814
- validate: record => {
815
- const qty = parseInt(record.quantity, 10);
816
-
817
- if (isNaN(qty) || qty < 0) {
818
- return {
819
- isValid: false,
820
- error: `Invalid quantity: ${record.quantity}`,
821
- };
822
- }
823
-
824
- return { isValid: true };
825
- },
826
- });
827
-
828
- qualityValidator.addRule({
829
- name: 'SKU Format',
830
- severity: 'error',
831
- validate: record => {
832
- const skuPattern = /^SKU-[A-Z0-9]{2,10}$/;
833
-
834
- if (!skuPattern.test(record.sku)) {
835
- return {
836
- isValid: false,
837
- error: `Invalid SKU format: ${record.sku}`,
838
- };
839
- }
840
-
841
- return { isValid: true };
842
- },
843
- });
844
-
845
- qualityValidator.addRule({
846
- name: 'Suspiciously Large Quantity',
847
- severity: 'warning',
848
- validate: record => {
849
- const qty = parseInt(record.quantity, 10);
850
-
851
- if (qty > 100000) {
852
- return {
853
- isValid: false,
854
- error: `Unusually large quantity: ${qty}`,
855
- };
856
- }
857
-
858
- return { isValid: true };
859
- },
860
- });
861
-
862
- // Usage
863
- const records = await parseCSV(fileContent);
864
- const validation = qualityValidator.validate(records);
865
-
866
- console.log(`Passed: ${validation.passed.length}`);
867
- console.log(`Failed: ${validation.failed.length}`);
868
- console.log(`Warnings: ${validation.warnings.length}`);
869
-
870
- // Log failures
871
- if (validation.failed.length > 0) {
872
- console.error('Data quality failures:');
873
- validation.failed.forEach(({ record, errors }) => {
874
- console.error(` SKU ${record.sku}:`, errors);
875
- });
876
-
877
- // Save failed records for review
878
- await saveDataQualityReport(validation.failed);
879
- }
880
-
881
- // Proceed with passed records only
882
- await processRecords(validation.passed);
883
- ```
884
-
885
- ### Reconciliation Reports
886
-
887
- ```typescript
888
- interface ReconciliationReport {
889
- fileKey: string;
890
- processedAt: string;
891
- totalRecords: number;
892
- successfulRecords: number;
893
- failedRecords: number;
894
- duplicateRecords: number;
895
- dataQualityIssues: number;
896
- batchesCreated: number;
897
- processingTimeMs: number;
898
- }
899
-
900
- async function generateReconciliationReport(
901
- fileKey: string,
902
- results: ProcessingResult
903
- ): Promise<ReconciliationReport> {
904
- return {
905
- fileKey,
906
- processedAt: new Date().toISOString(),
907
- totalRecords: results.totalRecords,
908
- successfulRecords: results.successful,
909
- failedRecords: results.failed,
910
- duplicateRecords: results.duplicates || 0,
911
- dataQualityIssues: results.qualityIssues || 0,
912
- batchesCreated: results.batches,
913
- processingTimeMs: results.processingTime,
914
- };
915
- }
916
-
917
- // Save report to S3
918
- async function saveReconciliationReport(report: ReconciliationReport): Promise<void> {
919
- const s3 = new S3DataSource(
920
- {
921
- type: 'S3_JSON',
922
- connectionId: 's3-reports',
923
- name: 'S3 Reports',
924
- s3Config: s3Config,
925
- },
926
- logger
927
- );
928
- const reportKey = `reports/reconciliation/${report.fileKey}-${Date.now()}.json`;
929
-
930
- await s3.uploadFile(reportKey, JSON.stringify(report, null, 2));
931
-
932
- console.log(`Reconciliation report saved: ${reportKey}`);
933
- }
934
- ```
935
-
936
- ## Security Best Practices
937
-
938
- ### Credential Management
939
-
940
- ```typescript
941
- // ✅ CORRECT - Use environment variables
942
- const config = {
943
- fluent: {
944
- baseUrl: process.env.FLUENT_BASE_URL!,
945
- clientId: process.env.FLUENT_CLIENT_ID!,
946
- clientSecret: process.env.FLUENT_CLIENT_SECRET!,
947
- retailerId: process.env.FLUENT_RETAILER_ID!,
948
- },
949
- s3: {
950
- region: process.env.AWS_REGION!,
951
- credentials: {
952
- accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
953
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
954
- },
955
- },
956
- };
957
-
958
- // ❌ WRONG - Never hardcode credentials
959
- const badConfig = {
960
- fluent: {
961
- clientId: 'hardcoded-client-id', // ❌ Don't do this
962
- clientSecret: 'hardcoded-secret', // ❌ Security risk
963
- },
964
- };
965
- ```
966
-
967
- ### Secrets Manager Integration
968
-
969
- ```typescript
970
- import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
971
-
972
- async function loadSecretsFromAWS(): Promise<any> {
973
- const client = new SecretsManagerClient({ region: 'us-east-1' });
974
-
975
- const command = new GetSecretValueCommand({
976
- SecretId: 'fluent-commerce/production',
977
- });
978
-
979
- const response = await client.send(command);
980
- return JSON.parse(response.SecretString!);
981
- }
982
-
983
- // Usage
984
- const secrets = await loadSecretsFromAWS();
985
-
986
- const client = await createClient({
987
- config: {
988
- baseUrl: secrets.FLUENT_BASE_URL,
989
- clientId: secrets.FLUENT_CLIENT_ID,
990
- clientSecret: secrets.FLUENT_CLIENT_SECRET,
991
- retailerId: secrets.FLUENT_RETAILER_ID,
992
- }
993
- });
994
- ```
995
-
996
- ### Data Encryption
997
-
998
- ```typescript
999
- // Encrypt sensitive data before storing
1000
- import * as crypto from 'crypto';
1001
-
1002
- function encryptSensitiveData(data: string, key: string): string {
1003
- const iv = crypto.randomBytes(16);
1004
- const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
1005
-
1006
- let encrypted = cipher.update(data, 'utf8', 'hex');
1007
- encrypted += cipher.final('hex');
1008
-
1009
- return iv.toString('hex') + ':' + encrypted;
1010
- }
1011
-
1012
- function decryptSensitiveData(encrypted: string, key: string): string {
1013
- const parts = encrypted.split(':');
1014
- const iv = Buffer.from(parts[0], 'hex');
1015
- const encryptedData = parts[1];
1016
-
1017
- const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
1018
-
1019
- let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
1020
- decrypted += decipher.final('utf8');
1021
-
1022
- return decrypted;
1023
- }
1024
- ```
1025
-
1026
- ### Audit Logging
1027
-
1028
- ```typescript
1029
- interface AuditLog {
1030
- timestamp: string;
1031
- operation: string;
1032
- user: string;
1033
- resource: string;
1034
- status: 'success' | 'failure';
1035
- details: Record<string, any>;
1036
- }
1037
-
1038
- async function logAuditEvent(event: Omit<AuditLog, 'timestamp'>): Promise<void> {
1039
- const auditLog: AuditLog = {
1040
- timestamp: new Date().toISOString(),
1041
- ...event,
1042
- };
1043
-
1044
- // Log to CloudWatch, Datadog, or custom logging service
1045
- console.log('[AUDIT]', JSON.stringify(auditLog));
1046
-
1047
- // Optionally save to S3 for long-term retention
1048
- await saveAuditLog(auditLog);
1049
- }
1050
-
1051
- // Usage
1052
- await logAuditEvent({
1053
- operation: 'INVENTORY_INGESTION',
1054
- user: process.env.USER || 'system',
1055
- resource: `s3://bucket/file.csv`,
1056
- status: 'success',
1057
- details: {
1058
- recordsProcessed: 1000,
1059
- jobId: 'job-123',
1060
- duration: 5000,
1061
- },
1062
- });
1063
- ```
1064
-
1065
- ## Monitoring and Alerting
1066
-
1067
- ### Metrics Tracking
1068
-
1069
- ```typescript
1070
- interface IngestionMetrics {
1071
- timestamp: string;
1072
- filesProcessed: number;
1073
- recordsIngested: number;
1074
- failedRecords: number;
1075
- processingTimeMs: number;
1076
- avgRecordsPerSecond: number;
1077
- errorRate: number;
1078
- }
1079
-
1080
- class MetricsCollector {
1081
- private metrics: IngestionMetrics[] = [];
1082
-
1083
- recordIngestion(result: IngestionResult): void {
1084
- const metrics: IngestionMetrics = {
1085
- timestamp: new Date().toISOString(),
1086
- filesProcessed: result.filesProcessed,
1087
- recordsIngested: result.recordsIngested,
1088
- failedRecords: result.failedRecords,
1089
- processingTimeMs: result.processingTimeMs,
1090
- avgRecordsPerSecond: result.recordsIngested / (result.processingTimeMs / 1000),
1091
- errorRate: result.failedRecords / result.recordsIngested
1092
- };
1093
-
1094
- this.metrics.push(metrics);
1095
-
1096
- // Alert on high error rate
1097
- if (metrics.errorRate > 0.05) { // > 5% error rate
1098
- this.sendAlert('High error rate detected', metrics);
1099
- }
1100
-
1101
- // Alert on slow processing
1102
- if (metrics.avgRecordsPerSecond < 10) {
1103
- this.sendAlert('Slow processing speed', metrics);
1104
- }
1105
- }
1106
-
1107
- private sendAlert(message: string, metrics: IngestionMetrics): void {
1108
- console.error(\`[ALERT] \${message}\`, metrics);
1109
- // Send to monitoring service (e.g., Datadog, New Relic)
1110
- }
1111
- }
1112
- ```
1113
-
1114
- ### Health Checks
1115
-
1116
- ```typescript
1117
- async function performHealthCheck(): Promise<HealthCheckResult> {
1118
- const checks = [];
1119
-
1120
- // Check Fluent API connectivity
1121
- try {
1122
- await client.graphql({
1123
- query: '{ __typename }'
1124
- });
1125
- checks.push({ name: 'Fluent API', status: 'healthy' });
1126
- } catch (error) {
1127
- checks.push({ name: 'Fluent API', status: 'unhealthy', error: error.message });
1128
- }
1129
-
1130
- // Check S3 connectivity
1131
- try {
1132
- await s3.listFiles({ prefix: 'health-check/', maxKeys: 1 });
1133
- checks.push({ name: 'S3', status: 'healthy' });
1134
- } catch (error) {
1135
- checks.push({ name: 'S3', status: 'unhealthy', error: error.message });
1136
- }
1137
-
1138
- // Check state storage
1139
- try {
1140
- await state.set('health-check', Date.now());
1141
- await state.get('health-check');
1142
- checks.push({ name: 'State Storage', status: 'healthy' });
1143
- } catch (error) {
1144
- checks.push({ name: 'State Storage', status: 'unhealthy', error: error.message });
1145
- }
1146
-
1147
- const allHealthy = checks.every(c => c.status === 'healthy');
1148
-
1149
- return {
1150
- status: allHealthy ? 'healthy' : 'degraded',
1151
- checks,
1152
- timestamp: new Date().toISOString(),
1153
- };
1154
- }
1155
- ```
1156
-
1157
- ## Complete Production Pipeline Example
1158
-
1159
- Putting all best practices together into a production-ready ingestion pipeline:
1160
-
1161
- ```typescript
1162
- import {
1163
- createClient,
1164
- S3DataSource,
1165
- CSVParserService,
1166
- UniversalMapper,
1167
- StateService,
1168
- VersoriKVAdapter,
1169
- FluentClient,
1170
- FileParsingError,
1171
- MappingError,
1172
- BatchAPIError,
1173
- } from '@fluentcommerce/fc-connect-sdk';
1174
- import * as winston from 'winston';
1175
-
1176
- /**
1177
- * Production-grade ingestion pipeline with all best practices
1178
- * - Error handling with retry and circuit breaker
1179
- * - Comprehensive monitoring and logging
1180
- * - Security best practices
1181
- * - Data quality validation
1182
- * - State management
1183
- */
1184
- class ProductionIngestionPipeline {
1185
- private client: FluentClient;
1186
- private s3: S3DataSource;
1187
- private parser: CSVParserService;
1188
- private mapper: UniversalMapper;
1189
- private state: StateService;
1190
- private kv: KVStore;
1191
- private logger: winston.Logger;
1192
- private circuitBreaker: CircuitBreaker;
1193
- private dlq: DeadLetterQueue;
1194
- private metrics: MetricsCollector;
1195
- private qualityValidator: DataQualityValidator;
1196
-
1197
- constructor(private config: ProductionConfig) {}
1198
-
1199
- async initialize(): Promise<void> {
1200
- // Setup structured logging
1201
- this.logger = winston.createLogger({
1202
- level: 'info',
1203
- format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
1204
- transports: [
1205
- new winston.transports.Console(),
1206
- new winston.transports.File({ filename: 'ingestion-error.log', level: 'error' }),
1207
- new winston.transports.File({ filename: 'ingestion-combined.log' }),
1208
- ],
1209
- });
1210
-
1211
- this.logger.info('Initializing production ingestion pipeline');
1212
-
1213
- // Initialize Fluent client
1214
- this.client = await createClient({ config: this.config.fluent });
1215
-
1216
- // Initialize data sources
1217
- this.s3 = new S3DataSource(
1218
- {
1219
- type: 'S3_CSV',
1220
- connectionId: 's3-production',
1221
- name: 'S3 Production',
1222
- s3Config: this.config.s3,
1223
- },
1224
- this.logger
1225
- );
1226
- this.parser = new CSVParserService();
1227
-
1228
- // Initialize field mapper with custom resolvers
1229
- this.mapper = new UniversalMapper(this.config.mapping, {
1230
- customResolvers: this.config.customResolvers || {},
1231
- });
1232
-
1233
- // Initialize state management
1234
- const logger = toStructuredLogger(this.logger, {
1235
- service: 'production-pipeline',
1236
- correlationId: generateCorrelationId()
1237
- });
1238
-
1239
- this.kv = new VersoriKVAdapter(this.config.kv);
1240
- this.stateService = new StateService(logger);
1241
-
1242
- // Initialize circuit breaker
1243
- this.circuitBreaker = new CircuitBreaker({
1244
- failureThreshold: 5,
1245
- resetTimeoutMs: 60000,
1246
- monitoringWindowMs: 120000,
1247
- });
1248
-
1249
- // Initialize dead letter queue
1250
- this.dlq = new DeadLetterQueue(this.s3, this.config.dlqBucket, 'dlq/');
1251
-
1252
- // Initialize metrics collector
1253
- this.metrics = new MetricsCollector();
1254
-
1255
- // Initialize data quality validator
1256
- this.qualityValidator = this.createQualityValidator();
1257
-
1258
- this.logger.info('Pipeline initialization complete');
1259
- }
1260
-
1261
- /**
1262
- * Run the complete ingestion pipeline
1263
- */
1264
- async run(bucket: string, prefix: string): Promise<ExecutionReport> {
1265
- const startTime = Date.now();
1266
-
1267
- try {
1268
- this.logger.info('Starting ingestion pipeline', { bucket, prefix });
1269
-
1270
- // Health check before starting
1271
- const healthCheck = await this.performHealthCheck();
1272
- if (healthCheck.status !== 'healthy') {
1273
- throw new Error(`System unhealthy: ${JSON.stringify(healthCheck.checks)}`);
1274
- }
1275
-
1276
- // List files
1277
- const allFiles = await this.s3.listFiles({ prefix });
1278
- this.logger.info(`Found ${allFiles.length} total files`);
1279
-
1280
- // Filter unprocessed files
1281
- const unprocessedFiles = await this.filterUnprocessedFiles(allFiles);
1282
- this.logger.info(`${unprocessedFiles.length} files to process`);
1283
-
1284
- if (unprocessedFiles.length === 0) {
1285
- return this.createReport(startTime, { filesProcessed: 0 });
1286
- }
1287
-
1288
- // Process files with circuit breaker
1289
- const results = await this.circuitBreaker.execute(async () => {
1290
- return await this.processFilesWithRetry(bucket, unprocessedFiles);
1291
- });
1292
-
1293
- // Generate report
1294
- const report = this.createReport(startTime, results);
1295
-
1296
- // Log metrics
1297
- this.metrics.recordIngestion({
1298
- filesProcessed: results.filesProcessed,
1299
- recordsIngested: results.recordsIngested,
1300
- failedRecords: results.failedRecords,
1301
- processingTimeMs: report.totalTimeMs,
1302
- avgRecordsPerSecond: report.throughput,
1303
- errorRate: results.failedRecords / (results.recordsIngested || 1),
1304
- });
1305
-
1306
- this.logger.info('Pipeline execution complete', report);
1307
-
1308
- return report;
1309
- } catch (error) {
1310
- this.logger.error('Pipeline execution failed', {
1311
- error: error.message,
1312
- stack: error.stack,
1313
- });
1314
-
1315
- await this.sendAlert('CRITICAL: Ingestion pipeline failure', error);
1316
- throw error;
1317
- }
1318
- }
1319
-
1320
- /**
1321
- * Process files with retry logic
1322
- */
1323
- private async processFilesWithRetry(
1324
- bucket: string,
1325
- files: Array<{ key: string }>
1326
- ): Promise<ProcessingResults> {
1327
- const results: ProcessingResults = {
1328
- filesProcessed: 0,
1329
- filesFailed: 0,
1330
- recordsIngested: 0,
1331
- failedRecords: 0,
1332
- details: [],
1333
- };
1334
-
1335
- for (const file of files) {
1336
- const maxRetries = 3;
1337
- let attempt = 0;
1338
- let success = false;
1339
-
1340
- while (attempt < maxRetries && !success) {
1341
- attempt++;
1342
-
1343
- try {
1344
- const fileResult = await this.processFile(bucket, file.path);
1345
-
1346
- results.filesProcessed++;
1347
- results.recordsIngested += fileResult.recordsProcessed;
1348
- results.details.push({
1349
- file: file.path,
1350
- status: 'success',
1351
- records: fileResult.recordsProcessed,
1352
- });
1353
-
1354
- success = true;
1355
-
1356
- this.logger.info(`File processed successfully`, {
1357
- file: file.path,
1358
- records: fileResult.recordsProcessed,
1359
- attempt,
1360
- });
1361
- } catch (error) {
1362
- this.logger.warn(`File processing attempt ${attempt} failed`, {
1363
- file: file.path,
1364
- error: error.message,
1365
- });
1366
-
1367
- if (attempt < maxRetries && this.isRetryable(error)) {
1368
- const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
1369
- await new Promise(resolve => setTimeout(resolve, delay));
1370
- } else {
1371
- results.filesFailed++;
1372
- results.details.push({
1373
- file: file.path,
1374
- status: 'failed',
1375
- error: error.message,
1376
- });
1377
-
1378
- this.logger.error(`File processing failed permanently`, {
1379
- file: file.path,
1380
- attempts: attempt,
1381
- error: error.message,
1382
- });
1383
- }
1384
- }
1385
- }
1386
- }
1387
-
1388
- return results;
1389
- }
1390
-
1391
- /**
1392
- * Process a single file with all validations
1393
- */
1394
- private async processFile(bucket: string, fileKey: string): Promise<FileProcessingResult> {
1395
- this.logger.info(`Processing file: ${fileKey}`);
1396
-
1397
- try {
1398
- // Read file
1399
- const fileContent = await this.s3.downloadFile(fileKey);
1400
-
1401
- // Parse CSV
1402
- const records = await this.parser.parse(fileContent);
1403
- this.logger.debug(`Parsed ${records.length} records from ${fileKey}`);
1404
-
1405
- // Data quality validation
1406
- const validation = this.qualityValidator.validate(records);
1407
-
1408
- if (validation.failed.length > 0) {
1409
- this.logger.warn(`Data quality issues found`, {
1410
- file: fileKey,
1411
- failed: validation.failed.length,
1412
- warnings: validation.warnings.length,
1413
- });
1414
-
1415
- // Save failed records to DLQ
1416
- for (const { record, errors } of validation.failed) {
1417
- await this.dlq.save({
1418
- originalFile: fileKey,
1419
- failedAt: new Date().toISOString(),
1420
- errorType: 'DATA_QUALITY_ERROR',
1421
- errorMessage: errors.join('; '),
1422
- recordData: record,
1423
- retryCount: 0,
1424
- metadata: {},
1425
- });
1426
- }
1427
- }
1428
-
1429
- // Proceed with passed records only
1430
- if (validation.passed.length === 0) {
1431
- throw new Error('No valid records after quality validation');
1432
- }
1433
-
1434
- // Field mapping
1435
- const mappingResult = await this.mapper.map(validation.passed);
1436
-
1437
- if (!mappingResult.success) {
1438
- throw new MappingError(`Field mapping failed: ${mappingResult.errors.join(', ')}`);
1439
- }
1440
-
1441
- // Create job
1442
- const job = await this.client.createJob({
1443
- name: `Production Import - ${fileKey}`,
1444
- retailerId: this.config.fluent.retailerId,
1445
- metadata: {
1446
- fileName: fileKey,
1447
- recordCount: mappingResult.data.length,
1448
- pipeline: 'production',
1449
- },
1450
- });
1451
-
1452
- // Send to Batch API
1453
- await this.client.sendBatch(job.id, {
1454
- action: 'UPSERT',
1455
- entityType: 'INVENTORY',
1456
- entities: mappingResult.data,
1457
- });
1458
-
1459
- // Mark as processed
1460
- await this.state.markFileProcessed(fileKey, {
1461
- jobId: job.id,
1462
- recordCount: mappingResult.data.length,
1463
- timestamp: new Date().toISOString(),
1464
- });
1465
-
1466
- // Audit log
1467
- await this.logAuditEvent({
1468
- operation: 'INVENTORY_INGESTION',
1469
- user: 'system',
1470
- resource: `s3://${bucket}/${fileKey}`,
1471
- status: 'success',
1472
- details: {
1473
- recordsProcessed: mappingResult.data.length,
1474
- jobId: job.id,
1475
- },
1476
- });
1477
-
1478
- return {
1479
- recordsProcessed: mappingResult.data.length,
1480
- jobId: job.id,
1481
- };
1482
- } catch (error) {
1483
- // Audit log failure
1484
- await this.logAuditEvent({
1485
- operation: 'INVENTORY_INGESTION',
1486
- user: 'system',
1487
- resource: `s3://${bucket}/${fileKey}`,
1488
- status: 'failure',
1489
- details: {
1490
- error: error.message,
1491
- },
1492
- });
1493
-
1494
- throw error;
1495
- }
1496
- }
1497
-
1498
- /**
1499
- * Check if error is retryable
1500
- */
1501
- private isRetryable(error: any): boolean {
1502
- if (error instanceof FileParsingError) {
1503
- return false; // File format errors are not retryable
1504
- }
1505
-
1506
- if (error instanceof BatchAPIError) {
1507
- return error.isRetryable;
1508
- }
1509
-
1510
- // Network errors are retryable
1511
- return (
1512
- error.code === 'NETWORK_ERROR' ||
1513
- error.message.includes('timeout') ||
1514
- error.message.includes('ECONNRESET')
1515
- );
1516
- }
1517
-
1518
- /**
1519
- * Filter unprocessed files
1520
- */
1521
- private async filterUnprocessedFiles(
1522
- files: FileMetadata[]
1523
- ): Promise<FileMetadata[]> {
1524
- const unprocessed = [];
1525
-
1526
- for (const file of files) {
1527
- if (!(await this.state.isFileProcessed(this.kv, file.path))) {
1528
- unprocessed.push(file);
1529
- }
1530
- }
1531
-
1532
- return unprocessed;
1533
- }
1534
-
1535
- /**
1536
- * Create data quality validator
1537
- */
1538
- private createQualityValidator(): DataQualityValidator {
1539
- const validator = new DataQualityValidator();
1540
-
1541
- // Add validation rules based on configuration
1542
- validator.addRule({
1543
- name: 'Required Fields',
1544
- severity: 'error',
1545
- validate: record => {
1546
- const required = this.config.requiredFields || [];
1547
- const missing = required.filter(field => !record[field]);
1548
-
1549
- if (missing.length > 0) {
1550
- return {
1551
- isValid: false,
1552
- error: `Missing required fields: ${missing.join(', ')}`,
1553
- };
1554
- }
1555
-
1556
- return { isValid: true };
1557
- },
1558
- });
1559
-
1560
- return validator;
1561
- }
1562
-
1563
- /**
1564
- * Perform system health check
1565
- */
1566
- private async performHealthCheck(): Promise<HealthCheckResult> {
1567
- const checks = [];
1568
-
1569
- // Check Fluent API
1570
- try {
1571
- await this.client.graphql({
1572
- query: '{ __typename }'
1573
- });
1574
- checks.push({ name: 'Fluent API', status: 'healthy' });
1575
- } catch (error) {
1576
- checks.push({ name: 'Fluent API', status: 'unhealthy', error: error.message });
1577
- }
1578
-
1579
- // Check S3
1580
- try {
1581
- await this.s3.listObjects(this.config.s3Bucket, 'health-check/');
1582
- checks.push({ name: 'S3', status: 'healthy' });
1583
- } catch (error) {
1584
- checks.push({ name: 'S3', status: 'unhealthy', error: error.message });
1585
- }
1586
-
1587
- const allHealthy = checks.every(c => c.status === 'healthy');
1588
-
1589
- return {
1590
- status: allHealthy ? 'healthy' : 'degraded',
1591
- checks,
1592
- timestamp: new Date().toISOString(),
1593
- };
1594
- }
1595
-
1596
- /**
1597
- * Create execution report
1598
- */
1599
- private createReport(startTime: number, results: Partial<ProcessingResults>): ExecutionReport {
1600
- const totalTimeMs = Date.now() - startTime;
1601
-
1602
- return {
1603
- startTime: new Date(startTime).toISOString(),
1604
- endTime: new Date().toISOString(),
1605
- totalTimeMs,
1606
- filesProcessed: results.filesProcessed || 0,
1607
- filesFailed: results.filesFailed || 0,
1608
- recordsIngested: results.recordsIngested || 0,
1609
- failedRecords: results.failedRecords || 0,
1610
- throughput: (results.recordsIngested || 0) / (totalTimeMs / 1000),
1611
- successRate:
1612
- ((results.filesProcessed || 0) /
1613
- ((results.filesProcessed || 0) + (results.filesFailed || 0))) *
1614
- 100,
1615
- };
1616
- }
1617
-
1618
- /**
1619
- * Log audit event
1620
- */
1621
- private async logAuditEvent(event: Omit<AuditLog, 'timestamp'>): Promise<void> {
1622
- const auditLog: AuditLog = {
1623
- timestamp: new Date().toISOString(),
1624
- ...event,
1625
- };
1626
-
1627
- this.logger.info('[AUDIT]', auditLog);
1628
-
1629
- // Optionally save to S3 for compliance
1630
- // await this.s3.putObject(auditBucket, auditKey, JSON.stringify(auditLog));
1631
- }
1632
-
1633
- /**
1634
- * Send alert to monitoring service
1635
- */
1636
- private async sendAlert(message: string, error: any): Promise<void> {
1637
- this.logger.error('[ALERT]', { message, error: error.message });
1638
-
1639
- // Integration with alerting service (PagerDuty, Slack, etc.)
1640
- // await alertService.send({ message, error });
1641
- }
1642
- }
1643
-
1644
- // Usage
1645
- const pipeline = new ProductionIngestionPipeline({
1646
- fluent: {
1647
- baseUrl: process.env.FLUENT_BASE_URL!,
1648
- clientId: process.env.FLUENT_CLIENT_ID!,
1649
- clientSecret: process.env.FLUENT_CLIENT_SECRET!,
1650
- retailerId: process.env.FLUENT_RETAILER_ID!,
1651
- },
1652
- s3: {
1653
- region: process.env.AWS_REGION!,
1654
- credentials: {
1655
- accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
1656
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
1657
- },
1658
- },
1659
- s3Bucket: 'inventory-bucket',
1660
- dlqBucket: 'inventory-dlq-bucket',
1661
- mapping: {
1662
- fields: {
1663
- ref: { source: 'sku', required: true },
1664
- productRef: { source: 'product_id', required: true },
1665
- locationRef: { source: 'warehouse_code', required: true },
1666
- qty: { source: 'quantity', resolver: 'sdk.parseInt', required: true },
1667
- type: { source: 'inventory_type', default: 'ON_HAND' },
1668
- status: { source: 'status', default: 'AVAILABLE' },
1669
- },
1670
- },
1671
- requiredFields: ['sku', 'warehouse', 'quantity'],
1672
- kv: openKv(),
1673
- });
1674
-
1675
- await pipeline.initialize();
1676
- const report = await pipeline.run('inventory-bucket', 'data/');
1677
-
1678
- console.log('Ingestion complete:', report);
1679
- ```
1680
-
1681
- ## Common Errors and Solutions
1682
-
1683
- ### Error: JOB_EXPIRED
1684
-
1685
- **Cause:** Job exceeded 24-hour lifetime
1686
-
1687
- **Solution:**
1688
-
1689
- ```typescript
1690
- async function handleExpiredJob(originalJobId: string, remainingBatches: any[]) {
1691
- // Create new job
1692
- const newJob = await client.createJob({
1693
- name: \`Recovery - \${new Date().toISOString()}\`,
1694
- retailerId: process.env.FLUENT_RETAILER_ID!,
1695
- metadata: {
1696
- originalJobId,
1697
- reason: 'JOB_EXPIRED'
1698
- }
1699
- });
1700
-
1701
- // Resend remaining batches
1702
- for (const batch of remainingBatches) {
1703
- await client.sendBatch(newJob.id, batch);
1704
- }
1705
-
1706
- return newJob.id;
1707
- }
1708
- ```
1709
-
1710
- ### Error: VALIDATION_ERROR
1711
-
1712
- **Cause:** Invalid field values or missing required fields
1713
-
1714
- **Solution:**
1715
-
1716
- ```typescript
1717
- async function handleValidationError(records: any[], error: any) {
1718
- // Parse validation errors
1719
- const invalidRecords = extractInvalidRecords(error);
1720
-
1721
- // Filter out invalid records
1722
- const validRecords = records.filter(r => !invalidRecords.includes(r));
1723
-
1724
- // Log invalid records
1725
- console.error('Invalid records:', invalidRecords);
1726
- await saveToErrorLog(invalidRecords);
1727
-
1728
- // Retry with valid records only
1729
- return validRecords;
1730
- }
1731
- ```
1732
-
1733
- ### Error: RATE_LIMIT_ERROR
1734
-
1735
- **Cause:** Too many requests to Fluent API
1736
-
1737
- **Solution:**
1738
-
1739
- ```typescript
1740
- class RateLimitHandler {
1741
- async handleRateLimit(operation: () => Promise<any>): Promise<any> {
1742
- try {
1743
- return await operation();
1744
- } catch (error) {
1745
- if (error.code === 'RATE_LIMIT_ERROR') {
1746
- const retryAfter = error.retryAfter || 60000; // Default 60s
1747
- console.log(\`Rate limited. Waiting \${retryAfter}ms\`);
1748
- await sleep(retryAfter);
1749
- return await operation(); // Retry once
1750
- }
1751
- throw error;
1752
- }
1753
- }
1754
- }
1755
- ```
1756
-
1757
- ## Production Checklist
1758
-
1759
- ### Before Deployment
1760
-
1761
- - [ ] Schema validated against Fluent Commerce API
1762
- - [ ] Field mappings tested with sample data
1763
- - [ ] Error handling implemented for all operations
1764
- - [ ] State management configured and tested
1765
- - [ ] Monitoring and alerting configured
1766
- - [ ] Health checks implemented
1767
- - [ ] Rate limiting configured
1768
- - [ ] Dead letter queue configured
1769
- - [ ] Rollback plan documented
1770
-
1771
- ### During Deployment
1772
-
1773
- - [ ] Start with small batch sizes (100-500)
1774
- - [ ] Monitor error rates closely
1775
- - [ ] Test with sample files first
1776
- - [ ] Gradually increase batch sizes
1777
- - [ ] Verify state management works correctly
1778
-
1779
- ### After Deployment
1780
-
1781
- - [ ] Monitor ingestion metrics daily
1782
- - [ ] Review error logs regularly
1783
- - [ ] Track processing times
1784
- - [ ] Optimize based on metrics
1785
- - [ ] Document common issues and solutions
1786
-
1787
- ## Debugging Tips
1788
-
1789
- ### Enable Debug Logging
1790
-
1791
- ```typescript
1792
- const client = await createClient({
1793
- config: {
1794
- ...config
1795
- },
1796
- logger: {
1797
- debug: (...args) => console.log('[DEBUG]', ...args),
1798
- info: (...args) => console.log('[INFO]', ...args),
1799
- warn: (...args) => console.warn('[WARN]', ...args),
1800
- error: (...args) => console.error('[ERROR]', ...args),
1801
- },
1802
- });
1803
- ```
1804
-
1805
- ### Trace File Processing
1806
-
1807
- ```typescript
1808
- async function traceFileProcessing(fileKey: string) {
1809
- console.log(\`[TRACE] Starting processing: \${fileKey}\`);
1810
-
1811
- try {
1812
- console.log('[TRACE] Reading file from S3');
1813
- const data = await s3.downloadFile(fileKey);
1814
- console.log(\`[TRACE] File size: \${data.length} bytes\`);
1815
-
1816
- console.log('[TRACE] Parsing CSV');
1817
- const records = await parser.parse(data);
1818
- console.log(\`[TRACE] Parsed \${records.length} records\`);
1819
-
1820
- console.log('[TRACE] Mapping fields');
1821
- const result = await mapper.map(records);
1822
- console.log(\`[TRACE] Mapped \${result.data.length} valid records\`);
1823
-
1824
- console.log('[TRACE] Creating job');
1825
- const job = await client.createJob({ name: \`Import - \${fileKey}\` });
1826
- console.log(\`[TRACE] Job created: \${job.id}\`);
1827
-
1828
- console.log('[TRACE] Sending batch');
1829
- await client.sendBatch(job.id, { entities: result.data });
1830
- console.log('[TRACE] Batch sent successfully');
1831
-
1832
- } catch (error) {
1833
- console.error(\`[TRACE] Error at: \${error.message}\`);
1834
- throw error;
1835
- }
1836
- }
1837
- ```
1838
-
1839
- ## Key Takeaways
1840
-
1841
- - 🎯 **Always validate** - Check schema and data before sending
1842
- - 🎯 **Handle errors gracefully** - Retry transient failures, log permanent ones
1843
- - 🎯 **Monitor everything** - Track metrics, set up alerts
1844
- - 🎯 **Use state management** - Prevent duplicates, track history
1845
- - 🎯 **Test thoroughly** - Start small, scale gradually
1846
- - 🎯 **Document** - Keep runbooks for common issues
1847
-
1848
- ## Congratulations!
1849
-
1850
- You've completed the Data Ingestion Learning Path! You now know:
1851
-
1852
- - ✅ Core ingestion concepts and architecture
1853
- - ✅ How to read from multiple data sources
1854
- - ✅ How to parse CSV, Parquet, XML, and JSON
1855
- - ✅ How to transform data with UniversalMapper
1856
- - ✅ How to use the Batch API effectively
1857
- - ✅ How to implement state management
1858
- - ✅ How to optimize performance
1859
- - ✅ How to build production-ready ingestion workflows
1860
-
1861
- ## Next Steps
1862
-
1863
- Congratulations! You've completed the Data Ingestion Learning Path and mastered production-ready ingestion patterns.
1864
-
1865
- **Continue Learning:**
1866
-
1867
- - 📖 [Data Extraction Guide](../../extraction/) - Learn reverse workflows (Fluent → S3/Parquet/CSV)
1868
- - 📖 [Resolver Development Guide](../../mapping/resolvers/mapping-resolvers-readme.md) - Build custom data transformations
1869
- - 📖 [Universal Mapping Guide](../../mapping/mapping-readme.md) - Master field mapping patterns
1870
- - 📖 [Error Handling Guide](../../../03-PATTERN-GUIDES/error-handling/error-handling-readme.md) - Deep dive into error patterns
1871
- - 📖 [Versori Platform Integration](../../../04-REFERENCE/platforms/versori/) - Deploy to production
1872
-
1873
- **Real-World Examples:**
1874
-
1875
- - 🔍 [Complete Connector Examples](../../../01-TEMPLATES/versori/workflows/readme.md) - Production Versori connectors
1876
- - 📋 [Use Case Library](../../../01-TEMPLATES/readme.md) - Business-specific implementations
1877
- - 🛠️ [CLI Tools](../../../../bin/) - Code generation and validation utilities
1878
-
1879
- **Resources:**
1880
-
1881
- - 📚 [Complete API Reference](../../api-reference/api-reference-readme.md)
1882
- - 📖 [Quick Reference Cheat Sheet](../ingestion-quick-reference.md)
1883
- - 🐛 [Troubleshooting Guide](../../../00-START-HERE/troubleshooting-quick-reference.md)
1884
-
1885
- ---
1886
-
1887
- [← Back to Ingestion Guide](../ingestion-readme.md) | [Previous: Module 8 - Performance Optimization](./02-core-guides-ingestion-08-performance-optimization.md)
1888
-
1889
- **Need Help?**
1890
-
1891
- - 📧 Email: support@fluentcommerce.com
1892
- - 📚 Documentation: [FC Connect SDK Docs](../../../)
1893
- - 🔗 GitHub: Report issues or contribute
1
+ # Module 9: Best Practices
2
+
3
+ [← Back to Ingestion Guide](../ingestion-readme.md)
4
+
5
+ **Module 9 of 9** | **Level**: All Levels | **Time**: 30 minutes
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ This module covers production-ready patterns for data ingestion workflows. Learn comprehensive error handling strategies, monitoring approaches, security best practices, scheduling patterns, data quality validation, and complete production deployment patterns.
12
+
13
+ New in this version:
14
+
15
+ - Preflight validation before runs (PreflightValidator)
16
+ - Job lifecycle tracking (JobTracker)
17
+ - Partial batch failure recovery (PartialBatchRecovery)
18
+ - Versori file-level deduplication (VersoriFileTracker)
19
+
20
+ ## Learning Objectives
21
+
22
+ By the end of this module, you will:
23
+
24
+ - ✅ Implement robust error handling with retry logic and circuit breakers
25
+ - ✅ Set up comprehensive monitoring, logging, and alerting
26
+ - ✅ Apply security best practices for credentials and data
27
+ - ✅ Design effective scheduling and trigger strategies
28
+ - ✅ Validate data quality before ingestion
29
+ - ✅ Build production-ready ingestion pipelines
30
+ - ✅ Troubleshoot common issues effectively
31
+ - ✅ Follow deployment checklists for success
32
+
33
+ ---
34
+
35
+ ## Error Handling
36
+
37
+ ### Retry Strategy with Exponential Backoff
38
+
39
+ ```typescript
40
+ async function retryWithBackoff<T>(
41
+ operation: () => Promise<T>,
42
+ maxRetries: number = 3
43
+ ): Promise<T> {
44
+ let lastError: Error;
45
+ let delay = 1000; // Start with 1 second
46
+
47
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
48
+ try {
49
+ return await operation();
50
+ } catch (error) {
51
+ lastError = error;
52
+
53
+ if (!isRetryable(error) || attempt === maxRetries) {
54
+ throw error;
55
+ }
56
+
57
+ console.log(\`Retry \${attempt}/\${maxRetries} after \${delay}ms\`);
58
+ await sleep(delay);
59
+ delay *= 2; // Exponential backoff
60
+ }
61
+ }
62
+
63
+ throw lastError;
64
+ }
65
+
66
+ function isRetryable(error: any): boolean {
67
+ const retryableCodes = [
68
+ 'RATE_LIMIT_ERROR',
69
+ 'NETWORK_ERROR',
70
+ 'TIMEOUT_ERROR',
71
+ 'SERVICE_UNAVAILABLE'
72
+ ];
73
+
74
+ return (
75
+ retryableCodes.includes(error.code) ||
76
+ error.message.includes('timeout') ||
77
+ error.message.includes('ECONNRESET')
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### SDK-Specific Error Handling
83
+
84
+ The SDK throws specific error types that require different handling strategies:
85
+
86
+ ```typescript
87
+ import {
88
+ FileParsingError,
89
+ MappingError,
90
+ BatchAPIError,
91
+ StateError,
92
+ createConsoleLogger,
93
+ toStructuredLogger,
94
+ } from '@fluentcommerce/fc-connect-sdk';
95
+
96
+ async function handleSDKErrors(fileKey: string) {
97
+ try {
98
+ await processFile(fileKey);
99
+ } catch (error) {
100
+ if (error instanceof FileParsingError) {
101
+ // File format issues - log and skip
102
+ console.error(`Invalid file format: ${fileKey}`, {
103
+ line: error.line,
104
+ column: error.column,
105
+ message: error.message,
106
+ });
107
+
108
+ // Move to error bucket for manual review
109
+ await moveToErrorBucket(fileKey, error);
110
+ return { status: 'skipped', reason: 'parsing_error' };
111
+ } else if (error instanceof MappingError) {
112
+ // Field mapping failures - log details
113
+ console.error(`Field mapping failed: ${fileKey}`, {
114
+ invalidFields: error.invalidFields,
115
+ recordIndex: error.recordIndex,
116
+ errors: error.errors,
117
+ });
118
+
119
+ // Save error report
120
+ await saveErrorReport(fileKey, error);
121
+ return { status: 'failed', reason: 'mapping_error' };
122
+ } else if (error instanceof BatchAPIError) {
123
+ // Batch API rejections - check if retryable
124
+ if (error.isRetryable) {
125
+ console.warn(`Retryable batch error: ${error.code}`);
126
+ throw error; // Will be caught by retry logic
127
+ } else {
128
+ console.error(`Permanent batch error: ${error.code}`, {
129
+ jobId: error.jobId,
130
+ batchId: error.batchId,
131
+ details: error.details,
132
+ });
133
+
134
+ // Save to dead letter queue
135
+ await saveToDLQ(fileKey, error);
136
+ return { status: 'failed', reason: 'batch_api_error' };
137
+ }
138
+ } else if (error instanceof StateError) {
139
+ // State management issues - critical
140
+ console.error(`State management error: ${error.message}`);
141
+
142
+ // Alert operations team
143
+ await sendAlert('CRITICAL: State management failure', error);
144
+ throw error; // Don't continue if state is broken
145
+ } else {
146
+ // Unknown error - log and alert
147
+ console.error(`Unexpected error processing ${fileKey}:`, error);
148
+ await sendAlert('Unknown ingestion error', error);
149
+ throw error;
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ### Circuit Breaker Pattern
156
+
157
+ Prevent cascading failures when external services are down:
158
+
159
+ ```typescript
160
+ interface CircuitBreakerConfig {
161
+ failureThreshold: number;
162
+ resetTimeoutMs: number;
163
+ monitoringWindowMs: number;
164
+ }
165
+
166
+ enum CircuitState {
167
+ CLOSED = 'CLOSED', // Normal operation
168
+ OPEN = 'OPEN', // Blocking requests
169
+ HALF_OPEN = 'HALF_OPEN', // Testing recovery
170
+ }
171
+
172
+ class CircuitBreaker {
173
+ private state: CircuitState = CircuitState.CLOSED;
174
+ private failureCount: number = 0;
175
+ private lastFailureTime: number = 0;
176
+ private failures: number[] = [];
177
+
178
+ constructor(private config: CircuitBreakerConfig) {}
179
+
180
+ async execute<T>(operation: () => Promise<T>): Promise<T> {
181
+ // Check if circuit should reset
182
+ if (this.shouldAttemptReset()) {
183
+ this.state = CircuitState.HALF_OPEN;
184
+ }
185
+
186
+ // Block if circuit is open
187
+ if (this.state === CircuitState.OPEN) {
188
+ throw new Error(
189
+ `Circuit breaker OPEN. Last failure: ${new Date(this.lastFailureTime).toISOString()}`
190
+ );
191
+ }
192
+
193
+ try {
194
+ const result = await operation();
195
+
196
+ // Success - reset if in half-open state
197
+ if (this.state === CircuitState.HALF_OPEN) {
198
+ console.log('Circuit breaker: Service recovered, closing circuit');
199
+ this.reset();
200
+ }
201
+
202
+ return result;
203
+ } catch (error) {
204
+ this.recordFailure();
205
+
206
+ // Open circuit if threshold exceeded
207
+ if (this.failureCount >= this.config.failureThreshold) {
208
+ this.state = CircuitState.OPEN;
209
+ this.lastFailureTime = Date.now();
210
+ console.error(`Circuit breaker OPENED after ${this.failureCount} failures`);
211
+ }
212
+
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ private recordFailure(): void {
218
+ const now = Date.now();
219
+ this.failures.push(now);
220
+
221
+ // Remove old failures outside monitoring window
222
+ this.failures = this.failures.filter(time => now - time < this.config.monitoringWindowMs);
223
+
224
+ this.failureCount = this.failures.length;
225
+ }
226
+
227
+ private shouldAttemptReset(): boolean {
228
+ if (this.state !== CircuitState.OPEN) {
229
+ return false;
230
+ }
231
+
232
+ const timeSinceLastFailure = Date.now() - this.lastFailureTime;
233
+ return timeSinceLastFailure >= this.config.resetTimeoutMs;
234
+ }
235
+
236
+ private reset(): void {
237
+ this.state = CircuitState.CLOSED;
238
+ this.failureCount = 0;
239
+ this.failures = [];
240
+ }
241
+
242
+ getState(): CircuitState {
243
+ return this.state;
244
+ }
245
+ }
246
+
247
+ // Usage
248
+ const fluentAPICircuit = new CircuitBreaker({
249
+ failureThreshold: 5, // Open after 5 failures
250
+ resetTimeoutMs: 60000, // Try again after 1 minute
251
+ monitoringWindowMs: 120000, // Track failures over 2 minutes
252
+ });
253
+
254
+ async function processWithCircuitBreaker(fileKey: string) {
255
+ try {
256
+ await fluentAPICircuit.execute(async () => {
257
+ return await processFile(fileKey);
258
+ });
259
+ } catch (error) {
260
+ if (error.message.includes('Circuit breaker OPEN')) {
261
+ console.log('Service temporarily unavailable, requeueing file');
262
+ await requeueFile(fileKey);
263
+ } else {
264
+ throw error;
265
+ }
266
+ }
267
+ }
268
+ ```
269
+
270
+ ### Dead Letter Queue Pattern
271
+
272
+ Store failed records for later processing:
273
+
274
+ ```typescript
275
+ import { S3DataSource } from '@fluentcommerce/fc-connect-sdk';
276
+
277
+ interface DLQRecord {
278
+ originalFile: string;
279
+ failedAt: string;
280
+ errorType: string;
281
+ errorMessage: string;
282
+ recordData: any;
283
+ retryCount: number;
284
+ metadata: Record<string, any>;
285
+ }
286
+
287
+ class DeadLetterQueue {
288
+ constructor(
289
+ private s3: S3DataSource,
290
+ private dlqBucket: string,
291
+ private dlqPrefix: string = 'dlq/'
292
+ ) {}
293
+
294
+ /**
295
+ * Save failed record to DLQ
296
+ */
297
+ async save(record: DLQRecord): Promise<void> {
298
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
299
+ const key = `${this.dlqPrefix}${record.errorType}/${timestamp}-${record.originalFile}`;
300
+
301
+ await this.s3.uploadFile(key, JSON.stringify(record, null, 2));
302
+
303
+ console.log(`Saved to DLQ: ${key}`);
304
+ }
305
+
306
+ /**
307
+ * Retry processing records from DLQ
308
+ */
309
+ async retryAll(
310
+ processor: (record: any) => Promise<void>
311
+ ): Promise<{ successful: number; failed: number }> {
312
+ const files = await this.s3.listFiles({ prefix: this.dlqPrefix });
313
+ let successful = 0;
314
+ let failed = 0;
315
+
316
+ for (const file of files) {
317
+ try {
318
+ const content = await this.s3.downloadFile(file.path);
319
+ const dlqRecord: DLQRecord = JSON.parse(content);
320
+
321
+ // Attempt reprocessing
322
+ await processor(dlqRecord.recordData);
323
+
324
+ // Success - delete from DLQ (Note: deleteObject method may not exist - use uploadFile with empty content or check SDK)
325
+ // await this.s3.deleteObject(this.dlqBucket, file.path);
326
+ successful++;
327
+ } catch (error) {
328
+ console.error(`DLQ retry failed for ${file.path}:`, error);
329
+ failed++;
330
+
331
+ // Update retry count
332
+ await this.incrementRetryCount(file.path);
333
+ }
334
+ }
335
+
336
+ return { successful, failed };
337
+ }
338
+
339
+ /**
340
+ * Increment retry count in DLQ record
341
+ */
342
+ private async incrementRetryCount(key: string): Promise<void> {
343
+ try {
344
+ const content = await this.s3.downloadFile(key);
345
+ const record: DLQRecord = JSON.parse(content);
346
+
347
+ record.retryCount = (record.retryCount || 0) + 1;
348
+ record.metadata.lastRetryAttempt = new Date().toISOString();
349
+
350
+ await this.s3.uploadFile(key, JSON.stringify(record, null, 2));
351
+ } catch (error) {
352
+ console.error(`Failed to update retry count for ${key}:`, error);
353
+ }
354
+ }
355
+ }
356
+
357
+ // Usage
358
+ const dlq = new DeadLetterQueue(s3DataSource, 'my-dlq-bucket');
359
+
360
+ async function processFileWithDLQ(fileKey: string) {
361
+ try {
362
+ const records = await parseFile(fileKey);
363
+
364
+ for (const [index, record] of records.entries()) {
365
+ try {
366
+ await processRecord(record);
367
+ } catch (error) {
368
+ // Save failed record to DLQ
369
+ await dlq.save({
370
+ originalFile: fileKey,
371
+ failedAt: new Date().toISOString(),
372
+ errorType: error.constructor.name,
373
+ errorMessage: error.message,
374
+ recordData: record,
375
+ retryCount: 0,
376
+ metadata: {
377
+ recordIndex: index,
378
+ totalRecords: records.length,
379
+ },
380
+ });
381
+ }
382
+ }
383
+ } catch (error) {
384
+ console.error(`Complete file failure: ${fileKey}`, error);
385
+ throw error;
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### Graceful Degradation
391
+
392
+ ```typescript
393
+ async function processFileWithFallback(fileKey: string) {
394
+ try {
395
+ // Primary processing path
396
+ await processFileNormally(fileKey);
397
+ } catch (error) {
398
+ console.error(\`Primary processing failed: \${error.message}\`);
399
+
400
+ try {
401
+ // Fallback: Process with reduced batch size
402
+ await processFileWithSmallerBatches(fileKey);
403
+ } catch (fallbackError) {
404
+ console.error(\`Fallback processing failed: \${fallbackError.message}\`);
405
+
406
+ // Last resort: Save to dead letter queue
407
+ await saveToDeadLetterQueue(fileKey, error);
408
+ throw new Error(\`File processing failed completely: \${fileKey}\`);
409
+ }
410
+ }
411
+ }
412
+ ```
413
+
414
+ ## Validation Best Practices
415
+
416
+ ### Pre-Flight Validation
417
+
418
+ ```typescript
419
+ async function validateBeforeIngestion(records: any[]): Promise<ValidationResult> {
420
+ const errors = [];
421
+ const warnings = [];
422
+
423
+ // Schema validation
424
+ for (const [index, record] of records.entries()) {
425
+ // Required fields
426
+ if (!record.ref) {
427
+ errors.push(\`Row \${index}: Missing required field 'ref'\`);
428
+ }
429
+
430
+ if (!record.productRef) {
431
+ errors.push(\`Row \${index}: Missing required field 'productRef'\`);
432
+ }
433
+
434
+ if (!record.locationRef) {
435
+ errors.push(\`Row \${index}: Missing required field 'locationRef'\`);
436
+ }
437
+
438
+ if (record.qty === undefined || record.qty === null) {
439
+ errors.push(\`Row \${index}: Missing required field 'qty'\`);
440
+ }
441
+
442
+ // Type validation
443
+ if (typeof record.qty !== 'number') {
444
+ errors.push(\`Row \${index}: 'qty' must be a number\`);
445
+ }
446
+
447
+ // Business rules
448
+ if (record.qty < 0) {
449
+ errors.push(\`Row \${index}: Quantity cannot be negative\`);
450
+ }
451
+
452
+ if (record.qty > 1000000) {
453
+ warnings.push(\`Row \${index}: Unusually large quantity\`);
454
+ }
455
+ }
456
+
457
+ return {
458
+ isValid: errors.length === 0,
459
+ errors,
460
+ warnings
461
+ };
462
+ }
463
+
464
+ // Usage
465
+ const validation = await validateBeforeIngestion(records);
466
+
467
+ if (!validation.isValid) {
468
+ console.error('Validation errors:', validation.errors);
469
+ throw new Error('Data validation failed');
470
+ }
471
+
472
+ if (validation.warnings.length > 0) {
473
+ console.warn('Validation warnings:', validation.warnings);
474
+ }
475
+ ```
476
+
477
+ ## Scheduling and Triggers
478
+
479
+ ###Cron Scheduling Patterns
480
+
481
+ Different scheduling strategies for different ingestion scenarios:
482
+
483
+ ```typescript
484
+ // Daily midnight sync (00:00 UTC)
485
+ const dailyMidnightCron = '0 0 * * *';
486
+
487
+ // Every 6 hours
488
+ const every6HoursCron = '0 */6 * * *';
489
+
490
+ // Every hour during business hours (9 AM - 5 PM, Mon-Fri)
491
+ const businessHoursCron = '0 9-17 * * 1-5';
492
+
493
+ // Every 15 minutes
494
+ const every15MinutesCron = '*/15 * * * *';
495
+
496
+ // Weekly Sunday at 2 AM
497
+ const weeklySundayCron = '0 2 * * 0';
498
+ ```
499
+
500
+ **Recommended schedules by use case:**
501
+
502
+ | Use Case | Schedule | Cron Expression | Rationale |
503
+ | ----------------------- | ------------- | ---------------- | ---------------------------- |
504
+ | **WMS Daily Sync** | Once daily | `0 2 * * *` | Process overnight updates |
505
+ | **3PL Cycle Counts** | Every 6 hours | `0 */6 * * *` | Keep inventory fresh |
506
+ | **Real-time Updates** | Every 15 min | `*/15 * * * *` | Near real-time sync |
507
+ | **Weekly Full Sync** | Sunday 2 AM | `0 2 * * 0` | Comprehensive reconciliation |
508
+ | **Business Hours Only** | 9 AM - 5 PM | `0 9-17 * * 1-5` | During operational hours |
509
+
510
+ ### Trigger Mechanisms
511
+
512
+ Different ways to trigger ingestion workflows:
513
+
514
+ #### 1. S3 Event Triggers
515
+
516
+ ```typescript
517
+ // Versori workflow triggered by S3 event
518
+ export const s3TriggeredIngestion = webhook('s3-inventory-upload', {
519
+ response: { mode: 'sync' },
520
+ })
521
+ .then(
522
+ fn('parse-s3-event', async ({ data }) => {
523
+ const event = JSON.parse(data);
524
+ const bucket = event.Records[0].s3.bucket.name;
525
+ const key = decodeURIComponent(event.Records[0].s3.object.key);
526
+
527
+ return { bucket, key };
528
+ })
529
+ )
530
+ .then(
531
+ fn('process-file', async ({ data, connections, log, openKv }) => {
532
+ const client = await createClient({ connections, log, openKv });
533
+
534
+ // Initialize logger and state management
535
+ const logger = toStructuredLogger(log, {
536
+ service: 'ingestion',
537
+ correlationId: generateCorrelationId()
538
+ });
539
+
540
+ const stateService = new StateService(logger);
541
+ const kvAdapter = new VersoriKVAdapter(openKv());
542
+
543
+ const s3 = new S3DataSource(
544
+ {
545
+ type: 'S3_CSV',
546
+ connectionId: 's3-triggered',
547
+ name: 'S3 Triggered Ingestion',
548
+ s3Config: { region: 'us-east-1', bucket: data.bucket },
549
+ },
550
+ logger
551
+ );
552
+
553
+ // Check if already processed
554
+ if (await stateService.isFileProcessed(kvAdapter, data.key)) {
555
+ log.info(`File already processed: ${data.key}`);
556
+ return { status: 'skipped', reason: 'already_processed' };
557
+ }
558
+
559
+ // Process file
560
+ await processInventoryFile(client, s3, data.bucket, data.key);
561
+
562
+ // Mark as processed
563
+ await stateService.updateSyncState(kvAdapter, [{
564
+ fileName: data.key,
565
+ lastModified: new Date().toISOString(),
566
+ }]);
567
+
568
+ return { status: 'success', file: data.key };
569
+ })
570
+ )
571
+ .catch(({ data, log }) => {
572
+ log.error('S3 triggered ingestion failed:', data);
573
+ return { status: 'error', error: data };
574
+ });
575
+ ```
576
+
577
+ #### 2. SFTP Polling
578
+
579
+ ```typescript
580
+ import { SftpDataSource, StateService, VersoriKVAdapter, createConsoleLogger, toStructuredLogger } from '@fluentcommerce/fc-connect-sdk';
581
+
582
+ async function pollSftpDirectory(kv: any) {
583
+ const logger = toStructuredLogger(createConsoleLogger(), {
584
+ service: 'ingestion',
585
+ correlationId: generateCorrelationId()
586
+ });
587
+
588
+ const stateService = new StateService(logger);
589
+ const kvAdapter = new VersoriKVAdapter(kv);
590
+
591
+ const sftp = new SftpDataSource({
592
+ type: 'SFTP_CSV',
593
+ connectionId: 'sftp-polling',
594
+ name: 'SFTP Polling',
595
+ settings: {
596
+ host: process.env.SFTP_HOST!,
597
+ port: 22,
598
+ username: process.env.SFTP_USER!,
599
+ privateKey: fs.readFileSync('/path/to/private/key'),
600
+ }
601
+ }, logger);
602
+
603
+ const remoteDir = '/inbound/inventory/';
604
+ const files = await sftp.listFiles(remoteDir);
605
+
606
+ for (const file of files) {
607
+ if (await stateService.isFileProcessed(kvAdapter, file.name)) {
608
+ continue; // Skip already processed
609
+ }
610
+
611
+ // Download and process
612
+ const localPath = `/tmp/${file.name}`;
613
+ await sftp.downloadFile(file.path, localPath); // Use file.path (full path) for download
614
+
615
+ await processLocalFile(localPath);
616
+
617
+ // Mark as processed
618
+ await stateService.updateSyncState(kvAdapter, [{
619
+ fileName: file.name,
620
+ lastModified: new Date().toISOString(),
621
+ }]);
622
+
623
+ // Optionally move to processed folder
624
+ await sftp.moveFile(`${remoteDir}${file.name}`, `/processed/${file.name}`);
625
+ }
626
+ }
627
+ ```
628
+
629
+ #### 3. Webhook Triggers
630
+
631
+ ```typescript
632
+ // Manual or external system triggered ingestion
633
+ export const manualIngestion = webhook('manual-trigger', {
634
+ response: { mode: 'sync' },
635
+ })
636
+ .then(
637
+ fn('validate-request', ({ data }) => {
638
+ const { bucket, prefix, batchSize = 2000 } = JSON.parse(data);
639
+
640
+ if (!bucket || !prefix) {
641
+ throw new Error('Missing required parameters: bucket, prefix');
642
+ }
643
+
644
+ return { bucket, prefix, batchSize };
645
+ })
646
+ )
647
+ .then(
648
+ fn('run-ingestion', async ({ data, connections, log }) => {
649
+ const client = await createClient(ctx); // Auto-detects Versori context
650
+ const s3 = new S3DataSource(
651
+ {
652
+ type: 'S3_CSV',
653
+ s3Config: { region: 'us-east-1' },
654
+ },
655
+ log
656
+ );
657
+
658
+ const files = await s3.listFiles({ prefix: data.prefix });
659
+
660
+ log.info(`Found ${files.length} files to process`);
661
+
662
+ const results = await processFilesInParallel(client, s3, data.bucket, files, {
663
+ batchSize: data.batchSize,
664
+ });
665
+
666
+ return {
667
+ filesProcessed: results.successful,
668
+ filesFailed: results.failed,
669
+ totalRecords: results.totalRecords,
670
+ };
671
+ })
672
+ );
673
+ ```
674
+
675
+ ### Backfill Strategies
676
+
677
+ Handle missed files or catch-up processing:
678
+
679
+ ```typescript
680
+ async function backfillMissedFiles(startDate: Date, endDate: Date, kv: any): Promise<void> {
681
+ const logger = toStructuredLogger(createConsoleLogger(), {
682
+ service: 'backfill',
683
+ correlationId: generateCorrelationId()
684
+ });
685
+
686
+ const stateService = new StateService(logger);
687
+ const kvAdapter = new VersoriKVAdapter(kv);
688
+
689
+ const s3 = new S3DataSource(
690
+ {
691
+ type: 'S3_CSV',
692
+ connectionId: 's3-backfill',
693
+ name: 'S3 Backfill',
694
+ s3Config: s3Config,
695
+ },
696
+ logger
697
+ );
698
+
699
+ console.log(`Backfilling files from ${startDate} to ${endDate}`);
700
+
701
+ // List all files in date range
702
+ const allFiles = await s3.listFiles({ prefix: 'data/' });
703
+
704
+ const filesInRange = allFiles.filter(file => {
705
+ const fileDate = extractDateFromKey(file.path);
706
+ return fileDate >= startDate && fileDate <= endDate;
707
+ });
708
+
709
+ console.log(`Found ${filesInRange.length} files in date range`);
710
+
711
+ // Check processing status
712
+ const unprocessedFiles = [];
713
+ for (const file of filesInRange) {
714
+ if (!(await stateService.isFileProcessed(kvAdapter, file.path))) {
715
+ unprocessedFiles.push(file);
716
+ }
717
+ }
718
+
719
+ console.log(`${unprocessedFiles.length} files need processing`);
720
+
721
+ // Process with lower concurrency to avoid overwhelming system
722
+ await processFilesInParallel(unprocessedFiles, 2); // Only 2 concurrent
723
+ }
724
+
725
+ function extractDateFromKey(key: string): Date {
726
+ // Extract date from key like "data/inventory-2025-01-15.csv"
727
+ const match = key.match(/(\d{4}-\d{2}-\d{2})/);
728
+ return match ? new Date(match[1]) : new Date(0);
729
+ }
730
+ ```
731
+
732
+ ## Data Quality and Validation
733
+
734
+ ### Pre-Ingestion Data Quality Checks
735
+
736
+ ```typescript
737
+ interface DataQualityRule {
738
+ name: string;
739
+ validate: (record: any) => { isValid: boolean; error?: string };
740
+ severity: 'error' | 'warning';
741
+ }
742
+
743
+ class DataQualityValidator {
744
+ private rules: DataQualityRule[] = [];
745
+
746
+ addRule(rule: DataQualityRule): void {
747
+ this.rules.push(rule);
748
+ }
749
+
750
+ validate(records: any[]): {
751
+ passed: any[];
752
+ failed: Array<{ record: any; errors: string[] }>;
753
+ warnings: Array<{ record: any; warnings: string[] }>;
754
+ } {
755
+ const passed: any[] = [];
756
+ const failed: Array<{ record: any; errors: string[] }> = [];
757
+ const warnings: Array<{ record: any; warnings: string[] }> = [];
758
+
759
+ for (const record of records) {
760
+ const errors: string[] = [];
761
+ const warns: string[] = [];
762
+
763
+ for (const rule of this.rules) {
764
+ const result = rule.validate(record);
765
+
766
+ if (!result.isValid) {
767
+ if (rule.severity === 'error') {
768
+ errors.push(`${rule.name}: ${result.error}`);
769
+ } else {
770
+ warns.push(`${rule.name}: ${result.error}`);
771
+ }
772
+ }
773
+ }
774
+
775
+ if (errors.length > 0) {
776
+ failed.push({ record, errors });
777
+ } else {
778
+ passed.push(record);
779
+
780
+ if (warns.length > 0) {
781
+ warnings.push({ record, warnings: warns });
782
+ }
783
+ }
784
+ }
785
+
786
+ return { passed, failed, warnings };
787
+ }
788
+ }
789
+
790
+ // Define quality rules
791
+ const qualityValidator = new DataQualityValidator();
792
+
793
+ qualityValidator.addRule({
794
+ name: 'Required Fields',
795
+ severity: 'error',
796
+ validate: record => {
797
+ const required = ['sku', 'warehouse', 'quantity'];
798
+ const missing = required.filter(field => !record[field]);
799
+
800
+ if (missing.length > 0) {
801
+ return {
802
+ isValid: false,
803
+ error: `Missing required fields: ${missing.join(', ')}`,
804
+ };
805
+ }
806
+
807
+ return { isValid: true };
808
+ },
809
+ });
810
+
811
+ qualityValidator.addRule({
812
+ name: 'Quantity Range',
813
+ severity: 'error',
814
+ validate: record => {
815
+ const qty = parseInt(record.quantity, 10);
816
+
817
+ if (isNaN(qty) || qty < 0) {
818
+ return {
819
+ isValid: false,
820
+ error: `Invalid quantity: ${record.quantity}`,
821
+ };
822
+ }
823
+
824
+ return { isValid: true };
825
+ },
826
+ });
827
+
828
+ qualityValidator.addRule({
829
+ name: 'SKU Format',
830
+ severity: 'error',
831
+ validate: record => {
832
+ const skuPattern = /^SKU-[A-Z0-9]{2,10}$/;
833
+
834
+ if (!skuPattern.test(record.sku)) {
835
+ return {
836
+ isValid: false,
837
+ error: `Invalid SKU format: ${record.sku}`,
838
+ };
839
+ }
840
+
841
+ return { isValid: true };
842
+ },
843
+ });
844
+
845
+ qualityValidator.addRule({
846
+ name: 'Suspiciously Large Quantity',
847
+ severity: 'warning',
848
+ validate: record => {
849
+ const qty = parseInt(record.quantity, 10);
850
+
851
+ if (qty > 100000) {
852
+ return {
853
+ isValid: false,
854
+ error: `Unusually large quantity: ${qty}`,
855
+ };
856
+ }
857
+
858
+ return { isValid: true };
859
+ },
860
+ });
861
+
862
+ // Usage
863
+ const records = await parseCSV(fileContent);
864
+ const validation = qualityValidator.validate(records);
865
+
866
+ console.log(`Passed: ${validation.passed.length}`);
867
+ console.log(`Failed: ${validation.failed.length}`);
868
+ console.log(`Warnings: ${validation.warnings.length}`);
869
+
870
+ // Log failures
871
+ if (validation.failed.length > 0) {
872
+ console.error('Data quality failures:');
873
+ validation.failed.forEach(({ record, errors }) => {
874
+ console.error(` SKU ${record.sku}:`, errors);
875
+ });
876
+
877
+ // Save failed records for review
878
+ await saveDataQualityReport(validation.failed);
879
+ }
880
+
881
+ // Proceed with passed records only
882
+ await processRecords(validation.passed);
883
+ ```
884
+
885
+ ### Reconciliation Reports
886
+
887
+ ```typescript
888
+ interface ReconciliationReport {
889
+ fileKey: string;
890
+ processedAt: string;
891
+ totalRecords: number;
892
+ successfulRecords: number;
893
+ failedRecords: number;
894
+ duplicateRecords: number;
895
+ dataQualityIssues: number;
896
+ batchesCreated: number;
897
+ processingTimeMs: number;
898
+ }
899
+
900
+ async function generateReconciliationReport(
901
+ fileKey: string,
902
+ results: ProcessingResult
903
+ ): Promise<ReconciliationReport> {
904
+ return {
905
+ fileKey,
906
+ processedAt: new Date().toISOString(),
907
+ totalRecords: results.totalRecords,
908
+ successfulRecords: results.successful,
909
+ failedRecords: results.failed,
910
+ duplicateRecords: results.duplicates || 0,
911
+ dataQualityIssues: results.qualityIssues || 0,
912
+ batchesCreated: results.batches,
913
+ processingTimeMs: results.processingTime,
914
+ };
915
+ }
916
+
917
+ // Save report to S3
918
+ async function saveReconciliationReport(report: ReconciliationReport): Promise<void> {
919
+ const s3 = new S3DataSource(
920
+ {
921
+ type: 'S3_JSON',
922
+ connectionId: 's3-reports',
923
+ name: 'S3 Reports',
924
+ s3Config: s3Config,
925
+ },
926
+ logger
927
+ );
928
+ const reportKey = `reports/reconciliation/${report.fileKey}-${Date.now()}.json`;
929
+
930
+ await s3.uploadFile(reportKey, JSON.stringify(report, null, 2));
931
+
932
+ console.log(`Reconciliation report saved: ${reportKey}`);
933
+ }
934
+ ```
935
+
936
+ ## Security Best Practices
937
+
938
+ ### Credential Management
939
+
940
+ ```typescript
941
+ // ✅ CORRECT - Use environment variables
942
+ const config = {
943
+ fluent: {
944
+ baseUrl: process.env.FLUENT_BASE_URL!,
945
+ clientId: process.env.FLUENT_CLIENT_ID!,
946
+ clientSecret: process.env.FLUENT_CLIENT_SECRET!,
947
+ retailerId: process.env.FLUENT_RETAILER_ID!,
948
+ },
949
+ s3: {
950
+ region: process.env.AWS_REGION!,
951
+ credentials: {
952
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
953
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
954
+ },
955
+ },
956
+ };
957
+
958
+ // ❌ WRONG - Never hardcode credentials
959
+ const badConfig = {
960
+ fluent: {
961
+ clientId: 'hardcoded-client-id', // ❌ Don't do this
962
+ clientSecret: 'hardcoded-secret', // ❌ Security risk
963
+ },
964
+ };
965
+ ```
966
+
967
+ ### Secrets Manager Integration
968
+
969
+ ```typescript
970
+ import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
971
+
972
+ async function loadSecretsFromAWS(): Promise<any> {
973
+ const client = new SecretsManagerClient({ region: 'us-east-1' });
974
+
975
+ const command = new GetSecretValueCommand({
976
+ SecretId: 'fluent-commerce/production',
977
+ });
978
+
979
+ const response = await client.send(command);
980
+ return JSON.parse(response.SecretString!);
981
+ }
982
+
983
+ // Usage
984
+ const secrets = await loadSecretsFromAWS();
985
+
986
+ const client = await createClient({
987
+ config: {
988
+ baseUrl: secrets.FLUENT_BASE_URL,
989
+ clientId: secrets.FLUENT_CLIENT_ID,
990
+ clientSecret: secrets.FLUENT_CLIENT_SECRET,
991
+ retailerId: secrets.FLUENT_RETAILER_ID,
992
+ }
993
+ });
994
+ ```
995
+
996
+ ### Data Encryption
997
+
998
+ ```typescript
999
+ // Encrypt sensitive data before storing
1000
+ import * as crypto from 'crypto';
1001
+
1002
+ function encryptSensitiveData(data: string, key: string): string {
1003
+ const iv = crypto.randomBytes(16);
1004
+ const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
1005
+
1006
+ let encrypted = cipher.update(data, 'utf8', 'hex');
1007
+ encrypted += cipher.final('hex');
1008
+
1009
+ return iv.toString('hex') + ':' + encrypted;
1010
+ }
1011
+
1012
+ function decryptSensitiveData(encrypted: string, key: string): string {
1013
+ const parts = encrypted.split(':');
1014
+ const iv = Buffer.from(parts[0], 'hex');
1015
+ const encryptedData = parts[1];
1016
+
1017
+ const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
1018
+
1019
+ let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
1020
+ decrypted += decipher.final('utf8');
1021
+
1022
+ return decrypted;
1023
+ }
1024
+ ```
1025
+
1026
+ ### Audit Logging
1027
+
1028
+ ```typescript
1029
+ interface AuditLog {
1030
+ timestamp: string;
1031
+ operation: string;
1032
+ user: string;
1033
+ resource: string;
1034
+ status: 'success' | 'failure';
1035
+ details: Record<string, any>;
1036
+ }
1037
+
1038
+ async function logAuditEvent(event: Omit<AuditLog, 'timestamp'>): Promise<void> {
1039
+ const auditLog: AuditLog = {
1040
+ timestamp: new Date().toISOString(),
1041
+ ...event,
1042
+ };
1043
+
1044
+ // Log to CloudWatch, Datadog, or custom logging service
1045
+ console.log('[AUDIT]', JSON.stringify(auditLog));
1046
+
1047
+ // Optionally save to S3 for long-term retention
1048
+ await saveAuditLog(auditLog);
1049
+ }
1050
+
1051
+ // Usage
1052
+ await logAuditEvent({
1053
+ operation: 'INVENTORY_INGESTION',
1054
+ user: process.env.USER || 'system',
1055
+ resource: `s3://bucket/file.csv`,
1056
+ status: 'success',
1057
+ details: {
1058
+ recordsProcessed: 1000,
1059
+ jobId: 'job-123',
1060
+ duration: 5000,
1061
+ },
1062
+ });
1063
+ ```
1064
+
1065
+ ## Monitoring and Alerting
1066
+
1067
+ ### Metrics Tracking
1068
+
1069
+ ```typescript
1070
+ interface IngestionMetrics {
1071
+ timestamp: string;
1072
+ filesProcessed: number;
1073
+ recordsIngested: number;
1074
+ failedRecords: number;
1075
+ processingTimeMs: number;
1076
+ avgRecordsPerSecond: number;
1077
+ errorRate: number;
1078
+ }
1079
+
1080
+ class MetricsCollector {
1081
+ private metrics: IngestionMetrics[] = [];
1082
+
1083
+ recordIngestion(result: IngestionResult): void {
1084
+ const metrics: IngestionMetrics = {
1085
+ timestamp: new Date().toISOString(),
1086
+ filesProcessed: result.filesProcessed,
1087
+ recordsIngested: result.recordsIngested,
1088
+ failedRecords: result.failedRecords,
1089
+ processingTimeMs: result.processingTimeMs,
1090
+ avgRecordsPerSecond: result.recordsIngested / (result.processingTimeMs / 1000),
1091
+ errorRate: result.failedRecords / result.recordsIngested
1092
+ };
1093
+
1094
+ this.metrics.push(metrics);
1095
+
1096
+ // Alert on high error rate
1097
+ if (metrics.errorRate > 0.05) { // > 5% error rate
1098
+ this.sendAlert('High error rate detected', metrics);
1099
+ }
1100
+
1101
+ // Alert on slow processing
1102
+ if (metrics.avgRecordsPerSecond < 10) {
1103
+ this.sendAlert('Slow processing speed', metrics);
1104
+ }
1105
+ }
1106
+
1107
+ private sendAlert(message: string, metrics: IngestionMetrics): void {
1108
+ console.error(\`[ALERT] \${message}\`, metrics);
1109
+ // Send to monitoring service (e.g., Datadog, New Relic)
1110
+ }
1111
+ }
1112
+ ```
1113
+
1114
+ ### Health Checks
1115
+
1116
+ ```typescript
1117
+ async function performHealthCheck(): Promise<HealthCheckResult> {
1118
+ const checks = [];
1119
+
1120
+ // Check Fluent API connectivity
1121
+ try {
1122
+ await client.graphql({
1123
+ query: '{ __typename }'
1124
+ });
1125
+ checks.push({ name: 'Fluent API', status: 'healthy' });
1126
+ } catch (error) {
1127
+ checks.push({ name: 'Fluent API', status: 'unhealthy', error: error.message });
1128
+ }
1129
+
1130
+ // Check S3 connectivity
1131
+ try {
1132
+ await s3.listFiles({ prefix: 'health-check/', maxKeys: 1 });
1133
+ checks.push({ name: 'S3', status: 'healthy' });
1134
+ } catch (error) {
1135
+ checks.push({ name: 'S3', status: 'unhealthy', error: error.message });
1136
+ }
1137
+
1138
+ // Check state storage
1139
+ try {
1140
+ await state.set('health-check', Date.now());
1141
+ await state.get('health-check');
1142
+ checks.push({ name: 'State Storage', status: 'healthy' });
1143
+ } catch (error) {
1144
+ checks.push({ name: 'State Storage', status: 'unhealthy', error: error.message });
1145
+ }
1146
+
1147
+ const allHealthy = checks.every(c => c.status === 'healthy');
1148
+
1149
+ return {
1150
+ status: allHealthy ? 'healthy' : 'degraded',
1151
+ checks,
1152
+ timestamp: new Date().toISOString(),
1153
+ };
1154
+ }
1155
+ ```
1156
+
1157
+ ## Complete Production Pipeline Example
1158
+
1159
+ Putting all best practices together into a production-ready ingestion pipeline:
1160
+
1161
+ ```typescript
1162
+ import {
1163
+ createClient,
1164
+ S3DataSource,
1165
+ CSVParserService,
1166
+ UniversalMapper,
1167
+ StateService,
1168
+ VersoriKVAdapter,
1169
+ FluentClient,
1170
+ FileParsingError,
1171
+ MappingError,
1172
+ BatchAPIError,
1173
+ } from '@fluentcommerce/fc-connect-sdk';
1174
+ import * as winston from 'winston';
1175
+
1176
+ /**
1177
+ * Production-grade ingestion pipeline with all best practices
1178
+ * - Error handling with retry and circuit breaker
1179
+ * - Comprehensive monitoring and logging
1180
+ * - Security best practices
1181
+ * - Data quality validation
1182
+ * - State management
1183
+ */
1184
+ class ProductionIngestionPipeline {
1185
+ private client: FluentClient;
1186
+ private s3: S3DataSource;
1187
+ private parser: CSVParserService;
1188
+ private mapper: UniversalMapper;
1189
+ private state: StateService;
1190
+ private kv: KVStore;
1191
+ private logger: winston.Logger;
1192
+ private circuitBreaker: CircuitBreaker;
1193
+ private dlq: DeadLetterQueue;
1194
+ private metrics: MetricsCollector;
1195
+ private qualityValidator: DataQualityValidator;
1196
+
1197
+ constructor(private config: ProductionConfig) {}
1198
+
1199
+ async initialize(): Promise<void> {
1200
+ // Setup structured logging
1201
+ this.logger = winston.createLogger({
1202
+ level: 'info',
1203
+ format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
1204
+ transports: [
1205
+ new winston.transports.Console(),
1206
+ new winston.transports.File({ filename: 'ingestion-error.log', level: 'error' }),
1207
+ new winston.transports.File({ filename: 'ingestion-combined.log' }),
1208
+ ],
1209
+ });
1210
+
1211
+ this.logger.info('Initializing production ingestion pipeline');
1212
+
1213
+ // Initialize Fluent client
1214
+ this.client = await createClient({ config: this.config.fluent });
1215
+
1216
+ // Initialize data sources
1217
+ this.s3 = new S3DataSource(
1218
+ {
1219
+ type: 'S3_CSV',
1220
+ connectionId: 's3-production',
1221
+ name: 'S3 Production',
1222
+ s3Config: this.config.s3,
1223
+ },
1224
+ this.logger
1225
+ );
1226
+ this.parser = new CSVParserService();
1227
+
1228
+ // Initialize field mapper with custom resolvers
1229
+ this.mapper = new UniversalMapper(this.config.mapping, {
1230
+ customResolvers: this.config.customResolvers || {},
1231
+ });
1232
+
1233
+ // Initialize state management
1234
+ const logger = toStructuredLogger(this.logger, {
1235
+ service: 'production-pipeline',
1236
+ correlationId: generateCorrelationId()
1237
+ });
1238
+
1239
+ this.kv = new VersoriKVAdapter(this.config.kv);
1240
+ this.stateService = new StateService(logger);
1241
+
1242
+ // Initialize circuit breaker
1243
+ this.circuitBreaker = new CircuitBreaker({
1244
+ failureThreshold: 5,
1245
+ resetTimeoutMs: 60000,
1246
+ monitoringWindowMs: 120000,
1247
+ });
1248
+
1249
+ // Initialize dead letter queue
1250
+ this.dlq = new DeadLetterQueue(this.s3, this.config.dlqBucket, 'dlq/');
1251
+
1252
+ // Initialize metrics collector
1253
+ this.metrics = new MetricsCollector();
1254
+
1255
+ // Initialize data quality validator
1256
+ this.qualityValidator = this.createQualityValidator();
1257
+
1258
+ this.logger.info('Pipeline initialization complete');
1259
+ }
1260
+
1261
+ /**
1262
+ * Run the complete ingestion pipeline
1263
+ */
1264
+ async run(bucket: string, prefix: string): Promise<ExecutionReport> {
1265
+ const startTime = Date.now();
1266
+
1267
+ try {
1268
+ this.logger.info('Starting ingestion pipeline', { bucket, prefix });
1269
+
1270
+ // Health check before starting
1271
+ const healthCheck = await this.performHealthCheck();
1272
+ if (healthCheck.status !== 'healthy') {
1273
+ throw new Error(`System unhealthy: ${JSON.stringify(healthCheck.checks)}`);
1274
+ }
1275
+
1276
+ // List files
1277
+ const allFiles = await this.s3.listFiles({ prefix });
1278
+ this.logger.info(`Found ${allFiles.length} total files`);
1279
+
1280
+ // Filter unprocessed files
1281
+ const unprocessedFiles = await this.filterUnprocessedFiles(allFiles);
1282
+ this.logger.info(`${unprocessedFiles.length} files to process`);
1283
+
1284
+ if (unprocessedFiles.length === 0) {
1285
+ return this.createReport(startTime, { filesProcessed: 0 });
1286
+ }
1287
+
1288
+ // Process files with circuit breaker
1289
+ const results = await this.circuitBreaker.execute(async () => {
1290
+ return await this.processFilesWithRetry(bucket, unprocessedFiles);
1291
+ });
1292
+
1293
+ // Generate report
1294
+ const report = this.createReport(startTime, results);
1295
+
1296
+ // Log metrics
1297
+ this.metrics.recordIngestion({
1298
+ filesProcessed: results.filesProcessed,
1299
+ recordsIngested: results.recordsIngested,
1300
+ failedRecords: results.failedRecords,
1301
+ processingTimeMs: report.totalTimeMs,
1302
+ avgRecordsPerSecond: report.throughput,
1303
+ errorRate: results.failedRecords / (results.recordsIngested || 1),
1304
+ });
1305
+
1306
+ this.logger.info('Pipeline execution complete', report);
1307
+
1308
+ return report;
1309
+ } catch (error) {
1310
+ this.logger.error('Pipeline execution failed', {
1311
+ error: error.message,
1312
+ stack: error.stack,
1313
+ });
1314
+
1315
+ await this.sendAlert('CRITICAL: Ingestion pipeline failure', error);
1316
+ throw error;
1317
+ }
1318
+ }
1319
+
1320
+ /**
1321
+ * Process files with retry logic
1322
+ */
1323
+ private async processFilesWithRetry(
1324
+ bucket: string,
1325
+ files: Array<{ key: string }>
1326
+ ): Promise<ProcessingResults> {
1327
+ const results: ProcessingResults = {
1328
+ filesProcessed: 0,
1329
+ filesFailed: 0,
1330
+ recordsIngested: 0,
1331
+ failedRecords: 0,
1332
+ details: [],
1333
+ };
1334
+
1335
+ for (const file of files) {
1336
+ const maxRetries = 3;
1337
+ let attempt = 0;
1338
+ let success = false;
1339
+
1340
+ while (attempt < maxRetries && !success) {
1341
+ attempt++;
1342
+
1343
+ try {
1344
+ const fileResult = await this.processFile(bucket, file.path);
1345
+
1346
+ results.filesProcessed++;
1347
+ results.recordsIngested += fileResult.recordsProcessed;
1348
+ results.details.push({
1349
+ file: file.path,
1350
+ status: 'success',
1351
+ records: fileResult.recordsProcessed,
1352
+ });
1353
+
1354
+ success = true;
1355
+
1356
+ this.logger.info(`File processed successfully`, {
1357
+ file: file.path,
1358
+ records: fileResult.recordsProcessed,
1359
+ attempt,
1360
+ });
1361
+ } catch (error) {
1362
+ this.logger.warn(`File processing attempt ${attempt} failed`, {
1363
+ file: file.path,
1364
+ error: error.message,
1365
+ });
1366
+
1367
+ if (attempt < maxRetries && this.isRetryable(error)) {
1368
+ const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
1369
+ await new Promise(resolve => setTimeout(resolve, delay));
1370
+ } else {
1371
+ results.filesFailed++;
1372
+ results.details.push({
1373
+ file: file.path,
1374
+ status: 'failed',
1375
+ error: error.message,
1376
+ });
1377
+
1378
+ this.logger.error(`File processing failed permanently`, {
1379
+ file: file.path,
1380
+ attempts: attempt,
1381
+ error: error.message,
1382
+ });
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ return results;
1389
+ }
1390
+
1391
+ /**
1392
+ * Process a single file with all validations
1393
+ */
1394
+ private async processFile(bucket: string, fileKey: string): Promise<FileProcessingResult> {
1395
+ this.logger.info(`Processing file: ${fileKey}`);
1396
+
1397
+ try {
1398
+ // Read file
1399
+ const fileContent = await this.s3.downloadFile(fileKey);
1400
+
1401
+ // Parse CSV
1402
+ const records = await this.parser.parse(fileContent);
1403
+ this.logger.debug(`Parsed ${records.length} records from ${fileKey}`);
1404
+
1405
+ // Data quality validation
1406
+ const validation = this.qualityValidator.validate(records);
1407
+
1408
+ if (validation.failed.length > 0) {
1409
+ this.logger.warn(`Data quality issues found`, {
1410
+ file: fileKey,
1411
+ failed: validation.failed.length,
1412
+ warnings: validation.warnings.length,
1413
+ });
1414
+
1415
+ // Save failed records to DLQ
1416
+ for (const { record, errors } of validation.failed) {
1417
+ await this.dlq.save({
1418
+ originalFile: fileKey,
1419
+ failedAt: new Date().toISOString(),
1420
+ errorType: 'DATA_QUALITY_ERROR',
1421
+ errorMessage: errors.join('; '),
1422
+ recordData: record,
1423
+ retryCount: 0,
1424
+ metadata: {},
1425
+ });
1426
+ }
1427
+ }
1428
+
1429
+ // Proceed with passed records only
1430
+ if (validation.passed.length === 0) {
1431
+ throw new Error('No valid records after quality validation');
1432
+ }
1433
+
1434
+ // Field mapping
1435
+ const mappingResult = await this.mapper.map(validation.passed);
1436
+
1437
+ if (!mappingResult.success) {
1438
+ throw new MappingError(`Field mapping failed: ${mappingResult.errors.join(', ')}`);
1439
+ }
1440
+
1441
+ // Create job
1442
+ const job = await this.client.createJob({
1443
+ name: `Production Import - ${fileKey}`,
1444
+ retailerId: this.config.fluent.retailerId,
1445
+ metadata: {
1446
+ fileName: fileKey,
1447
+ recordCount: mappingResult.data.length,
1448
+ pipeline: 'production',
1449
+ },
1450
+ });
1451
+
1452
+ // Send to Batch API
1453
+ await this.client.sendBatch(job.id, {
1454
+ action: 'UPSERT',
1455
+ entityType: 'INVENTORY',
1456
+ entities: mappingResult.data,
1457
+ });
1458
+
1459
+ // Mark as processed
1460
+ await this.state.markFileProcessed(fileKey, {
1461
+ jobId: job.id,
1462
+ recordCount: mappingResult.data.length,
1463
+ timestamp: new Date().toISOString(),
1464
+ });
1465
+
1466
+ // Audit log
1467
+ await this.logAuditEvent({
1468
+ operation: 'INVENTORY_INGESTION',
1469
+ user: 'system',
1470
+ resource: `s3://${bucket}/${fileKey}`,
1471
+ status: 'success',
1472
+ details: {
1473
+ recordsProcessed: mappingResult.data.length,
1474
+ jobId: job.id,
1475
+ },
1476
+ });
1477
+
1478
+ return {
1479
+ recordsProcessed: mappingResult.data.length,
1480
+ jobId: job.id,
1481
+ };
1482
+ } catch (error) {
1483
+ // Audit log failure
1484
+ await this.logAuditEvent({
1485
+ operation: 'INVENTORY_INGESTION',
1486
+ user: 'system',
1487
+ resource: `s3://${bucket}/${fileKey}`,
1488
+ status: 'failure',
1489
+ details: {
1490
+ error: error.message,
1491
+ },
1492
+ });
1493
+
1494
+ throw error;
1495
+ }
1496
+ }
1497
+
1498
+ /**
1499
+ * Check if error is retryable
1500
+ */
1501
+ private isRetryable(error: any): boolean {
1502
+ if (error instanceof FileParsingError) {
1503
+ return false; // File format errors are not retryable
1504
+ }
1505
+
1506
+ if (error instanceof BatchAPIError) {
1507
+ return error.isRetryable;
1508
+ }
1509
+
1510
+ // Network errors are retryable
1511
+ return (
1512
+ error.code === 'NETWORK_ERROR' ||
1513
+ error.message.includes('timeout') ||
1514
+ error.message.includes('ECONNRESET')
1515
+ );
1516
+ }
1517
+
1518
+ /**
1519
+ * Filter unprocessed files
1520
+ */
1521
+ private async filterUnprocessedFiles(
1522
+ files: FileMetadata[]
1523
+ ): Promise<FileMetadata[]> {
1524
+ const unprocessed = [];
1525
+
1526
+ for (const file of files) {
1527
+ if (!(await this.state.isFileProcessed(this.kv, file.path))) {
1528
+ unprocessed.push(file);
1529
+ }
1530
+ }
1531
+
1532
+ return unprocessed;
1533
+ }
1534
+
1535
+ /**
1536
+ * Create data quality validator
1537
+ */
1538
+ private createQualityValidator(): DataQualityValidator {
1539
+ const validator = new DataQualityValidator();
1540
+
1541
+ // Add validation rules based on configuration
1542
+ validator.addRule({
1543
+ name: 'Required Fields',
1544
+ severity: 'error',
1545
+ validate: record => {
1546
+ const required = this.config.requiredFields || [];
1547
+ const missing = required.filter(field => !record[field]);
1548
+
1549
+ if (missing.length > 0) {
1550
+ return {
1551
+ isValid: false,
1552
+ error: `Missing required fields: ${missing.join(', ')}`,
1553
+ };
1554
+ }
1555
+
1556
+ return { isValid: true };
1557
+ },
1558
+ });
1559
+
1560
+ return validator;
1561
+ }
1562
+
1563
+ /**
1564
+ * Perform system health check
1565
+ */
1566
+ private async performHealthCheck(): Promise<HealthCheckResult> {
1567
+ const checks = [];
1568
+
1569
+ // Check Fluent API
1570
+ try {
1571
+ await this.client.graphql({
1572
+ query: '{ __typename }'
1573
+ });
1574
+ checks.push({ name: 'Fluent API', status: 'healthy' });
1575
+ } catch (error) {
1576
+ checks.push({ name: 'Fluent API', status: 'unhealthy', error: error.message });
1577
+ }
1578
+
1579
+ // Check S3
1580
+ try {
1581
+ await this.s3.listObjects(this.config.s3Bucket, 'health-check/');
1582
+ checks.push({ name: 'S3', status: 'healthy' });
1583
+ } catch (error) {
1584
+ checks.push({ name: 'S3', status: 'unhealthy', error: error.message });
1585
+ }
1586
+
1587
+ const allHealthy = checks.every(c => c.status === 'healthy');
1588
+
1589
+ return {
1590
+ status: allHealthy ? 'healthy' : 'degraded',
1591
+ checks,
1592
+ timestamp: new Date().toISOString(),
1593
+ };
1594
+ }
1595
+
1596
+ /**
1597
+ * Create execution report
1598
+ */
1599
+ private createReport(startTime: number, results: Partial<ProcessingResults>): ExecutionReport {
1600
+ const totalTimeMs = Date.now() - startTime;
1601
+
1602
+ return {
1603
+ startTime: new Date(startTime).toISOString(),
1604
+ endTime: new Date().toISOString(),
1605
+ totalTimeMs,
1606
+ filesProcessed: results.filesProcessed || 0,
1607
+ filesFailed: results.filesFailed || 0,
1608
+ recordsIngested: results.recordsIngested || 0,
1609
+ failedRecords: results.failedRecords || 0,
1610
+ throughput: (results.recordsIngested || 0) / (totalTimeMs / 1000),
1611
+ successRate:
1612
+ ((results.filesProcessed || 0) /
1613
+ ((results.filesProcessed || 0) + (results.filesFailed || 0))) *
1614
+ 100,
1615
+ };
1616
+ }
1617
+
1618
+ /**
1619
+ * Log audit event
1620
+ */
1621
+ private async logAuditEvent(event: Omit<AuditLog, 'timestamp'>): Promise<void> {
1622
+ const auditLog: AuditLog = {
1623
+ timestamp: new Date().toISOString(),
1624
+ ...event,
1625
+ };
1626
+
1627
+ this.logger.info('[AUDIT]', auditLog);
1628
+
1629
+ // Optionally save to S3 for compliance
1630
+ // await this.s3.putObject(auditBucket, auditKey, JSON.stringify(auditLog));
1631
+ }
1632
+
1633
+ /**
1634
+ * Send alert to monitoring service
1635
+ */
1636
+ private async sendAlert(message: string, error: any): Promise<void> {
1637
+ this.logger.error('[ALERT]', { message, error: error.message });
1638
+
1639
+ // Integration with alerting service (PagerDuty, Slack, etc.)
1640
+ // await alertService.send({ message, error });
1641
+ }
1642
+ }
1643
+
1644
+ // Usage
1645
+ const pipeline = new ProductionIngestionPipeline({
1646
+ fluent: {
1647
+ baseUrl: process.env.FLUENT_BASE_URL!,
1648
+ clientId: process.env.FLUENT_CLIENT_ID!,
1649
+ clientSecret: process.env.FLUENT_CLIENT_SECRET!,
1650
+ retailerId: process.env.FLUENT_RETAILER_ID!,
1651
+ },
1652
+ s3: {
1653
+ region: process.env.AWS_REGION!,
1654
+ credentials: {
1655
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
1656
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
1657
+ },
1658
+ },
1659
+ s3Bucket: 'inventory-bucket',
1660
+ dlqBucket: 'inventory-dlq-bucket',
1661
+ mapping: {
1662
+ fields: {
1663
+ ref: { source: 'sku', required: true },
1664
+ productRef: { source: 'product_id', required: true },
1665
+ locationRef: { source: 'warehouse_code', required: true },
1666
+ qty: { source: 'quantity', resolver: 'sdk.parseInt', required: true },
1667
+ type: { source: 'inventory_type', default: 'ON_HAND' },
1668
+ status: { source: 'status', default: 'AVAILABLE' },
1669
+ },
1670
+ },
1671
+ requiredFields: ['sku', 'warehouse', 'quantity'],
1672
+ kv: openKv(),
1673
+ });
1674
+
1675
+ await pipeline.initialize();
1676
+ const report = await pipeline.run('inventory-bucket', 'data/');
1677
+
1678
+ console.log('Ingestion complete:', report);
1679
+ ```
1680
+
1681
+ ## Common Errors and Solutions
1682
+
1683
+ ### Error: JOB_EXPIRED
1684
+
1685
+ **Cause:** Job exceeded 24-hour lifetime
1686
+
1687
+ **Solution:**
1688
+
1689
+ ```typescript
1690
+ async function handleExpiredJob(originalJobId: string, remainingBatches: any[]) {
1691
+ // Create new job
1692
+ const newJob = await client.createJob({
1693
+ name: \`Recovery - \${new Date().toISOString()}\`,
1694
+ retailerId: process.env.FLUENT_RETAILER_ID!,
1695
+ metadata: {
1696
+ originalJobId,
1697
+ reason: 'JOB_EXPIRED'
1698
+ }
1699
+ });
1700
+
1701
+ // Resend remaining batches
1702
+ for (const batch of remainingBatches) {
1703
+ await client.sendBatch(newJob.id, batch);
1704
+ }
1705
+
1706
+ return newJob.id;
1707
+ }
1708
+ ```
1709
+
1710
+ ### Error: VALIDATION_ERROR
1711
+
1712
+ **Cause:** Invalid field values or missing required fields
1713
+
1714
+ **Solution:**
1715
+
1716
+ ```typescript
1717
+ async function handleValidationError(records: any[], error: any) {
1718
+ // Parse validation errors
1719
+ const invalidRecords = extractInvalidRecords(error);
1720
+
1721
+ // Filter out invalid records
1722
+ const validRecords = records.filter(r => !invalidRecords.includes(r));
1723
+
1724
+ // Log invalid records
1725
+ console.error('Invalid records:', invalidRecords);
1726
+ await saveToErrorLog(invalidRecords);
1727
+
1728
+ // Retry with valid records only
1729
+ return validRecords;
1730
+ }
1731
+ ```
1732
+
1733
+ ### Error: RATE_LIMIT_ERROR
1734
+
1735
+ **Cause:** Too many requests to Fluent API
1736
+
1737
+ **Solution:**
1738
+
1739
+ ```typescript
1740
+ class RateLimitHandler {
1741
+ async handleRateLimit(operation: () => Promise<any>): Promise<any> {
1742
+ try {
1743
+ return await operation();
1744
+ } catch (error) {
1745
+ if (error.code === 'RATE_LIMIT_ERROR') {
1746
+ const retryAfter = error.retryAfter || 60000; // Default 60s
1747
+ console.log(\`Rate limited. Waiting \${retryAfter}ms\`);
1748
+ await sleep(retryAfter);
1749
+ return await operation(); // Retry once
1750
+ }
1751
+ throw error;
1752
+ }
1753
+ }
1754
+ }
1755
+ ```
1756
+
1757
+ ## Production Checklist
1758
+
1759
+ ### Before Deployment
1760
+
1761
+ - [ ] Schema validated against Fluent Commerce API
1762
+ - [ ] Field mappings tested with sample data
1763
+ - [ ] Error handling implemented for all operations
1764
+ - [ ] State management configured and tested
1765
+ - [ ] Monitoring and alerting configured
1766
+ - [ ] Health checks implemented
1767
+ - [ ] Rate limiting configured
1768
+ - [ ] Dead letter queue configured
1769
+ - [ ] Rollback plan documented
1770
+
1771
+ ### During Deployment
1772
+
1773
+ - [ ] Start with small batch sizes (100-500)
1774
+ - [ ] Monitor error rates closely
1775
+ - [ ] Test with sample files first
1776
+ - [ ] Gradually increase batch sizes
1777
+ - [ ] Verify state management works correctly
1778
+
1779
+ ### After Deployment
1780
+
1781
+ - [ ] Monitor ingestion metrics daily
1782
+ - [ ] Review error logs regularly
1783
+ - [ ] Track processing times
1784
+ - [ ] Optimize based on metrics
1785
+ - [ ] Document common issues and solutions
1786
+
1787
+ ## Debugging Tips
1788
+
1789
+ ### Enable Debug Logging
1790
+
1791
+ ```typescript
1792
+ const client = await createClient({
1793
+ config: {
1794
+ ...config
1795
+ },
1796
+ logger: {
1797
+ debug: (...args) => console.log('[DEBUG]', ...args),
1798
+ info: (...args) => console.log('[INFO]', ...args),
1799
+ warn: (...args) => console.warn('[WARN]', ...args),
1800
+ error: (...args) => console.error('[ERROR]', ...args),
1801
+ },
1802
+ });
1803
+ ```
1804
+
1805
+ ### Trace File Processing
1806
+
1807
+ ```typescript
1808
+ async function traceFileProcessing(fileKey: string) {
1809
+ console.log(\`[TRACE] Starting processing: \${fileKey}\`);
1810
+
1811
+ try {
1812
+ console.log('[TRACE] Reading file from S3');
1813
+ const data = await s3.downloadFile(fileKey);
1814
+ console.log(\`[TRACE] File size: \${data.length} bytes\`);
1815
+
1816
+ console.log('[TRACE] Parsing CSV');
1817
+ const records = await parser.parse(data);
1818
+ console.log(\`[TRACE] Parsed \${records.length} records\`);
1819
+
1820
+ console.log('[TRACE] Mapping fields');
1821
+ const result = await mapper.map(records);
1822
+ console.log(\`[TRACE] Mapped \${result.data.length} valid records\`);
1823
+
1824
+ console.log('[TRACE] Creating job');
1825
+ const job = await client.createJob({ name: \`Import - \${fileKey}\` });
1826
+ console.log(\`[TRACE] Job created: \${job.id}\`);
1827
+
1828
+ console.log('[TRACE] Sending batch');
1829
+ await client.sendBatch(job.id, { entities: result.data });
1830
+ console.log('[TRACE] Batch sent successfully');
1831
+
1832
+ } catch (error) {
1833
+ console.error(\`[TRACE] Error at: \${error.message}\`);
1834
+ throw error;
1835
+ }
1836
+ }
1837
+ ```
1838
+
1839
+ ## Key Takeaways
1840
+
1841
+ - 🎯 **Always validate** - Check schema and data before sending
1842
+ - 🎯 **Handle errors gracefully** - Retry transient failures, log permanent ones
1843
+ - 🎯 **Monitor everything** - Track metrics, set up alerts
1844
+ - 🎯 **Use state management** - Prevent duplicates, track history
1845
+ - 🎯 **Test thoroughly** - Start small, scale gradually
1846
+ - 🎯 **Document** - Keep runbooks for common issues
1847
+
1848
+ ## Congratulations!
1849
+
1850
+ You've completed the Data Ingestion Learning Path! You now know:
1851
+
1852
+ - ✅ Core ingestion concepts and architecture
1853
+ - ✅ How to read from multiple data sources
1854
+ - ✅ How to parse CSV, Parquet, XML, and JSON
1855
+ - ✅ How to transform data with UniversalMapper
1856
+ - ✅ How to use the Batch API effectively
1857
+ - ✅ How to implement state management
1858
+ - ✅ How to optimize performance
1859
+ - ✅ How to build production-ready ingestion workflows
1860
+
1861
+ ## Next Steps
1862
+
1863
+ Congratulations! You've completed the Data Ingestion Learning Path and mastered production-ready ingestion patterns.
1864
+
1865
+ **Continue Learning:**
1866
+
1867
+ - 📖 [Data Extraction Guide](../../extraction/) - Learn reverse workflows (Fluent → S3/Parquet/CSV)
1868
+ - 📖 [Resolver Development Guide](../../mapping/resolvers/mapping-resolvers-readme.md) - Build custom data transformations
1869
+ - 📖 [Universal Mapping Guide](../../mapping/mapping-readme.md) - Master field mapping patterns
1870
+ - 📖 [Error Handling Guide](../../../03-PATTERN-GUIDES/error-handling/error-handling-readme.md) - Deep dive into error patterns
1871
+ - 📖 [Versori Platform Integration](../../../04-REFERENCE/platforms/versori/) - Deploy to production
1872
+
1873
+ **Real-World Examples:**
1874
+
1875
+ - 🔍 [Complete Connector Examples](../../../01-TEMPLATES/versori/workflows/readme.md) - Production Versori connectors
1876
+ - 📋 [Use Case Library](../../../01-TEMPLATES/readme.md) - Business-specific implementations
1877
+ - 🛠️ [CLI Tools](../../../../bin/) - Code generation and validation utilities
1878
+
1879
+ **Resources:**
1880
+
1881
+ - 📚 [Complete API Reference](../../api-reference/api-reference-readme.md)
1882
+ - 📖 [Quick Reference Cheat Sheet](../ingestion-quick-reference.md)
1883
+ - 🐛 [Troubleshooting Guide](../../../00-START-HERE/troubleshooting-quick-reference.md)
1884
+
1885
+ ---
1886
+
1887
+ [← Back to Ingestion Guide](../ingestion-readme.md) | [Previous: Module 8 - Performance Optimization](./02-core-guides-ingestion-08-performance-optimization.md)
1888
+
1889
+ **Need Help?**
1890
+
1891
+ - 📧 Email: support@fluentcommerce.com
1892
+ - 📚 Documentation: [FC Connect SDK Docs](../../../)
1893
+ - 🔗 GitHub: Report issues or contribute