@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,1533 +1,1533 @@
1
- # Versori KV State Management
2
-
3
- **FC Connect SDK Use Case Guide**
4
-
5
- > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
6
- > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
7
-
8
- **Context**: Use Versori KV storage for duplicate prevention, checkpoints, and file tracking
9
-
10
- **Complexity**: Low-Medium
11
-
12
- **Runtime**: Versori Platform
13
-
14
- **Estimated Lines**: ~400 lines
15
-
16
- ## What You'll Build
17
-
18
- - VersoriKV adapter setup
19
- - File tracking (prevent duplicates)
20
- - Checkpoint/resume capabilities
21
- - Batch processing state
22
- - Error tracking and retry logic
23
-
24
- ## SDK Methods Used
25
-
26
- - `VersoriKVAdapter(openKv())` - Wrap Versori KV for StateService
27
- - `VersoriFileTracker(openKv())` - Simple file tracking
28
- - `VersoriIndexedFileTracker(openKv())` - File tracking with listing support
29
- - `StateService(logger)` - High-level state operations
30
- - `stateService.isFileProcessed(kv, key)` - Check if processed
31
- - `stateService.acquireLock(lockName, kv, timeoutMinutes)` - Distributed locking
32
- - `stateService.getSyncState(kv, workflowId)` - Get sync state
33
- - `stateService.updateSyncState(kv, files, workflowId)` - Update sync state
34
- - `stateService.getDailyJob(kv, workflowId)` - Get daily job
35
- - `stateService.setDailyJob(kv, workflowId, jobId, hours)` - Store daily job
36
-
37
- ---
38
-
39
- ## Versori Workflows Structure
40
-
41
- **Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
42
-
43
- **Trigger Types:**
44
- - **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
45
- - **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
46
- - **`http()`** → External API calls (chained from webhook/schedule)
47
- - **`fn()`** → Internal processing (chained from webhook/schedule)
48
-
49
- ### Recommended Project Structure
50
-
51
- ```
52
- kv-state-management/
53
- ├── index.ts # Entry point - exports all workflows
54
- └── src/
55
- ├── workflows/
56
- │ ├── webhook/
57
- │ │ └── file-ingestion.ts # Webhook: File processing
58
- │ │
59
- │ └── scheduled/
60
- │ └── daily-sync.ts # Scheduled: Daily sync
61
-
62
- ├── services/
63
- │ └── state-management.service.ts # Shared state logic (reusable)
64
-
65
- └── config/
66
- └── state-config.json # Configuration
67
- ```
68
-
69
- **Benefits:**
70
- - ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
71
- - ✅ Descriptive file names (easy to browse and understand)
72
- - ✅ Scalable (add new workflows without cluttering)
73
- - ✅ Reusable code in `services/` (DRY principle)
74
- - ✅ Easy to modify individual workflows without affecting others
75
-
76
- ---
77
-
78
- ## Complete Working Code
79
-
80
- ### Pattern 1: Simple File Duplicate Prevention
81
-
82
- **Use Case**: Prevent reprocessing the same files in ingestion workflows.
83
-
84
- ```typescript
85
- import { webhook, fn } from '@versori/run';
86
- // FC Connect SDK+
87
- // Install: npm install @fluentcommerce/fc-connect-sdk@latest
88
- // Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
89
- // GitHub: https://github.com/fluentcommerce/fc-connect-sdk
90
- import { VersoriFileTracker } from '@fluentcommerce/fc-connect-sdk';
91
-
92
- /**
93
- * Simple file tracking pattern
94
- * Perfect for basic duplicate prevention without complex state management
95
- */
96
- export const simpleFileIngestion = webhook('ingest-files', {
97
- response: { mode: 'sync' }
98
- })
99
- .then(fn('check-and-process-files', async (ctx) => {
100
- const { openKv, log, data } = ctx;
101
-
102
- // Initialize simple file tracker
103
- const fileTracker = new VersoriFileTracker(openKv(':project:'), 'inventory-ingestion');
104
-
105
- // Sample files to process
106
- const files = data.files || [
107
- { name: 'inventory-2025-01-15.csv', url: 's3://bucket/file1.csv' },
108
- { name: 'inventory-2025-01-16.csv', url: 's3://bucket/file2.csv' }
109
- ];
110
-
111
- const processedFiles = [];
112
- const skippedFiles = [];
113
-
114
- for (const file of files) {
115
- // Check if file was already processed
116
- const wasProcessed = await fileTracker.wasFileProcessed(file.name);
117
-
118
- if (wasProcessed) {
119
- log.info(`⏭️ [FileTracking] Skipping already processed file: ${file.name}`);
120
- skippedFiles.push(file.name);
121
- continue;
122
- }
123
-
124
- try {
125
- // Process the file (your ingestion logic here)
126
- log.info(`🚀 [Processing] Processing file: ${file.name}`);
127
- const recordCount = await processFile(file);
128
-
129
- // Mark as processed with metadata
130
- await fileTracker.markFileProcessed(file.name, {
131
- recordCount,
132
- processedBy: 'webhook-ingestion',
133
- source: file.url
134
- });
135
-
136
- processedFiles.push({ name: file.name, recordCount });
137
- log.info(`✅ [Success] Successfully processed: ${file.name} (${recordCount} records)`);
138
- } catch (error) {
139
- // ? Enhanced: Error logging with recommendations
140
- log.error('[KVStateManagement] Failed to process file', {
141
- fileName: file.name,
142
- error: error instanceof Error ? error.message : String(error),
143
- errorType: error instanceof Error ? error.constructor.name : 'Error',
144
- recommendation: error.message?.includes('parse') || error.message?.includes('format')
145
- ? 'Check file format and structure - ensure file is valid'
146
- : error.message?.includes('mapping') || error.message?.includes('field')
147
- ? 'Check mapping configuration and verify file column structure'
148
- : error.message?.includes('connection') || error.message?.includes('timeout')
149
- ? 'Check network connectivity and data source availability'
150
- : 'Review error details and check file processing logic'
151
- });
152
- throw error; // Fail workflow so we can retry later
153
- }
154
- }
155
-
156
- // Track last processed file
157
- if (processedFiles.length > 0) {
158
- const lastFile = processedFiles[processedFiles.length - 1];
159
- await fileTracker.setLastProcessedFile(lastFile.name);
160
- }
161
-
162
- return {
163
- success: true,
164
- processed: processedFiles,
165
- skipped: skippedFiles,
166
- lastProcessedFile: await fileTracker.getLastProcessedFile()
167
- };
168
- }));
169
-
170
- // Helper function (implement based on your data source)
171
- async function processFile(file: { name: string; url: string }): Promise<number> {
172
- // Your file processing logic
173
- return 1000; // Return record count
174
- }
175
- ```
176
-
177
- **Key Points**:
178
-
179
- - `VersoriFileTracker` is lightweight - perfect for simple use cases
180
- - Automatic duplicate prevention with `wasFileProcessed()`
181
- - Stores metadata like record count with each file
182
- - Tracks last processed file for incremental processing
183
-
184
- ---
185
-
186
- ### Pattern 2: Distributed Locking with StateService
187
-
188
- **Use Case**: Prevent concurrent workflow executions with distributed locks.
189
-
190
- ```typescript
191
- import { schedule, fn } from '@versori/run';
192
- import { StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
193
-
194
- /**
195
- * Distributed locking pattern
196
- * Prevents multiple instances from running simultaneously
197
- */
198
- export const scheduledIngestionWithLock = schedule('inventory-sync', '0 */6 * * *')
199
- .then(fn('acquire-lock', async (ctx) => {
200
- const { openKv, log } = ctx;
201
-
202
- // Create StateService with KV adapter
203
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
204
- const stateService = new StateService(log);
205
-
206
- // Try to acquire lock (15 minute timeout)
207
- const lockName = 'inventory-sync-lock';
208
- const lockAcquired = await stateService.acquireLock(lockName, kvAdapter, 15);
209
-
210
- if (!lockAcquired) {
211
- log.warn('⚠️ [Lock] Lock already held, skipping this run');
212
- return {
213
- shouldSkip: true,
214
- reason: 'Lock already held by another instance'
215
- };
216
- }
217
-
218
- log.info('🔐 [Lock] Lock acquired successfully');
219
- return {
220
- shouldSkip: false,
221
- lockName,
222
- kvAdapter,
223
- stateService
224
- };
225
- }))
226
- .then(fn('process-with-lock', async ({ data, log }) => {
227
- if (data.shouldSkip) {
228
- return data;
229
- }
230
-
231
- const { kvAdapter, stateService, lockName } = data;
232
-
233
- try {
234
- // Your ingestion logic here
235
- log.info('🚀 [Processing] Processing inventory sync...');
236
- await performIngestion();
237
- log.info('✅ [Success] Ingestion completed successfully');
238
-
239
- return { success: true };
240
- } finally {
241
- // ALWAYS release lock in finally block
242
- await stateService.releaseLock(lockName, kvAdapter);
243
- log.info('🔓 [Lock] Lock released');
244
- }
245
- }))
246
- .catch(fn('handle-error-and-release-lock', async ({ data, error, log }) => {
247
- // Ensure lock is released even on error
248
- if (data?.stateService && data?.lockName && data?.kvAdapter) {
249
- await data.stateService.releaseLock(data.lockName, data.kvAdapter);
250
- log.info('🔓 [Lock] Lock released after error');
251
- }
252
-
253
- log.error('❌ [Error] Ingestion failed', error as Error);
254
- return { success: false, error: (error as Error).message };
255
- }));
256
-
257
- async function performIngestion(): Promise<void> {
258
- // Your ingestion logic
259
- await new Promise(resolve => setTimeout(resolve, 1000));
260
- }
261
- ```
262
-
263
- **Key Points**:
264
-
265
- - Distributed locking prevents concurrent executions
266
- - Stale lock detection (automatically overrides expired locks)
267
- - Lock timeout ensures recovery from crashes
268
- - ALWAYS release locks in finally blocks
269
- - Proper error handling to prevent lock leaks
270
-
271
- ---
272
-
273
- ### Pattern 3: Checkpoint & Resume
274
-
275
- **Use Case**: Save progress during long-running workflows and resume from last checkpoint.
276
-
277
- ```typescript
278
- import { webhook, fn } from '@versori/run';
279
- import { StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
280
-
281
- interface CheckpointData {
282
- currentBatch: number;
283
- totalBatches: number;
284
- processedRecords: number;
285
- lastProcessedId: string;
286
- startTime: string;
287
- }
288
-
289
- /**
290
- * Checkpoint & resume pattern
291
- * Useful for long-running batch processing that might fail partway through
292
- */
293
- export const batchProcessingWithCheckpoints = webhook('process-batches', {
294
- response: { mode: 'sync' }
295
- })
296
- .then(fn('check-checkpoint', async (ctx) => {
297
- const { openKv, log, data } = ctx;
298
-
299
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
300
- const stateService = new StateService(log);
301
- const workflowId = 'batch-processing';
302
- const checkpointKey = `checkpoint:${workflowId}`;
303
-
304
- // Try to restore from checkpoint
305
- const syncState = await stateService.getSyncState(kvAdapter, workflowId);
306
- let checkpoint: CheckpointData | null = null;
307
-
308
- if (syncState.isInitialized) {
309
- // Check if there's a saved checkpoint (stored in custom state)
310
- const stored = await kvAdapter.get([checkpointKey]);
311
- if (stored?.value) {
312
- checkpoint = stored.value as CheckpointData;
313
- log.info('♻️ [Checkpoint] Resuming from checkpoint', checkpoint);
314
- }
315
- }
316
-
317
- // Get batches to process
318
- const allBatches = data.batches || generateBatches(100); // 100 batches total
319
-
320
- // Determine starting point
321
- const startBatch = checkpoint ? checkpoint.currentBatch : 0;
322
- const processedSoFar = checkpoint ? checkpoint.processedRecords : 0;
323
-
324
- return {
325
- batches: allBatches,
326
- startBatch,
327
- processedSoFar,
328
- kvAdapter,
329
- stateService,
330
- workflowId,
331
- checkpointKey,
332
- startTime: checkpoint?.startTime || new Date().toISOString()
333
- };
334
- }))
335
- .then(fn('process-with-checkpoints', async ({ data, log }) => {
336
- const {
337
- batches,
338
- startBatch,
339
- processedSoFar,
340
- kvAdapter,
341
- stateService,
342
- workflowId,
343
- checkpointKey,
344
- startTime
345
- } = data;
346
-
347
- let processedRecords = processedSoFar;
348
-
349
- // Process batches starting from checkpoint
350
- for (let i = startBatch; i < batches.length; i++) {
351
- const batch = batches[i];
352
-
353
- try {
354
- log.info(`🚀 [Processing] Processing batch ${i + 1}/${batches.length}`);
355
-
356
- // Process the batch
357
- const result = await processBatch(batch);
358
- processedRecords += result.recordCount;
359
-
360
- // Save checkpoint after each batch (or every N batches)
361
- if ((i + 1) % 10 === 0 || i === batches.length - 1) {
362
- const checkpoint: CheckpointData = {
363
- currentBatch: i + 1,
364
- totalBatches: batches.length,
365
- processedRecords,
366
- lastProcessedId: result.lastId,
367
- startTime
368
- };
369
-
370
- await kvAdapter.set([checkpointKey], checkpoint);
371
- log.info(`💾 [Checkpoint] Checkpoint saved at batch ${i + 1}`);
372
- }
373
- } catch (error) {
374
- // ? Enhanced: Error logging with recommendations
375
- log.error('[KVStateManagement] Batch processing failed', {
376
- batchNumber: i + 1,
377
- error: error instanceof Error ? error.message : String(error),
378
- errorType: error instanceof Error ? error.constructor.name : 'Error',
379
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
380
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
381
- : error.message?.includes('mutation') || error.message?.includes('GraphQL')
382
- ? 'Check GraphQL mutation syntax and batch payload structure'
383
- : error.message?.includes('connection') || error.message?.includes('timeout')
384
- ? 'Check network connectivity and Fluent Commerce API availability'
385
- : 'Review error details - checkpoint saved for retry'
386
- });
387
-
388
- // Save checkpoint at failure point
389
- const checkpoint: CheckpointData = {
390
- currentBatch: i, // Retry this batch
391
- totalBatches: batches.length,
392
- processedRecords,
393
- lastProcessedId: batch[0]?.id || '',
394
- startTime
395
- };
396
-
397
- await kvAdapter.set([checkpointKey], checkpoint);
398
- throw error; // Fail workflow so it can be retried
399
- }
400
- }
401
-
402
- // All batches processed - clear checkpoint
403
- await kvAdapter.delete([checkpointKey]);
404
- log.info('✅ [Checkpoint] All batches processed, checkpoint cleared');
405
-
406
- // Update final sync state
407
- await stateService.updateSyncState(kvAdapter, [{
408
- fileName: 'batch-processing-complete',
409
- lastModified: new Date().toISOString(),
410
- recordCount: processedRecords
411
- }], workflowId);
412
-
413
- return {
414
- success: true,
415
- totalBatches: batches.length,
416
- processedRecords,
417
- duration: Date.now() - new Date(startTime).getTime()
418
- };
419
- }));
420
-
421
- function generateBatches(count: number): any[] {
422
- return Array.from({ length: count }, (_, i) => ({
423
- id: `batch-${i + 1}`,
424
- data: []
425
- }));
426
- }
427
-
428
- async function processBatch(batch: any): Promise<{ recordCount: number; lastId: string }> {
429
- // Your batch processing logic
430
- await new Promise(resolve => setTimeout(resolve, 100));
431
- return { recordCount: 50, lastId: batch.id };
432
- }
433
- ```
434
-
435
- **Key Points**:
436
-
437
- - Save checkpoints periodically (not after every operation)
438
- - Store enough context to resume exactly where you left off
439
- - Clear checkpoint after successful completion
440
- - On failure, checkpoint allows resuming without reprocessing
441
-
442
- ---
443
-
444
- ### Pattern 4: Daily Job Management
445
-
446
- **Use Case**: Reuse jobs across multiple batches throughout the day (DAILY strategy).
447
-
448
- ```typescript
449
- import { webhook, fn } from '@versori/run';
450
- import {
451
- StateService,
452
- VersoriKVAdapter,
453
- createClient
454
- } from '@fluentcommerce/fc-connect-sdk';
455
-
456
- /**
457
- * Daily job management pattern
458
- * Reuses a single job for multiple batches within a day
459
- */
460
- export const dailyJobIngestion = webhook('ingest-daily', {
461
- response: { mode: 'sync' }
462
- })
463
- .then(fn('get-or-create-job', async (ctx) => {
464
- // Destructure context inside function body
465
- const { openKv, log, connections, activation } = ctx;
466
-
467
- const kvAdapter = new VersoriKVAdapter(openKv());
468
- const stateService = new StateService(log);
469
- const workflowId = 'daily-inventory-ingestion';
470
-
471
- // Check for existing daily job
472
- const existingJob = await stateService.getDailyJob(kvAdapter, workflowId);
473
-
474
- let jobId: string;
475
- let isNewJob = false;
476
-
477
- if (existingJob && existingJob.jobId) {
478
- // Reuse existing job
479
- jobId = existingJob.jobId;
480
- log.info(`♻️ [DailyJob] Reusing daily job: ${jobId}`, {
481
- createdAt: existingJob.createdAt,
482
- expiresAt: existingJob.expiresAt
483
- });
484
- } else {
485
- // Create new job
486
- const client = await createClient(ctx); // Auto-detects Versori context
487
-
488
- const job = await client.createJob({
489
- name: `daily-inventory-${new Date().toISOString().split('T')[0]}`,
490
- retailerId: activation.getVariable('fluentRetailerId') as string
491
- });
492
-
493
- jobId = job.id;
494
- isNewJob = true;
495
-
496
- // Store job for 24 hours
497
- await stateService.setDailyJob(kvAdapter, workflowId, jobId, 24);
498
- log.info(`🆕 [DailyJob] Created new daily job: ${jobId}`);
499
- }
500
-
501
- return {
502
- jobId,
503
- isNewJob,
504
- kvAdapter,
505
- stateService,
506
- workflowId,
507
- client: await createClient(ctx) // Auto-detects Versori context
508
- };
509
- }))
510
- .then(fn('send-batches-to-job', async ({ data, log }) => {
511
- const { jobId, client, kvAdapter, stateService, workflowId } = data;
512
-
513
- // Your batches to process
514
- const batches = [
515
- { entities: [{ skuRef: 'SKU001', qty: 100 }] },
516
- { entities: [{ skuRef: 'SKU002', qty: 200 }] }
517
- ];
518
-
519
- const batchResults = [];
520
-
521
- for (const batch of batches) {
522
- try {
523
- const result = await client.sendBatch(jobId, {
524
- action: 'UPSERT',
525
- entityType: 'INVENTORY_CATALOGUE',
526
- entities: batch.entities
527
- });
528
-
529
- batchResults.push({ success: true, batchId: result.id });
530
- log.info(`✅ [Batch] Batch sent to job ${jobId}: ${result.id}`);
531
- } catch (error) {
532
- // ? Enhanced: Error logging with recommendations
533
- log.error('[KVStateManagement] Batch submission failed', {
534
- batchId: result?.id,
535
- error: error instanceof Error ? error.message : String(error),
536
- errorType: error instanceof Error ? error.constructor.name : 'Error',
537
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
538
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
539
- : error.message?.includes('batch') || error.message?.includes('job')
540
- ? 'Check batch API payload and job status'
541
- : error.message?.includes('connection') || error.message?.includes('timeout')
542
- ? 'Check network connectivity and Fluent Commerce API availability'
543
- : 'Review error details and check batch submission payload'
544
- });
545
- batchResults.push({ success: false, error: (error as Error).message });
546
- }
547
- }
548
-
549
- // Update sync state with processed records
550
- await stateService.updateSyncState(kvAdapter, [{
551
- fileName: `daily-batch-${new Date().toISOString()}`,
552
- lastModified: new Date().toISOString(),
553
- recordCount: batches.reduce((sum, b) => sum + b.entities.length, 0)
554
- }], workflowId);
555
-
556
- return {
557
- success: true,
558
- jobId,
559
- batchCount: batchResults.length,
560
- successCount: batchResults.filter(r => r.success).length
561
- };
562
- }));
563
- ```
564
-
565
- **Key Points**:
566
-
567
- - Single job per day reduces API overhead
568
- - Job expires automatically after 24 hours
569
- - Tracks batch count per job
570
- - Perfect for high-frequency ingestion workflows
571
-
572
- ---
573
-
574
- ### Pattern 5: Error State Management
575
-
576
- **Use Case**: Track errors and implement retry logic with exponential backoff.
577
-
578
- ```typescript
579
- import { webhook, fn } from '@versori/run';
580
- import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
581
-
582
- interface ErrorState {
583
- fileName: string;
584
- attemptCount: number;
585
- lastError: string;
586
- lastAttemptAt: string;
587
- firstFailedAt: string;
588
- nextRetryAt: string;
589
- }
590
-
591
- /**
592
- * Error state management pattern
593
- * Tracks failures and implements smart retry logic
594
- */
595
- export const retryableIngestion = webhook('ingest-with-retry', {
596
- response: { mode: 'sync' }
597
- })
598
- .then(fn('process-with-retry-tracking', async (ctx) => {
599
- const { openKv, log, data } = ctx;
600
-
601
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
602
- const files = data.files || ['file1.csv', 'file2.csv', 'file3.csv'];
603
-
604
- const results = {
605
- succeeded: [] as string[],
606
- failed: [] as string[],
607
- skipped: [] as string[]
608
- };
609
-
610
- for (const fileName of files) {
611
- const errorStateKey = ['error-state', fileName];
612
-
613
- // Check if file has error state
614
- const errorStateResult = await kvAdapter.get(errorStateKey);
615
- const errorState = errorStateResult?.value as ErrorState | undefined;
616
-
617
- // Check if we should retry
618
- if (errorState) {
619
- const nextRetry = new Date(errorState.nextRetryAt);
620
- const now = new Date();
621
-
622
- if (now < nextRetry) {
623
- log.info(`⏭️ [Retry] Skipping ${fileName} until ${errorState.nextRetryAt}`, {
624
- attemptCount: errorState.attemptCount,
625
- lastError: errorState.lastError
626
- });
627
- results.skipped.push(fileName);
628
- continue;
629
- }
630
-
631
- // Max retry attempts reached?
632
- if (errorState.attemptCount >= 5) {
633
- log.warn(`⚠️ [Retry] Max retries exceeded for ${fileName}, requires manual intervention`);
634
- results.skipped.push(fileName);
635
- continue;
636
- }
637
-
638
- log.info(`♻️ [Retry] Retrying ${fileName} (attempt ${errorState.attemptCount + 1})`, {
639
- lastError: errorState.lastError,
640
- firstFailedAt: errorState.firstFailedAt
641
- });
642
- }
643
-
644
- try {
645
- // Process the file
646
- await processFileWithPossibleFailure(fileName);
647
-
648
- // Success! Clear error state
649
- if (errorState) {
650
- await kvAdapter.delete(errorStateKey);
651
- log.info(`✅ [Retry] File recovered after ${errorState.attemptCount} retries: ${fileName}`);
652
- }
653
-
654
- results.succeeded.push(fileName);
655
- } catch (error) {
656
- // ? Enhanced: Error logging with recommendations
657
- const errorMessage = (error as Error).message;
658
- const attemptCount = errorState ? errorState.attemptCount + 1 : 1;
659
- const now = new Date();
660
-
661
- // Calculate next retry with exponential backoff
662
- const backoffMinutes = Math.pow(2, attemptCount) * 5; // 5, 10, 20, 40, 80 minutes
663
- const nextRetryAt = new Date(now.getTime() + backoffMinutes * 60 * 1000);
664
-
665
- // Save error state
666
- const newErrorState: ErrorState = {
667
- fileName,
668
- attemptCount,
669
- lastError: errorMessage,
670
- lastAttemptAt: now.toISOString(),
671
- firstFailedAt: errorState?.firstFailedAt || now.toISOString(),
672
- nextRetryAt: nextRetryAt.toISOString()
673
- };
674
-
675
- await kvAdapter.set(errorStateKey, newErrorState);
676
-
677
- log.error('[KVStateManagement] File processing failed', {
678
- fileName,
679
- attemptCount,
680
- error: error instanceof Error ? error.message : String(error),
681
- errorType: error instanceof Error ? error.constructor.name : 'Error',
682
- nextRetryAt: nextRetryAt.toISOString(),
683
- backoffMinutes,
684
- recommendation: error.message?.includes('parse') || error.message?.includes('format')
685
- ? 'Check file format and structure - ensure file is valid'
686
- : error.message?.includes('mapping') || error.message?.includes('field')
687
- ? 'Check mapping configuration and verify file column structure'
688
- : error.message?.includes('connection') || error.message?.includes('timeout')
689
- ? 'Check network connectivity and data source availability'
690
- : `Will retry after ${backoffMinutes} minutes - review error details`
691
- });
692
-
693
- results.failed.push(fileName);
694
- }
695
- }
696
-
697
- return {
698
- success: true,
699
- results,
700
- summary: {
701
- succeeded: results.succeeded.length,
702
- failed: results.failed.length,
703
- skipped: results.skipped.length
704
- }
705
- };
706
- }))
707
- .then(fn('cleanup-old-errors', async (ctx) => {
708
- const { openKv, log, data } = ctx;
709
-
710
- // Cleanup error states older than 7 days
711
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
712
- const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
713
-
714
- // Note: This requires tracking error state keys separately
715
- // In production, use VersoriIndexedFileTracker pattern
716
-
717
- log.info('Error state cleanup completed');
718
- return data;
719
- }));
720
-
721
- async function processFileWithPossibleFailure(fileName: string): Promise<void> {
722
- // Simulate random failures for demonstration
723
- if (Math.random() < 0.3) {
724
- throw new Error('Simulated processing failure');
725
- }
726
- await new Promise(resolve => setTimeout(resolve, 100));
727
- }
728
- ```
729
-
730
- **Key Points**:
731
-
732
- - Exponential backoff prevents overwhelming failing systems
733
- - Track first failure time for monitoring
734
- - Max retry limit prevents infinite loops
735
- - Clear error state on success
736
- - Skip files until retry time
737
-
738
- ---
739
-
740
- ### Pattern 6: Advanced File Tracking with Indexing
741
-
742
- **Use Case**: Track files with ability to list all processed files.
743
-
744
- ```typescript
745
- import { webhook, fn } from '@versori/run';
746
- import { VersoriIndexedFileTracker } from '@fluentcommerce/fc-connect-sdk';
747
-
748
- /**
749
- * Advanced file tracking with indexing
750
- * Allows listing all processed files (overcomes Versori KV list limitation)
751
- */
752
- export const indexedFileTracking = webhook('manage-files', {
753
- response: { mode: 'sync' }
754
- })
755
- .then(fn('track-and-manage-files', async (ctx) => {
756
- const { openKv, log, data } = ctx;
757
-
758
- // Initialize indexed file tracker
759
- const fileTracker = new VersoriIndexedFileTracker(openKv(':project:'), 'inventory-ingestion');
760
- const operation = data.operation || 'process'; // process, list, stats, cleanup
761
-
762
- switch (operation) {
763
- case 'process': {
764
- const files = data.files || ['file1.csv', 'file2.csv'];
765
-
766
- for (const file of files) {
767
- // Process file
768
- log.info(`🚀 [Processing] Processing: ${file}`);
769
- const recordCount = Math.floor(Math.random() * 1000) + 100;
770
-
771
- // Mark as processed (automatically updates index)
772
- await fileTracker.markFileProcessed(file, {
773
- recordCount,
774
- source: 's3://bucket/' + file,
775
- processingDuration: 1234
776
- });
777
- }
778
-
779
- return { success: true, processed: files.length };
780
- }
781
-
782
- case 'list': {
783
- // List all processed files
784
- const processedFiles = await fileTracker.listProcessedFiles();
785
- log.info(`📋 [Index] Found ${processedFiles.length} processed files`);
786
-
787
- return {
788
- success: true,
789
- files: processedFiles.map(f => ({
790
- name: f.fileName,
791
- processedAt: f.metadata?.processedAt,
792
- recordCount: f.metadata?.recordCount
793
- }))
794
- };
795
- }
796
-
797
- case 'stats': {
798
- // Get processing statistics
799
- const stats = await fileTracker.getStats();
800
- log.info('📊 [Index] File processing statistics', stats);
801
-
802
- return {
803
- success: true,
804
- stats
805
- };
806
- }
807
-
808
- case 'cleanup': {
809
- // Clear all processed files
810
- const result = await fileTracker.clearAllProcessedFiles();
811
- log.info(`🧹 [Index] Cleanup complete: ${result.success} deleted, ${result.failed} failed`);
812
-
813
- return {
814
- success: true,
815
- deleted: result.success,
816
- failed: result.failed
817
- };
818
- }
819
-
820
- case 'remove': {
821
- // Remove specific file
822
- const fileName = data.fileName;
823
- if (!fileName) {
824
- throw new Error('fileName required for remove operation');
825
- }
826
-
827
- await fileTracker.removeProcessedFile(fileName);
828
- log.info(`🗑️ [Index] Removed file: ${fileName}`);
829
-
830
- return { success: true, removed: fileName };
831
- }
832
-
833
- default:
834
- throw new Error(`Unknown operation: ${operation}`);
835
- }
836
- }));
837
- ```
838
-
839
- **Key Points**:
840
-
841
- - Maintains internal index to overcome Versori KV list limitation
842
- - List all processed files
843
- - Get aggregate statistics
844
- - Cleanup operations
845
- - Automatic index maintenance
846
-
847
- ---
848
-
849
- ### Pattern 7: MemoryInterpreter for In-Memory State
850
-
851
- **Use Case**: Store interpreter state in memory for long-running connections (SFTP, database connections).
852
-
853
- ```typescript
854
- import { webhook, fn } from '@versori/run';
855
- import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
856
-
857
- interface MemoryInterpreterState {
858
- sftpConnections: Map<string, any>;
859
- lastActivity: Map<string, number>;
860
- connectionPool: any[];
861
- }
862
-
863
- /**
864
- * MemoryInterpreter pattern
865
- * Maintains in-memory state for connection pools and active sessions
866
- */
867
- export const connectionManager = webhook('manage-connections', {
868
- response: { mode: 'sync' }
869
- })
870
- .then(fn('initialize-memory-state', async (ctx) => {
871
- const { openKv, log } = ctx;
872
-
873
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
874
- const stateKey = ['memory-interpreter', 'connections'];
875
-
876
- // Load existing state or initialize new
877
- const existingState = await kvAdapter.get(stateKey);
878
-
879
- const memoryState: MemoryInterpreterState = existingState?.value || {
880
- sftpConnections: new Map(),
881
- lastActivity: new Map(),
882
- connectionPool: []
883
- };
884
-
885
- log.info('🧠 [MemoryInterpreter] State initialized', {
886
- activeConnections: memoryState.sftpConnections.size,
887
- poolSize: memoryState.connectionPool.length
888
- });
889
-
890
- return {
891
- kvAdapter,
892
- stateKey,
893
- memoryState
894
- };
895
- }))
896
- .then(fn('process-with-memory-state', async ({ data, log }) => {
897
- const { kvAdapter, stateKey, memoryState } = data;
898
-
899
- // Use memory state for processing
900
- const connectionId = 'sftp-main';
901
- const now = Date.now();
902
-
903
- // Check if connection is stale (> 5 minutes inactive)
904
- const lastActivity = memoryState.lastActivity.get(connectionId);
905
- const isStale = lastActivity && (now - lastActivity) > 5 * 60 * 1000;
906
-
907
- if (isStale) {
908
- log.info('♻️ [MemoryInterpreter] Refreshing stale connection', {
909
- connectionId,
910
- lastActivity: new Date(lastActivity).toISOString()
911
- });
912
-
913
- // Refresh connection
914
- memoryState.sftpConnections.delete(connectionId);
915
- }
916
-
917
- // Update activity timestamp
918
- memoryState.lastActivity.set(connectionId, now);
919
-
920
- // Persist updated state
921
- await kvAdapter.set(stateKey, {
922
- sftpConnections: Array.from(memoryState.sftpConnections.entries()),
923
- lastActivity: Array.from(memoryState.lastActivity.entries()),
924
- connectionPool: memoryState.connectionPool
925
- });
926
-
927
- log.info('💾 [MemoryInterpreter] State persisted', {
928
- connections: memoryState.sftpConnections.size,
929
- lastUpdate: new Date(now).toISOString()
930
- });
931
-
932
- return {
933
- success: true,
934
- activeConnections: memoryState.sftpConnections.size,
935
- lastActivity: now
936
- };
937
- }));
938
- ```
939
-
940
- **Key Points**:
941
-
942
- - Use MemoryInterpreter for connection pools and active sessions
943
- - Persist state to KV to survive workflow restarts
944
- - Implement stale connection detection and refresh
945
- - Track activity timestamps for connection reuse
946
- - Serialize/deserialize Maps and complex objects properly
947
-
948
- ---
949
-
950
- ### Pattern 8: Connection Validation
951
-
952
- **Use Case**: Validate data source connections before processing to catch configuration errors early.
953
-
954
- ```typescript
955
- import { webhook, fn } from '@versori/run';
956
- import {
957
- SftpDataSource,
958
- S3DataSource,
959
- createClient
960
- } from '@fluentcommerce/fc-connect-sdk';
961
-
962
- /**
963
- * Connection validation pattern
964
- * Validates all data sources before processing
965
- */
966
- export const validatedIngestion = webhook('ingest-with-validation', {
967
- response: { mode: 'sync' }
968
- })
969
- .then(fn('validate-connections', async (ctx) => {
970
- const { log, activation } = ctx;
971
-
972
- log.info('🔍 [Validation] Starting connection validation');
973
-
974
- // Validate SFTP connection
975
- const sftp = new SftpDataSource(
976
- {
977
- type: 'SFTP_XML',
978
- connectionId: 'sftp-validation',
979
- name: 'validated-sftp',
980
- settings: {
981
- host: activation.getVariable('sftpHost'),
982
- port: parseInt(activation.getVariable('sftpPort') || '22', 10),
983
- username: activation.getVariable('sftpUsername'),
984
- password: activation.getVariable('sftpPassword'),
985
- remotePath: '/incoming/',
986
- encoding: 'utf8',
987
- requireAbsolutePaths: true
988
- }
989
- },
990
- log
991
- );
992
-
993
- try {
994
- await sftp.validateConnection();
995
- log.info('✅ [Validation] SFTP connection validated successfully');
996
- } catch (error) {
997
- log.error('❌ [Validation] SFTP connection failed', {
998
- error: error instanceof Error ? error.message : String(error),
999
- recommendation: error.message?.includes('authentication')
1000
- ? 'Check SFTP username and password in connection configuration'
1001
- : error.message?.includes('timeout')
1002
- ? 'Check SFTP host and port - ensure server is reachable'
1003
- : error.message?.includes('host')
1004
- ? 'Verify SFTP host address is correct'
1005
- : 'Review SFTP connection settings and server logs'
1006
- });
1007
- throw error;
1008
- }
1009
-
1010
- // Validate S3 connection
1011
- const s3 = new S3DataSource(
1012
- {
1013
- type: 'S3_CSV',
1014
- connectionId: 's3-validation',
1015
- name: 'validated-s3',
1016
- settings: {
1017
- region: activation.getVariable('awsRegion') || 'us-east-1',
1018
- bucket: activation.getVariable('s3Bucket'),
1019
- prefix: 'incoming/',
1020
- accessKeyId: activation.getVariable('awsAccessKeyId'),
1021
- secretAccessKey: activation.getVariable('awsSecretAccessKey')
1022
- }
1023
- },
1024
- log
1025
- );
1026
-
1027
- try {
1028
- await s3.validateConnection();
1029
- log.info('✅ [Validation] S3 connection validated successfully');
1030
- } catch (error) {
1031
- log.error('❌ [Validation] S3 connection failed', {
1032
- error: error instanceof Error ? error.message : String(error),
1033
- recommendation: error.message?.includes('credentials')
1034
- ? 'Check AWS access key and secret in connection configuration'
1035
- : error.message?.includes('bucket')
1036
- ? 'Verify S3 bucket exists and credentials have access'
1037
- : error.message?.includes('region')
1038
- ? 'Check AWS region is correct for the bucket'
1039
- : 'Review S3 connection settings and IAM permissions'
1040
- });
1041
- throw error;
1042
- }
1043
-
1044
- // Validate Fluent Commerce API connection
1045
- const client = await createClient(ctx);
1046
- const retailerId = activation.getVariable('fluentRetailerId');
1047
-
1048
- if (!retailerId) {
1049
- log.error('❌ [Validation] Missing retailerId activation variable');
1050
- throw new Error('fluentRetailerId is required for Fluent API operations');
1051
- }
1052
-
1053
- client.setRetailerId(retailerId);
1054
-
1055
- try {
1056
- // Test connection with a simple query
1057
- const testQuery = `query { retailers { edges { node { id } } } }`;
1058
- await client.graphql({ query: testQuery });
1059
- log.info('✅ [Validation] Fluent Commerce API connection validated');
1060
- } catch (error) {
1061
- log.error('❌ [Validation] Fluent Commerce API connection failed', {
1062
- error: error instanceof Error ? error.message : String(error),
1063
- recommendation: error.message?.includes('401') || error.message?.includes('authentication')
1064
- ? 'Verify fluent_commerce connection OAuth2 credentials'
1065
- : error.message?.includes('GraphQL')
1066
- ? 'Check GraphQL query syntax and permissions'
1067
- : 'Review Fluent Commerce API connection settings'
1068
- });
1069
- throw error;
1070
- }
1071
-
1072
- log.info('✅ [Validation] All connections validated successfully');
1073
-
1074
- return {
1075
- sftp,
1076
- s3,
1077
- client,
1078
- validated: true
1079
- };
1080
- }))
1081
- .then(fn('process-with-validated-connections', async ({ data, log }) => {
1082
- const { sftp, s3, client } = data;
1083
-
1084
- log.info('🚀 [Processing] Starting ingestion with validated connections');
1085
-
1086
- try {
1087
- // Your ingestion logic here with validated connections
1088
- const files = await sftp.listFiles({ remotePath: '/incoming/' });
1089
-
1090
- log.info('📋 [Processing] Files discovered', { count: files.length });
1091
-
1092
- // Process files...
1093
-
1094
- return {
1095
- success: true,
1096
- filesProcessed: files.length
1097
- };
1098
- } finally {
1099
- // Always dispose connections
1100
- await sftp.dispose();
1101
- await s3.dispose();
1102
- log.info('🧹 [Cleanup] Connections disposed');
1103
- }
1104
- }));
1105
- ```
1106
-
1107
- **Key Points**:
1108
-
1109
- - Always validate connections before processing
1110
- - Provide detailed error messages with recommendations
1111
- - Use emoji logging for visual scanning
1112
- - Fail fast on configuration errors
1113
- - Dispose connections in finally blocks
1114
-
1115
- ---
1116
-
1117
- ## Production Best Practices
1118
-
1119
- ### 1. Emoji Logging for Operations Visibility
1120
-
1121
- Use emoji prefixes in logs for quick visual scanning:
1122
-
1123
- ```typescript
1124
- log.info('🔍 [Discovery] Scanning SFTP directory');
1125
- log.info('✅ [Success] File processed successfully');
1126
- log.error('❌ [Error] Connection failed', { error });
1127
- log.warn('⚠️ [Warning] Partial batch failure');
1128
- log.info('🧹 [Cleanup] Archiving processed files');
1129
- log.info('🧠 [MemoryInterpreter] State synchronized');
1130
- log.info('💾 [Persistence] State saved to KV');
1131
- log.info('♻️ [Refresh] Stale connection renewed');
1132
- log.info('🚀 [Start] Processing batch');
1133
- log.info('📋 [Info] Configuration loaded');
1134
- log.info('🔐 [Auth] Token refreshed');
1135
- ```
1136
-
1137
- **Benefits:**
1138
- - Quick visual scanning in production logs
1139
- - Easy pattern recognition for operations
1140
- - Faster debugging and troubleshooting
1141
- - Better collaboration across teams
1142
-
1143
- ### 2. Connection Validation Pattern
1144
-
1145
- Always validate connections before processing:
1146
-
1147
- ```typescript
1148
- // ✅ CORRECT - Validate early
1149
- const sftp = new SftpDataSource(config, log);
1150
- await sftp.validateConnection();
1151
- log.info('✅ Connection validated');
1152
-
1153
- // Process files...
1154
-
1155
- // ❌ WRONG - Discover errors during processing
1156
- const sftp = new SftpDataSource(config, log);
1157
- const files = await sftp.listFiles(); // Might fail here!
1158
- ```
1159
-
1160
- ### 3. Error Handling with Recommendations
1161
-
1162
- Provide actionable recommendations in error logs:
1163
-
1164
- ```typescript
1165
- catch (error) {
1166
- log.error('❌ [Processing] File processing failed', {
1167
- fileName: file.name,
1168
- error: error instanceof Error ? error.message : String(error),
1169
- recommendation: error.message?.includes('authentication')
1170
- ? 'Check connection credentials in Connections section'
1171
- : error.message?.includes('timeout')
1172
- ? 'Check network connectivity and increase timeout settings'
1173
- : error.message?.includes('parse')
1174
- ? 'Verify file format matches expected structure'
1175
- : 'Review error details and check file processing logic'
1176
- });
1177
- }
1178
- ```
1179
-
1180
- ### 4. Resource Cleanup Pattern
1181
-
1182
- Always dispose data sources in finally blocks:
1183
-
1184
- ```typescript
1185
- const sftp = new SftpDataSource(config, log);
1186
- try {
1187
- await sftp.validateConnection();
1188
- // Process files...
1189
- } finally {
1190
- await sftp.dispose();
1191
- log.info('🧹 [Cleanup] SFTP connection disposed');
1192
- }
1193
- ```
1194
-
1195
- ### 5. MemoryInterpreter State Management
1196
-
1197
- Use MemoryInterpreter for connection pools and session state:
1198
-
1199
- ```typescript
1200
- // Persist complex objects to KV
1201
- await kvAdapter.set(['memory-interpreter', 'state'], {
1202
- connections: Array.from(connectionMap.entries()),
1203
- lastActivity: Array.from(activityMap.entries()),
1204
- pool: connectionPool
1205
- });
1206
-
1207
- // Restore from KV
1208
- const state = await kvAdapter.get(['memory-interpreter', 'state']);
1209
- const connectionMap = new Map(state.value.connections);
1210
- ```
1211
-
1212
- ### 6. JobTracker Integration
1213
-
1214
- Use JobTracker for production monitoring:
1215
-
1216
- ```typescript
1217
- const tracker = new JobTracker(openKv(':project:'), log);
1218
- const jobId = `job-${Date.now()}`;
1219
-
1220
- await tracker.createJob(jobId, {
1221
- triggeredBy: 'schedule',
1222
- stage: 'initialization',
1223
- startTime: Date.now()
1224
- });
1225
-
1226
- try {
1227
- // Process...
1228
- await tracker.markCompleted(jobId, { filesProcessed: 10 });
1229
- } catch (error) {
1230
- await tracker.markFailed(jobId, error.message);
1231
- }
1232
- ```
1233
-
1234
- ---
1235
-
1236
- ## Testing State Management
1237
-
1238
- ### Local Testing Pattern
1239
-
1240
- ```typescript
1241
- /**
1242
- * Test state management locally
1243
- */
1244
- export const testStateManagement = webhook('test-state', {
1245
- response: { mode: 'sync' }
1246
- })
1247
- .then(fn('test-operations', async (ctx) => {
1248
- const { openKv, log } = ctx;
1249
-
1250
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
1251
- const stateService = new StateService(log);
1252
-
1253
- // Test 1: Lock acquire and release
1254
- log.info('🧪 [Test] Test 1: Lock management');
1255
- const lockAcquired = await stateService.acquireLock('test-lock', kvAdapter, 5);
1256
- log.info(`🔐 [Test] Lock acquired: ${lockAcquired}`);
1257
-
1258
- // Try to acquire again (should fail)
1259
- const lockAcquired2 = await stateService.acquireLock('test-lock', kvAdapter, 5);
1260
- log.info(`⚠️ [Test] Second lock acquired: ${lockAcquired2} (should be false)`);
1261
-
1262
- // Release lock
1263
- await stateService.releaseLock('test-lock', kvAdapter);
1264
- log.info('🔓 [Test] Lock released');
1265
-
1266
- // Test 2: File tracking
1267
- log.info('🧪 [Test] Test 2: File tracking');
1268
- const fileTracker = new VersoriFileTracker(openKv(':project:'), 'test');
1269
- await fileTracker.markFileProcessed('test-file.csv', { recordCount: 100 });
1270
- const wasProcessed = await fileTracker.wasFileProcessed('test-file.csv');
1271
- log.info(`✅ [Test] File processed: ${wasProcessed} (should be true)`);
1272
-
1273
- const lastFile = await fileTracker.getLastProcessedFile();
1274
- await fileTracker.setLastProcessedFile('test-file.csv');
1275
- const newLastFile = await fileTracker.getLastProcessedFile();
1276
- log.info(`📋 [Test] Last file: ${newLastFile}`);
1277
-
1278
- // Test 3: Sync state
1279
- log.info('🧪 [Test] Test 3: Sync state');
1280
- await stateService.updateSyncState(kvAdapter, [{
1281
- fileName: 'test.csv',
1282
- lastModified: new Date().toISOString(),
1283
- recordCount: 100
1284
- }], 'test-workflow');
1285
-
1286
- const syncState = await stateService.getSyncState(kvAdapter, 'test-workflow');
1287
- log.info('💾 [Test] Sync state', syncState);
1288
-
1289
- // Test 4: Daily job
1290
- log.info('🧪 [Test] Test 4: Daily job');
1291
- await stateService.setDailyJob(kvAdapter, 'test-workflow', 'job-123', 24);
1292
- const dailyJob = await stateService.getDailyJob(kvAdapter, 'test-workflow');
1293
- log.info('📋 [Test] Daily job', dailyJob);
1294
-
1295
- return {
1296
- success: true,
1297
- message: '✅ All state management tests passed'
1298
- };
1299
- }));
1300
- ```
1301
-
1302
- ---
1303
-
1304
- ## Common Issues & Solutions
1305
-
1306
- ### Issue 1: Lock Not Released After Error
1307
-
1308
- **Problem**: Workflow crashes and lock is never released, blocking future runs.
1309
-
1310
- **Solution**: Always use try/finally or .catch() to ensure lock release:
1311
-
1312
- ```typescript
1313
- export const safeWorkflow = schedule('safe-job', '0 * * * *')
1314
- .then(fn('work', async (ctx) => {
1315
- const { openKv, log } = ctx;
1316
-
1317
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
1318
- const stateService = new StateService(log);
1319
- const lockName = 'safe-lock';
1320
-
1321
- try {
1322
- await stateService.acquireLock(lockName, kvAdapter, 15);
1323
- // Your work here
1324
- await doWork();
1325
- } finally {
1326
- // Always release, even on error
1327
- await stateService.releaseLock(lockName, kvAdapter);
1328
- }
1329
- }));
1330
- ```
1331
-
1332
- Or use the catch pattern:
1333
-
1334
- ```typescript
1335
- export const workflowWithCatch = schedule('job', '0 * * * *')
1336
- .then(fn('acquire', async (ctx) => {
1337
- const { openKv, log } = ctx;
1338
-
1339
- const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
1340
- const stateService = new StateService(log);
1341
- await stateService.acquireLock('my-lock', kvAdapter, 15);
1342
- return { kvAdapter, stateService };
1343
- }))
1344
- .then(fn('work', async ({ data }) => {
1345
- // Work here
1346
- return data;
1347
- }))
1348
- .catch(fn('cleanup', async ({ data }) => {
1349
- // Release lock on any error
1350
- if (data?.stateService) {
1351
- await data.stateService.releaseLock('my-lock', data.kvAdapter);
1352
- }
1353
- throw new Error('Workflow failed');
1354
- }));
1355
- ```
1356
-
1357
- ---
1358
-
1359
- ### Issue 2: Stale Lock Detection
1360
-
1361
- **Problem**: Lock holder crashed and never released lock.
1362
-
1363
- **Solution**: Use lock timeout - StateService automatically overrides stale locks:
1364
-
1365
- ```typescript
1366
- // Lock with 15 minute timeout
1367
- const acquired = await stateService.acquireLock('my-lock', kvAdapter, 15);
1368
-
1369
- // If lock is older than 15 minutes, it will be overridden automatically
1370
- // This prevents permanent deadlocks from crashed processes
1371
- ```
1372
-
1373
- **How it works**:
1374
-
1375
- - Each lock stores `expiresAt` timestamp
1376
- - `acquireLock()` checks if existing lock is expired
1377
- - Expired locks are automatically overridden
1378
- - Recent locks return false (lock not acquired)
1379
-
1380
- ---
1381
-
1382
- ### Issue 3: Cannot List Processed Files
1383
-
1384
- **Problem**: Versori KV doesn't support list operations, can't enumerate processed files.
1385
-
1386
- **Solution**: Use `VersoriIndexedFileTracker` which maintains an index:
1387
-
1388
- ```typescript
1389
- // Instead of VersoriFileTracker
1390
- const fileTracker = new VersoriFileTracker(openKv(':project:'));
1391
-
1392
- // Use VersoriIndexedFileTracker
1393
- const indexedTracker = new VersoriIndexedFileTracker(openKv(':project:'));
1394
-
1395
- // Now you can list files
1396
- const allFiles = await indexedTracker.listProcessedFiles();
1397
- const stats = await indexedTracker.getStats();
1398
- ```
1399
-
1400
- **How it works**:
1401
-
1402
- - Maintains separate index key with list of all file names
1403
- - Index updated atomically when files are marked/removed
1404
- - Enables listing without KV list support
1405
-
1406
- ---
1407
-
1408
- ### Issue 4: Checkpoint Corruption
1409
-
1410
- **Problem**: Checkpoint data gets corrupted or becomes invalid.
1411
-
1412
- **Solution**: Add validation and version to checkpoint data:
1413
-
1414
- ```typescript
1415
- interface VersionedCheckpoint {
1416
- version: number; // Schema version
1417
- data: CheckpointData;
1418
- checksum?: string; // Optional integrity check
1419
- savedAt: string;
1420
- }
1421
-
1422
- async function saveCheckpoint(
1423
- kvAdapter: VersoriKVAdapter,
1424
- key: string[],
1425
- data: CheckpointData
1426
- ): Promise<void> {
1427
- const checkpoint: VersionedCheckpoint = {
1428
- version: 1,
1429
- data,
1430
- savedAt: new Date().toISOString()
1431
- };
1432
-
1433
- await kvAdapter.set(key, checkpoint);
1434
- }
1435
-
1436
- async function loadCheckpoint(
1437
- kvAdapter: VersoriKVAdapter,
1438
- key: string[]
1439
- ): Promise<CheckpointData | null> {
1440
- try {
1441
- const result = await kvAdapter.get(key);
1442
- if (!result?.value) return null;
1443
-
1444
- const checkpoint = result.value as VersionedCheckpoint;
1445
-
1446
- // Validate version
1447
- if (checkpoint.version !== 1) {
1448
- console.warn('Checkpoint version mismatch, ignoring');
1449
- return null;
1450
- }
1451
-
1452
- // Check if checkpoint is too old (e.g., > 7 days)
1453
- const savedAt = new Date(checkpoint.savedAt);
1454
- const age = Date.now() - savedAt.getTime();
1455
- if (age > 7 * 24 * 60 * 60 * 1000) {
1456
- console.warn('Checkpoint too old, starting fresh');
1457
- return null;
1458
- }
1459
-
1460
- return checkpoint.data;
1461
- } catch (error) {
1462
- console.error('Failed to load checkpoint', error);
1463
- return null; // Start fresh on corruption
1464
- }
1465
- }
1466
- ```
1467
-
1468
- ---
1469
-
1470
- ## Best Practices Summary
1471
-
1472
- ### Core State Management Practices
1473
-
1474
- 1. **Always Release Locks**: Use try/finally or .catch() to ensure locks are released
1475
- 2. **Use Lock Timeouts**: Set appropriate timeouts (15-30 minutes typical)
1476
- 3. **Checkpoint Periodically**: Not too often (overhead) or too rarely (lost progress)
1477
- 4. **Validate Restored State**: Check checkpoint age and integrity before using
1478
- 5. **Clear Completed Checkpoints**: Remove checkpoint data after successful completion
1479
- 6. **Use Indexed Tracker for Listing**: VersoriKV doesn't support list operations natively
1480
- 7. **Track Error State**: Implement exponential backoff for retries
1481
- 8. **Monitor Lock Ages**: Alert on locks held longer than expected
1482
- 9. **Namespace Your Keys**: Use prefixes to organize KV data (`workflow:resource:identifier`)
1483
- 10. **Document Key Schemas**: Keep track of what data is stored under which keys
1484
-
1485
- ### Production Patterns (NEW)
1486
-
1487
- 11. **Use Emoji Logging**: Add emoji prefixes for quick visual scanning in logs (see Pattern 8)
1488
- 12. **Validate Connections Early**: Always call `validateConnection()` before processing (see Pattern 8)
1489
- 13. **Provide Error Recommendations**: Include actionable recommendations in error logs
1490
- 14. **Dispose Resources**: Always dispose data sources in finally blocks
1491
- 15. **Use MemoryInterpreter**: For connection pools and session state (see Pattern 7)
1492
- 16. **Integrate JobTracker**: Track job lifecycle for production monitoring
1493
-
1494
- ---
1495
-
1496
- ## Related Guides
1497
-
1498
- - **01-inventory-ingestion.md** - Complete ingestion workflow using state management
1499
- - **02-inventory-extraction.md** - Extraction workflow with checkpoints
1500
- - **04-batch-archival.md** - Archiving batch data to S3 (uses daily jobs)
1501
- - **05-error-handling.md** - Advanced error handling patterns
1502
- - **StateService full reference**: `../../02-CORE-GUIDES/ingestion/modules/07-state-management.md`
1503
-
1504
- ---
1505
-
1506
- ## Summary
1507
-
1508
- This guide covered comprehensive state management patterns for Versori connectors:
1509
-
1510
- 1. **Simple File Tracking** - Prevent duplicate processing with VersoriFileTracker
1511
- 2. **Distributed Locking** - Prevent concurrent executions with StateService
1512
- 3. **Checkpoints** - Save progress and resume long-running workflows
1513
- 4. **Daily Jobs** - Reuse jobs across batches throughout the day
1514
- 5. **Error States** - Track failures and implement smart retry logic
1515
- 6. **Indexed Tracking** - List and manage processed files
1516
- 7. **MemoryInterpreter** (NEW) - In-memory state for connection pools
1517
- 8. **Connection Validation** (NEW) - Validate connections before processing
1518
-
1519
- **Key Takeaways**:
1520
-
1521
- - VersoriFileTracker for simple duplicate prevention
1522
- - StateService for distributed locking and sync state
1523
- - VersoriIndexedFileTracker when you need to list files
1524
- - Always release locks in finally blocks
1525
- - Checkpoint periodically to enable resume
1526
- - Validate restored state before using
1527
- - Use exponential backoff for retries
1528
- - **Use emoji logging for production visibility** (NEW)
1529
- - **Validate connections early with `validateConnection()`** (NEW)
1530
- - **Use MemoryInterpreter for connection pool state** (NEW)
1531
- - **Provide actionable error recommendations** (NEW)
1532
-
1533
- State management is critical for robust production workflows. These patterns ensure your connectors are reliable, resumable, and prevent duplicate processing.
1
+ # Versori KV State Management
2
+
3
+ **FC Connect SDK Use Case Guide**
4
+
5
+ > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
6
+ > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
7
+
8
+ **Context**: Use Versori KV storage for duplicate prevention, checkpoints, and file tracking
9
+
10
+ **Complexity**: Low-Medium
11
+
12
+ **Runtime**: Versori Platform
13
+
14
+ **Estimated Lines**: ~400 lines
15
+
16
+ ## What You'll Build
17
+
18
+ - VersoriKV adapter setup
19
+ - File tracking (prevent duplicates)
20
+ - Checkpoint/resume capabilities
21
+ - Batch processing state
22
+ - Error tracking and retry logic
23
+
24
+ ## SDK Methods Used
25
+
26
+ - `VersoriKVAdapter(openKv())` - Wrap Versori KV for StateService
27
+ - `VersoriFileTracker(openKv())` - Simple file tracking
28
+ - `VersoriIndexedFileTracker(openKv())` - File tracking with listing support
29
+ - `StateService(logger)` - High-level state operations
30
+ - `stateService.isFileProcessed(kv, key)` - Check if processed
31
+ - `stateService.acquireLock(lockName, kv, timeoutMinutes)` - Distributed locking
32
+ - `stateService.getSyncState(kv, workflowId)` - Get sync state
33
+ - `stateService.updateSyncState(kv, files, workflowId)` - Update sync state
34
+ - `stateService.getDailyJob(kv, workflowId)` - Get daily job
35
+ - `stateService.setDailyJob(kv, workflowId, jobId, hours)` - Store daily job
36
+
37
+ ---
38
+
39
+ ## Versori Workflows Structure
40
+
41
+ **Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
42
+
43
+ **Trigger Types:**
44
+ - **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
45
+ - **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
46
+ - **`http()`** → External API calls (chained from webhook/schedule)
47
+ - **`fn()`** → Internal processing (chained from webhook/schedule)
48
+
49
+ ### Recommended Project Structure
50
+
51
+ ```
52
+ kv-state-management/
53
+ ├── index.ts # Entry point - exports all workflows
54
+ └── src/
55
+ ├── workflows/
56
+ │ ├── webhook/
57
+ │ │ └── file-ingestion.ts # Webhook: File processing
58
+ │ │
59
+ │ └── scheduled/
60
+ │ └── daily-sync.ts # Scheduled: Daily sync
61
+
62
+ ├── services/
63
+ │ └── state-management.service.ts # Shared state logic (reusable)
64
+
65
+ └── config/
66
+ └── state-config.json # Configuration
67
+ ```
68
+
69
+ **Benefits:**
70
+ - ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
71
+ - ✅ Descriptive file names (easy to browse and understand)
72
+ - ✅ Scalable (add new workflows without cluttering)
73
+ - ✅ Reusable code in `services/` (DRY principle)
74
+ - ✅ Easy to modify individual workflows without affecting others
75
+
76
+ ---
77
+
78
+ ## Complete Working Code
79
+
80
+ ### Pattern 1: Simple File Duplicate Prevention
81
+
82
+ **Use Case**: Prevent reprocessing the same files in ingestion workflows.
83
+
84
+ ```typescript
85
+ import { webhook, fn } from '@versori/run';
86
+ // FC Connect SDK+
87
+ // Install: npm install @fluentcommerce/fc-connect-sdk@latest
88
+ // Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
89
+ // GitHub: https://github.com/fluentcommerce/fc-connect-sdk
90
+ import { VersoriFileTracker } from '@fluentcommerce/fc-connect-sdk';
91
+
92
+ /**
93
+ * Simple file tracking pattern
94
+ * Perfect for basic duplicate prevention without complex state management
95
+ */
96
+ export const simpleFileIngestion = webhook('ingest-files', {
97
+ response: { mode: 'sync' }
98
+ })
99
+ .then(fn('check-and-process-files', async (ctx) => {
100
+ const { openKv, log, data } = ctx;
101
+
102
+ // Initialize simple file tracker
103
+ const fileTracker = new VersoriFileTracker(openKv(':project:'), 'inventory-ingestion');
104
+
105
+ // Sample files to process
106
+ const files = data.files || [
107
+ { name: 'inventory-2025-01-15.csv', url: 's3://bucket/file1.csv' },
108
+ { name: 'inventory-2025-01-16.csv', url: 's3://bucket/file2.csv' }
109
+ ];
110
+
111
+ const processedFiles = [];
112
+ const skippedFiles = [];
113
+
114
+ for (const file of files) {
115
+ // Check if file was already processed
116
+ const wasProcessed = await fileTracker.wasFileProcessed(file.name);
117
+
118
+ if (wasProcessed) {
119
+ log.info(`⏭️ [FileTracking] Skipping already processed file: ${file.name}`);
120
+ skippedFiles.push(file.name);
121
+ continue;
122
+ }
123
+
124
+ try {
125
+ // Process the file (your ingestion logic here)
126
+ log.info(`🚀 [Processing] Processing file: ${file.name}`);
127
+ const recordCount = await processFile(file);
128
+
129
+ // Mark as processed with metadata
130
+ await fileTracker.markFileProcessed(file.name, {
131
+ recordCount,
132
+ processedBy: 'webhook-ingestion',
133
+ source: file.url
134
+ });
135
+
136
+ processedFiles.push({ name: file.name, recordCount });
137
+ log.info(`✅ [Success] Successfully processed: ${file.name} (${recordCount} records)`);
138
+ } catch (error) {
139
+ // ? Enhanced: Error logging with recommendations
140
+ log.error('[KVStateManagement] Failed to process file', {
141
+ fileName: file.name,
142
+ error: error instanceof Error ? error.message : String(error),
143
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
144
+ recommendation: error.message?.includes('parse') || error.message?.includes('format')
145
+ ? 'Check file format and structure - ensure file is valid'
146
+ : error.message?.includes('mapping') || error.message?.includes('field')
147
+ ? 'Check mapping configuration and verify file column structure'
148
+ : error.message?.includes('connection') || error.message?.includes('timeout')
149
+ ? 'Check network connectivity and data source availability'
150
+ : 'Review error details and check file processing logic'
151
+ });
152
+ throw error; // Fail workflow so we can retry later
153
+ }
154
+ }
155
+
156
+ // Track last processed file
157
+ if (processedFiles.length > 0) {
158
+ const lastFile = processedFiles[processedFiles.length - 1];
159
+ await fileTracker.setLastProcessedFile(lastFile.name);
160
+ }
161
+
162
+ return {
163
+ success: true,
164
+ processed: processedFiles,
165
+ skipped: skippedFiles,
166
+ lastProcessedFile: await fileTracker.getLastProcessedFile()
167
+ };
168
+ }));
169
+
170
+ // Helper function (implement based on your data source)
171
+ async function processFile(file: { name: string; url: string }): Promise<number> {
172
+ // Your file processing logic
173
+ return 1000; // Return record count
174
+ }
175
+ ```
176
+
177
+ **Key Points**:
178
+
179
+ - `VersoriFileTracker` is lightweight - perfect for simple use cases
180
+ - Automatic duplicate prevention with `wasFileProcessed()`
181
+ - Stores metadata like record count with each file
182
+ - Tracks last processed file for incremental processing
183
+
184
+ ---
185
+
186
+ ### Pattern 2: Distributed Locking with StateService
187
+
188
+ **Use Case**: Prevent concurrent workflow executions with distributed locks.
189
+
190
+ ```typescript
191
+ import { schedule, fn } from '@versori/run';
192
+ import { StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
193
+
194
+ /**
195
+ * Distributed locking pattern
196
+ * Prevents multiple instances from running simultaneously
197
+ */
198
+ export const scheduledIngestionWithLock = schedule('inventory-sync', '0 */6 * * *')
199
+ .then(fn('acquire-lock', async (ctx) => {
200
+ const { openKv, log } = ctx;
201
+
202
+ // Create StateService with KV adapter
203
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
204
+ const stateService = new StateService(log);
205
+
206
+ // Try to acquire lock (15 minute timeout)
207
+ const lockName = 'inventory-sync-lock';
208
+ const lockAcquired = await stateService.acquireLock(lockName, kvAdapter, 15);
209
+
210
+ if (!lockAcquired) {
211
+ log.warn('⚠️ [Lock] Lock already held, skipping this run');
212
+ return {
213
+ shouldSkip: true,
214
+ reason: 'Lock already held by another instance'
215
+ };
216
+ }
217
+
218
+ log.info('🔐 [Lock] Lock acquired successfully');
219
+ return {
220
+ shouldSkip: false,
221
+ lockName,
222
+ kvAdapter,
223
+ stateService
224
+ };
225
+ }))
226
+ .then(fn('process-with-lock', async ({ data, log }) => {
227
+ if (data.shouldSkip) {
228
+ return data;
229
+ }
230
+
231
+ const { kvAdapter, stateService, lockName } = data;
232
+
233
+ try {
234
+ // Your ingestion logic here
235
+ log.info('🚀 [Processing] Processing inventory sync...');
236
+ await performIngestion();
237
+ log.info('✅ [Success] Ingestion completed successfully');
238
+
239
+ return { success: true };
240
+ } finally {
241
+ // ALWAYS release lock in finally block
242
+ await stateService.releaseLock(lockName, kvAdapter);
243
+ log.info('🔓 [Lock] Lock released');
244
+ }
245
+ }))
246
+ .catch(fn('handle-error-and-release-lock', async ({ data, error, log }) => {
247
+ // Ensure lock is released even on error
248
+ if (data?.stateService && data?.lockName && data?.kvAdapter) {
249
+ await data.stateService.releaseLock(data.lockName, data.kvAdapter);
250
+ log.info('🔓 [Lock] Lock released after error');
251
+ }
252
+
253
+ log.error('❌ [Error] Ingestion failed', error as Error);
254
+ return { success: false, error: (error as Error).message };
255
+ }));
256
+
257
+ async function performIngestion(): Promise<void> {
258
+ // Your ingestion logic
259
+ await new Promise(resolve => setTimeout(resolve, 1000));
260
+ }
261
+ ```
262
+
263
+ **Key Points**:
264
+
265
+ - Distributed locking prevents concurrent executions
266
+ - Stale lock detection (automatically overrides expired locks)
267
+ - Lock timeout ensures recovery from crashes
268
+ - ALWAYS release locks in finally blocks
269
+ - Proper error handling to prevent lock leaks
270
+
271
+ ---
272
+
273
+ ### Pattern 3: Checkpoint & Resume
274
+
275
+ **Use Case**: Save progress during long-running workflows and resume from last checkpoint.
276
+
277
+ ```typescript
278
+ import { webhook, fn } from '@versori/run';
279
+ import { StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
280
+
281
+ interface CheckpointData {
282
+ currentBatch: number;
283
+ totalBatches: number;
284
+ processedRecords: number;
285
+ lastProcessedId: string;
286
+ startTime: string;
287
+ }
288
+
289
+ /**
290
+ * Checkpoint & resume pattern
291
+ * Useful for long-running batch processing that might fail partway through
292
+ */
293
+ export const batchProcessingWithCheckpoints = webhook('process-batches', {
294
+ response: { mode: 'sync' }
295
+ })
296
+ .then(fn('check-checkpoint', async (ctx) => {
297
+ const { openKv, log, data } = ctx;
298
+
299
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
300
+ const stateService = new StateService(log);
301
+ const workflowId = 'batch-processing';
302
+ const checkpointKey = `checkpoint:${workflowId}`;
303
+
304
+ // Try to restore from checkpoint
305
+ const syncState = await stateService.getSyncState(kvAdapter, workflowId);
306
+ let checkpoint: CheckpointData | null = null;
307
+
308
+ if (syncState.isInitialized) {
309
+ // Check if there's a saved checkpoint (stored in custom state)
310
+ const stored = await kvAdapter.get([checkpointKey]);
311
+ if (stored?.value) {
312
+ checkpoint = stored.value as CheckpointData;
313
+ log.info('♻️ [Checkpoint] Resuming from checkpoint', checkpoint);
314
+ }
315
+ }
316
+
317
+ // Get batches to process
318
+ const allBatches = data.batches || generateBatches(100); // 100 batches total
319
+
320
+ // Determine starting point
321
+ const startBatch = checkpoint ? checkpoint.currentBatch : 0;
322
+ const processedSoFar = checkpoint ? checkpoint.processedRecords : 0;
323
+
324
+ return {
325
+ batches: allBatches,
326
+ startBatch,
327
+ processedSoFar,
328
+ kvAdapter,
329
+ stateService,
330
+ workflowId,
331
+ checkpointKey,
332
+ startTime: checkpoint?.startTime || new Date().toISOString()
333
+ };
334
+ }))
335
+ .then(fn('process-with-checkpoints', async ({ data, log }) => {
336
+ const {
337
+ batches,
338
+ startBatch,
339
+ processedSoFar,
340
+ kvAdapter,
341
+ stateService,
342
+ workflowId,
343
+ checkpointKey,
344
+ startTime
345
+ } = data;
346
+
347
+ let processedRecords = processedSoFar;
348
+
349
+ // Process batches starting from checkpoint
350
+ for (let i = startBatch; i < batches.length; i++) {
351
+ const batch = batches[i];
352
+
353
+ try {
354
+ log.info(`🚀 [Processing] Processing batch ${i + 1}/${batches.length}`);
355
+
356
+ // Process the batch
357
+ const result = await processBatch(batch);
358
+ processedRecords += result.recordCount;
359
+
360
+ // Save checkpoint after each batch (or every N batches)
361
+ if ((i + 1) % 10 === 0 || i === batches.length - 1) {
362
+ const checkpoint: CheckpointData = {
363
+ currentBatch: i + 1,
364
+ totalBatches: batches.length,
365
+ processedRecords,
366
+ lastProcessedId: result.lastId,
367
+ startTime
368
+ };
369
+
370
+ await kvAdapter.set([checkpointKey], checkpoint);
371
+ log.info(`💾 [Checkpoint] Checkpoint saved at batch ${i + 1}`);
372
+ }
373
+ } catch (error) {
374
+ // ? Enhanced: Error logging with recommendations
375
+ log.error('[KVStateManagement] Batch processing failed', {
376
+ batchNumber: i + 1,
377
+ error: error instanceof Error ? error.message : String(error),
378
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
379
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
380
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
381
+ : error.message?.includes('mutation') || error.message?.includes('GraphQL')
382
+ ? 'Check GraphQL mutation syntax and batch payload structure'
383
+ : error.message?.includes('connection') || error.message?.includes('timeout')
384
+ ? 'Check network connectivity and Fluent Commerce API availability'
385
+ : 'Review error details - checkpoint saved for retry'
386
+ });
387
+
388
+ // Save checkpoint at failure point
389
+ const checkpoint: CheckpointData = {
390
+ currentBatch: i, // Retry this batch
391
+ totalBatches: batches.length,
392
+ processedRecords,
393
+ lastProcessedId: batch[0]?.id || '',
394
+ startTime
395
+ };
396
+
397
+ await kvAdapter.set([checkpointKey], checkpoint);
398
+ throw error; // Fail workflow so it can be retried
399
+ }
400
+ }
401
+
402
+ // All batches processed - clear checkpoint
403
+ await kvAdapter.delete([checkpointKey]);
404
+ log.info('✅ [Checkpoint] All batches processed, checkpoint cleared');
405
+
406
+ // Update final sync state
407
+ await stateService.updateSyncState(kvAdapter, [{
408
+ fileName: 'batch-processing-complete',
409
+ lastModified: new Date().toISOString(),
410
+ recordCount: processedRecords
411
+ }], workflowId);
412
+
413
+ return {
414
+ success: true,
415
+ totalBatches: batches.length,
416
+ processedRecords,
417
+ duration: Date.now() - new Date(startTime).getTime()
418
+ };
419
+ }));
420
+
421
+ function generateBatches(count: number): any[] {
422
+ return Array.from({ length: count }, (_, i) => ({
423
+ id: `batch-${i + 1}`,
424
+ data: []
425
+ }));
426
+ }
427
+
428
+ async function processBatch(batch: any): Promise<{ recordCount: number; lastId: string }> {
429
+ // Your batch processing logic
430
+ await new Promise(resolve => setTimeout(resolve, 100));
431
+ return { recordCount: 50, lastId: batch.id };
432
+ }
433
+ ```
434
+
435
+ **Key Points**:
436
+
437
+ - Save checkpoints periodically (not after every operation)
438
+ - Store enough context to resume exactly where you left off
439
+ - Clear checkpoint after successful completion
440
+ - On failure, checkpoint allows resuming without reprocessing
441
+
442
+ ---
443
+
444
+ ### Pattern 4: Daily Job Management
445
+
446
+ **Use Case**: Reuse jobs across multiple batches throughout the day (DAILY strategy).
447
+
448
+ ```typescript
449
+ import { webhook, fn } from '@versori/run';
450
+ import {
451
+ StateService,
452
+ VersoriKVAdapter,
453
+ createClient
454
+ } from '@fluentcommerce/fc-connect-sdk';
455
+
456
+ /**
457
+ * Daily job management pattern
458
+ * Reuses a single job for multiple batches within a day
459
+ */
460
+ export const dailyJobIngestion = webhook('ingest-daily', {
461
+ response: { mode: 'sync' }
462
+ })
463
+ .then(fn('get-or-create-job', async (ctx) => {
464
+ // Destructure context inside function body
465
+ const { openKv, log, connections, activation } = ctx;
466
+
467
+ const kvAdapter = new VersoriKVAdapter(openKv());
468
+ const stateService = new StateService(log);
469
+ const workflowId = 'daily-inventory-ingestion';
470
+
471
+ // Check for existing daily job
472
+ const existingJob = await stateService.getDailyJob(kvAdapter, workflowId);
473
+
474
+ let jobId: string;
475
+ let isNewJob = false;
476
+
477
+ if (existingJob && existingJob.jobId) {
478
+ // Reuse existing job
479
+ jobId = existingJob.jobId;
480
+ log.info(`♻️ [DailyJob] Reusing daily job: ${jobId}`, {
481
+ createdAt: existingJob.createdAt,
482
+ expiresAt: existingJob.expiresAt
483
+ });
484
+ } else {
485
+ // Create new job
486
+ const client = await createClient(ctx); // Auto-detects Versori context
487
+
488
+ const job = await client.createJob({
489
+ name: `daily-inventory-${new Date().toISOString().split('T')[0]}`,
490
+ retailerId: activation.getVariable('fluentRetailerId') as string
491
+ });
492
+
493
+ jobId = job.id;
494
+ isNewJob = true;
495
+
496
+ // Store job for 24 hours
497
+ await stateService.setDailyJob(kvAdapter, workflowId, jobId, 24);
498
+ log.info(`🆕 [DailyJob] Created new daily job: ${jobId}`);
499
+ }
500
+
501
+ return {
502
+ jobId,
503
+ isNewJob,
504
+ kvAdapter,
505
+ stateService,
506
+ workflowId,
507
+ client: await createClient(ctx) // Auto-detects Versori context
508
+ };
509
+ }))
510
+ .then(fn('send-batches-to-job', async ({ data, log }) => {
511
+ const { jobId, client, kvAdapter, stateService, workflowId } = data;
512
+
513
+ // Your batches to process
514
+ const batches = [
515
+ { entities: [{ skuRef: 'SKU001', qty: 100 }] },
516
+ { entities: [{ skuRef: 'SKU002', qty: 200 }] }
517
+ ];
518
+
519
+ const batchResults = [];
520
+
521
+ for (const batch of batches) {
522
+ try {
523
+ const result = await client.sendBatch(jobId, {
524
+ action: 'UPSERT',
525
+ entityType: 'INVENTORY_CATALOGUE',
526
+ entities: batch.entities
527
+ });
528
+
529
+ batchResults.push({ success: true, batchId: result.id });
530
+ log.info(`✅ [Batch] Batch sent to job ${jobId}: ${result.id}`);
531
+ } catch (error) {
532
+ // ? Enhanced: Error logging with recommendations
533
+ log.error('[KVStateManagement] Batch submission failed', {
534
+ batchId: result?.id,
535
+ error: error instanceof Error ? error.message : String(error),
536
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
537
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
538
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
539
+ : error.message?.includes('batch') || error.message?.includes('job')
540
+ ? 'Check batch API payload and job status'
541
+ : error.message?.includes('connection') || error.message?.includes('timeout')
542
+ ? 'Check network connectivity and Fluent Commerce API availability'
543
+ : 'Review error details and check batch submission payload'
544
+ });
545
+ batchResults.push({ success: false, error: (error as Error).message });
546
+ }
547
+ }
548
+
549
+ // Update sync state with processed records
550
+ await stateService.updateSyncState(kvAdapter, [{
551
+ fileName: `daily-batch-${new Date().toISOString()}`,
552
+ lastModified: new Date().toISOString(),
553
+ recordCount: batches.reduce((sum, b) => sum + b.entities.length, 0)
554
+ }], workflowId);
555
+
556
+ return {
557
+ success: true,
558
+ jobId,
559
+ batchCount: batchResults.length,
560
+ successCount: batchResults.filter(r => r.success).length
561
+ };
562
+ }));
563
+ ```
564
+
565
+ **Key Points**:
566
+
567
+ - Single job per day reduces API overhead
568
+ - Job expires automatically after 24 hours
569
+ - Tracks batch count per job
570
+ - Perfect for high-frequency ingestion workflows
571
+
572
+ ---
573
+
574
+ ### Pattern 5: Error State Management
575
+
576
+ **Use Case**: Track errors and implement retry logic with exponential backoff.
577
+
578
+ ```typescript
579
+ import { webhook, fn } from '@versori/run';
580
+ import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
581
+
582
+ interface ErrorState {
583
+ fileName: string;
584
+ attemptCount: number;
585
+ lastError: string;
586
+ lastAttemptAt: string;
587
+ firstFailedAt: string;
588
+ nextRetryAt: string;
589
+ }
590
+
591
+ /**
592
+ * Error state management pattern
593
+ * Tracks failures and implements smart retry logic
594
+ */
595
+ export const retryableIngestion = webhook('ingest-with-retry', {
596
+ response: { mode: 'sync' }
597
+ })
598
+ .then(fn('process-with-retry-tracking', async (ctx) => {
599
+ const { openKv, log, data } = ctx;
600
+
601
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
602
+ const files = data.files || ['file1.csv', 'file2.csv', 'file3.csv'];
603
+
604
+ const results = {
605
+ succeeded: [] as string[],
606
+ failed: [] as string[],
607
+ skipped: [] as string[]
608
+ };
609
+
610
+ for (const fileName of files) {
611
+ const errorStateKey = ['error-state', fileName];
612
+
613
+ // Check if file has error state
614
+ const errorStateResult = await kvAdapter.get(errorStateKey);
615
+ const errorState = errorStateResult?.value as ErrorState | undefined;
616
+
617
+ // Check if we should retry
618
+ if (errorState) {
619
+ const nextRetry = new Date(errorState.nextRetryAt);
620
+ const now = new Date();
621
+
622
+ if (now < nextRetry) {
623
+ log.info(`⏭️ [Retry] Skipping ${fileName} until ${errorState.nextRetryAt}`, {
624
+ attemptCount: errorState.attemptCount,
625
+ lastError: errorState.lastError
626
+ });
627
+ results.skipped.push(fileName);
628
+ continue;
629
+ }
630
+
631
+ // Max retry attempts reached?
632
+ if (errorState.attemptCount >= 5) {
633
+ log.warn(`⚠️ [Retry] Max retries exceeded for ${fileName}, requires manual intervention`);
634
+ results.skipped.push(fileName);
635
+ continue;
636
+ }
637
+
638
+ log.info(`♻️ [Retry] Retrying ${fileName} (attempt ${errorState.attemptCount + 1})`, {
639
+ lastError: errorState.lastError,
640
+ firstFailedAt: errorState.firstFailedAt
641
+ });
642
+ }
643
+
644
+ try {
645
+ // Process the file
646
+ await processFileWithPossibleFailure(fileName);
647
+
648
+ // Success! Clear error state
649
+ if (errorState) {
650
+ await kvAdapter.delete(errorStateKey);
651
+ log.info(`✅ [Retry] File recovered after ${errorState.attemptCount} retries: ${fileName}`);
652
+ }
653
+
654
+ results.succeeded.push(fileName);
655
+ } catch (error) {
656
+ // ? Enhanced: Error logging with recommendations
657
+ const errorMessage = (error as Error).message;
658
+ const attemptCount = errorState ? errorState.attemptCount + 1 : 1;
659
+ const now = new Date();
660
+
661
+ // Calculate next retry with exponential backoff
662
+ const backoffMinutes = Math.pow(2, attemptCount) * 5; // 5, 10, 20, 40, 80 minutes
663
+ const nextRetryAt = new Date(now.getTime() + backoffMinutes * 60 * 1000);
664
+
665
+ // Save error state
666
+ const newErrorState: ErrorState = {
667
+ fileName,
668
+ attemptCount,
669
+ lastError: errorMessage,
670
+ lastAttemptAt: now.toISOString(),
671
+ firstFailedAt: errorState?.firstFailedAt || now.toISOString(),
672
+ nextRetryAt: nextRetryAt.toISOString()
673
+ };
674
+
675
+ await kvAdapter.set(errorStateKey, newErrorState);
676
+
677
+ log.error('[KVStateManagement] File processing failed', {
678
+ fileName,
679
+ attemptCount,
680
+ error: error instanceof Error ? error.message : String(error),
681
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
682
+ nextRetryAt: nextRetryAt.toISOString(),
683
+ backoffMinutes,
684
+ recommendation: error.message?.includes('parse') || error.message?.includes('format')
685
+ ? 'Check file format and structure - ensure file is valid'
686
+ : error.message?.includes('mapping') || error.message?.includes('field')
687
+ ? 'Check mapping configuration and verify file column structure'
688
+ : error.message?.includes('connection') || error.message?.includes('timeout')
689
+ ? 'Check network connectivity and data source availability'
690
+ : `Will retry after ${backoffMinutes} minutes - review error details`
691
+ });
692
+
693
+ results.failed.push(fileName);
694
+ }
695
+ }
696
+
697
+ return {
698
+ success: true,
699
+ results,
700
+ summary: {
701
+ succeeded: results.succeeded.length,
702
+ failed: results.failed.length,
703
+ skipped: results.skipped.length
704
+ }
705
+ };
706
+ }))
707
+ .then(fn('cleanup-old-errors', async (ctx) => {
708
+ const { openKv, log, data } = ctx;
709
+
710
+ // Cleanup error states older than 7 days
711
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
712
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
713
+
714
+ // Note: This requires tracking error state keys separately
715
+ // In production, use VersoriIndexedFileTracker pattern
716
+
717
+ log.info('Error state cleanup completed');
718
+ return data;
719
+ }));
720
+
721
+ async function processFileWithPossibleFailure(fileName: string): Promise<void> {
722
+ // Simulate random failures for demonstration
723
+ if (Math.random() < 0.3) {
724
+ throw new Error('Simulated processing failure');
725
+ }
726
+ await new Promise(resolve => setTimeout(resolve, 100));
727
+ }
728
+ ```
729
+
730
+ **Key Points**:
731
+
732
+ - Exponential backoff prevents overwhelming failing systems
733
+ - Track first failure time for monitoring
734
+ - Max retry limit prevents infinite loops
735
+ - Clear error state on success
736
+ - Skip files until retry time
737
+
738
+ ---
739
+
740
+ ### Pattern 6: Advanced File Tracking with Indexing
741
+
742
+ **Use Case**: Track files with ability to list all processed files.
743
+
744
+ ```typescript
745
+ import { webhook, fn } from '@versori/run';
746
+ import { VersoriIndexedFileTracker } from '@fluentcommerce/fc-connect-sdk';
747
+
748
+ /**
749
+ * Advanced file tracking with indexing
750
+ * Allows listing all processed files (overcomes Versori KV list limitation)
751
+ */
752
+ export const indexedFileTracking = webhook('manage-files', {
753
+ response: { mode: 'sync' }
754
+ })
755
+ .then(fn('track-and-manage-files', async (ctx) => {
756
+ const { openKv, log, data } = ctx;
757
+
758
+ // Initialize indexed file tracker
759
+ const fileTracker = new VersoriIndexedFileTracker(openKv(':project:'), 'inventory-ingestion');
760
+ const operation = data.operation || 'process'; // process, list, stats, cleanup
761
+
762
+ switch (operation) {
763
+ case 'process': {
764
+ const files = data.files || ['file1.csv', 'file2.csv'];
765
+
766
+ for (const file of files) {
767
+ // Process file
768
+ log.info(`🚀 [Processing] Processing: ${file}`);
769
+ const recordCount = Math.floor(Math.random() * 1000) + 100;
770
+
771
+ // Mark as processed (automatically updates index)
772
+ await fileTracker.markFileProcessed(file, {
773
+ recordCount,
774
+ source: 's3://bucket/' + file,
775
+ processingDuration: 1234
776
+ });
777
+ }
778
+
779
+ return { success: true, processed: files.length };
780
+ }
781
+
782
+ case 'list': {
783
+ // List all processed files
784
+ const processedFiles = await fileTracker.listProcessedFiles();
785
+ log.info(`📋 [Index] Found ${processedFiles.length} processed files`);
786
+
787
+ return {
788
+ success: true,
789
+ files: processedFiles.map(f => ({
790
+ name: f.fileName,
791
+ processedAt: f.metadata?.processedAt,
792
+ recordCount: f.metadata?.recordCount
793
+ }))
794
+ };
795
+ }
796
+
797
+ case 'stats': {
798
+ // Get processing statistics
799
+ const stats = await fileTracker.getStats();
800
+ log.info('📊 [Index] File processing statistics', stats);
801
+
802
+ return {
803
+ success: true,
804
+ stats
805
+ };
806
+ }
807
+
808
+ case 'cleanup': {
809
+ // Clear all processed files
810
+ const result = await fileTracker.clearAllProcessedFiles();
811
+ log.info(`🧹 [Index] Cleanup complete: ${result.success} deleted, ${result.failed} failed`);
812
+
813
+ return {
814
+ success: true,
815
+ deleted: result.success,
816
+ failed: result.failed
817
+ };
818
+ }
819
+
820
+ case 'remove': {
821
+ // Remove specific file
822
+ const fileName = data.fileName;
823
+ if (!fileName) {
824
+ throw new Error('fileName required for remove operation');
825
+ }
826
+
827
+ await fileTracker.removeProcessedFile(fileName);
828
+ log.info(`🗑️ [Index] Removed file: ${fileName}`);
829
+
830
+ return { success: true, removed: fileName };
831
+ }
832
+
833
+ default:
834
+ throw new Error(`Unknown operation: ${operation}`);
835
+ }
836
+ }));
837
+ ```
838
+
839
+ **Key Points**:
840
+
841
+ - Maintains internal index to overcome Versori KV list limitation
842
+ - List all processed files
843
+ - Get aggregate statistics
844
+ - Cleanup operations
845
+ - Automatic index maintenance
846
+
847
+ ---
848
+
849
+ ### Pattern 7: MemoryInterpreter for In-Memory State
850
+
851
+ **Use Case**: Store interpreter state in memory for long-running connections (SFTP, database connections).
852
+
853
+ ```typescript
854
+ import { webhook, fn } from '@versori/run';
855
+ import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
856
+
857
+ interface MemoryInterpreterState {
858
+ sftpConnections: Map<string, any>;
859
+ lastActivity: Map<string, number>;
860
+ connectionPool: any[];
861
+ }
862
+
863
+ /**
864
+ * MemoryInterpreter pattern
865
+ * Maintains in-memory state for connection pools and active sessions
866
+ */
867
+ export const connectionManager = webhook('manage-connections', {
868
+ response: { mode: 'sync' }
869
+ })
870
+ .then(fn('initialize-memory-state', async (ctx) => {
871
+ const { openKv, log } = ctx;
872
+
873
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
874
+ const stateKey = ['memory-interpreter', 'connections'];
875
+
876
+ // Load existing state or initialize new
877
+ const existingState = await kvAdapter.get(stateKey);
878
+
879
+ const memoryState: MemoryInterpreterState = existingState?.value || {
880
+ sftpConnections: new Map(),
881
+ lastActivity: new Map(),
882
+ connectionPool: []
883
+ };
884
+
885
+ log.info('🧠 [MemoryInterpreter] State initialized', {
886
+ activeConnections: memoryState.sftpConnections.size,
887
+ poolSize: memoryState.connectionPool.length
888
+ });
889
+
890
+ return {
891
+ kvAdapter,
892
+ stateKey,
893
+ memoryState
894
+ };
895
+ }))
896
+ .then(fn('process-with-memory-state', async ({ data, log }) => {
897
+ const { kvAdapter, stateKey, memoryState } = data;
898
+
899
+ // Use memory state for processing
900
+ const connectionId = 'sftp-main';
901
+ const now = Date.now();
902
+
903
+ // Check if connection is stale (> 5 minutes inactive)
904
+ const lastActivity = memoryState.lastActivity.get(connectionId);
905
+ const isStale = lastActivity && (now - lastActivity) > 5 * 60 * 1000;
906
+
907
+ if (isStale) {
908
+ log.info('♻️ [MemoryInterpreter] Refreshing stale connection', {
909
+ connectionId,
910
+ lastActivity: new Date(lastActivity).toISOString()
911
+ });
912
+
913
+ // Refresh connection
914
+ memoryState.sftpConnections.delete(connectionId);
915
+ }
916
+
917
+ // Update activity timestamp
918
+ memoryState.lastActivity.set(connectionId, now);
919
+
920
+ // Persist updated state
921
+ await kvAdapter.set(stateKey, {
922
+ sftpConnections: Array.from(memoryState.sftpConnections.entries()),
923
+ lastActivity: Array.from(memoryState.lastActivity.entries()),
924
+ connectionPool: memoryState.connectionPool
925
+ });
926
+
927
+ log.info('💾 [MemoryInterpreter] State persisted', {
928
+ connections: memoryState.sftpConnections.size,
929
+ lastUpdate: new Date(now).toISOString()
930
+ });
931
+
932
+ return {
933
+ success: true,
934
+ activeConnections: memoryState.sftpConnections.size,
935
+ lastActivity: now
936
+ };
937
+ }));
938
+ ```
939
+
940
+ **Key Points**:
941
+
942
+ - Use MemoryInterpreter for connection pools and active sessions
943
+ - Persist state to KV to survive workflow restarts
944
+ - Implement stale connection detection and refresh
945
+ - Track activity timestamps for connection reuse
946
+ - Serialize/deserialize Maps and complex objects properly
947
+
948
+ ---
949
+
950
+ ### Pattern 8: Connection Validation
951
+
952
+ **Use Case**: Validate data source connections before processing to catch configuration errors early.
953
+
954
+ ```typescript
955
+ import { webhook, fn } from '@versori/run';
956
+ import {
957
+ SftpDataSource,
958
+ S3DataSource,
959
+ createClient
960
+ } from '@fluentcommerce/fc-connect-sdk';
961
+
962
+ /**
963
+ * Connection validation pattern
964
+ * Validates all data sources before processing
965
+ */
966
+ export const validatedIngestion = webhook('ingest-with-validation', {
967
+ response: { mode: 'sync' }
968
+ })
969
+ .then(fn('validate-connections', async (ctx) => {
970
+ const { log, activation } = ctx;
971
+
972
+ log.info('🔍 [Validation] Starting connection validation');
973
+
974
+ // Validate SFTP connection
975
+ const sftp = new SftpDataSource(
976
+ {
977
+ type: 'SFTP_XML',
978
+ connectionId: 'sftp-validation',
979
+ name: 'validated-sftp',
980
+ settings: {
981
+ host: activation.getVariable('sftpHost'),
982
+ port: parseInt(activation.getVariable('sftpPort') || '22', 10),
983
+ username: activation.getVariable('sftpUsername'),
984
+ password: activation.getVariable('sftpPassword'),
985
+ remotePath: '/incoming/',
986
+ encoding: 'utf8',
987
+ requireAbsolutePaths: true
988
+ }
989
+ },
990
+ log
991
+ );
992
+
993
+ try {
994
+ await sftp.validateConnection();
995
+ log.info('✅ [Validation] SFTP connection validated successfully');
996
+ } catch (error) {
997
+ log.error('❌ [Validation] SFTP connection failed', {
998
+ error: error instanceof Error ? error.message : String(error),
999
+ recommendation: error.message?.includes('authentication')
1000
+ ? 'Check SFTP username and password in connection configuration'
1001
+ : error.message?.includes('timeout')
1002
+ ? 'Check SFTP host and port - ensure server is reachable'
1003
+ : error.message?.includes('host')
1004
+ ? 'Verify SFTP host address is correct'
1005
+ : 'Review SFTP connection settings and server logs'
1006
+ });
1007
+ throw error;
1008
+ }
1009
+
1010
+ // Validate S3 connection
1011
+ const s3 = new S3DataSource(
1012
+ {
1013
+ type: 'S3_CSV',
1014
+ connectionId: 's3-validation',
1015
+ name: 'validated-s3',
1016
+ settings: {
1017
+ region: activation.getVariable('awsRegion') || 'us-east-1',
1018
+ bucket: activation.getVariable('s3Bucket'),
1019
+ prefix: 'incoming/',
1020
+ accessKeyId: activation.getVariable('awsAccessKeyId'),
1021
+ secretAccessKey: activation.getVariable('awsSecretAccessKey')
1022
+ }
1023
+ },
1024
+ log
1025
+ );
1026
+
1027
+ try {
1028
+ await s3.validateConnection();
1029
+ log.info('✅ [Validation] S3 connection validated successfully');
1030
+ } catch (error) {
1031
+ log.error('❌ [Validation] S3 connection failed', {
1032
+ error: error instanceof Error ? error.message : String(error),
1033
+ recommendation: error.message?.includes('credentials')
1034
+ ? 'Check AWS access key and secret in connection configuration'
1035
+ : error.message?.includes('bucket')
1036
+ ? 'Verify S3 bucket exists and credentials have access'
1037
+ : error.message?.includes('region')
1038
+ ? 'Check AWS region is correct for the bucket'
1039
+ : 'Review S3 connection settings and IAM permissions'
1040
+ });
1041
+ throw error;
1042
+ }
1043
+
1044
+ // Validate Fluent Commerce API connection
1045
+ const client = await createClient(ctx);
1046
+ const retailerId = activation.getVariable('fluentRetailerId');
1047
+
1048
+ if (!retailerId) {
1049
+ log.error('❌ [Validation] Missing retailerId activation variable');
1050
+ throw new Error('fluentRetailerId is required for Fluent API operations');
1051
+ }
1052
+
1053
+ client.setRetailerId(retailerId);
1054
+
1055
+ try {
1056
+ // Test connection with a simple query
1057
+ const testQuery = `query { retailers { edges { node { id } } } }`;
1058
+ await client.graphql({ query: testQuery });
1059
+ log.info('✅ [Validation] Fluent Commerce API connection validated');
1060
+ } catch (error) {
1061
+ log.error('❌ [Validation] Fluent Commerce API connection failed', {
1062
+ error: error instanceof Error ? error.message : String(error),
1063
+ recommendation: error.message?.includes('401') || error.message?.includes('authentication')
1064
+ ? 'Verify fluent_commerce connection OAuth2 credentials'
1065
+ : error.message?.includes('GraphQL')
1066
+ ? 'Check GraphQL query syntax and permissions'
1067
+ : 'Review Fluent Commerce API connection settings'
1068
+ });
1069
+ throw error;
1070
+ }
1071
+
1072
+ log.info('✅ [Validation] All connections validated successfully');
1073
+
1074
+ return {
1075
+ sftp,
1076
+ s3,
1077
+ client,
1078
+ validated: true
1079
+ };
1080
+ }))
1081
+ .then(fn('process-with-validated-connections', async ({ data, log }) => {
1082
+ const { sftp, s3, client } = data;
1083
+
1084
+ log.info('🚀 [Processing] Starting ingestion with validated connections');
1085
+
1086
+ try {
1087
+ // Your ingestion logic here with validated connections
1088
+ const files = await sftp.listFiles({ remotePath: '/incoming/' });
1089
+
1090
+ log.info('📋 [Processing] Files discovered', { count: files.length });
1091
+
1092
+ // Process files...
1093
+
1094
+ return {
1095
+ success: true,
1096
+ filesProcessed: files.length
1097
+ };
1098
+ } finally {
1099
+ // Always dispose connections
1100
+ await sftp.dispose();
1101
+ await s3.dispose();
1102
+ log.info('🧹 [Cleanup] Connections disposed');
1103
+ }
1104
+ }));
1105
+ ```
1106
+
1107
+ **Key Points**:
1108
+
1109
+ - Always validate connections before processing
1110
+ - Provide detailed error messages with recommendations
1111
+ - Use emoji logging for visual scanning
1112
+ - Fail fast on configuration errors
1113
+ - Dispose connections in finally blocks
1114
+
1115
+ ---
1116
+
1117
+ ## Production Best Practices
1118
+
1119
+ ### 1. Emoji Logging for Operations Visibility
1120
+
1121
+ Use emoji prefixes in logs for quick visual scanning:
1122
+
1123
+ ```typescript
1124
+ log.info('🔍 [Discovery] Scanning SFTP directory');
1125
+ log.info('✅ [Success] File processed successfully');
1126
+ log.error('❌ [Error] Connection failed', { error });
1127
+ log.warn('⚠️ [Warning] Partial batch failure');
1128
+ log.info('🧹 [Cleanup] Archiving processed files');
1129
+ log.info('🧠 [MemoryInterpreter] State synchronized');
1130
+ log.info('💾 [Persistence] State saved to KV');
1131
+ log.info('♻️ [Refresh] Stale connection renewed');
1132
+ log.info('🚀 [Start] Processing batch');
1133
+ log.info('📋 [Info] Configuration loaded');
1134
+ log.info('🔐 [Auth] Token refreshed');
1135
+ ```
1136
+
1137
+ **Benefits:**
1138
+ - Quick visual scanning in production logs
1139
+ - Easy pattern recognition for operations
1140
+ - Faster debugging and troubleshooting
1141
+ - Better collaboration across teams
1142
+
1143
+ ### 2. Connection Validation Pattern
1144
+
1145
+ Always validate connections before processing:
1146
+
1147
+ ```typescript
1148
+ // ✅ CORRECT - Validate early
1149
+ const sftp = new SftpDataSource(config, log);
1150
+ await sftp.validateConnection();
1151
+ log.info('✅ Connection validated');
1152
+
1153
+ // Process files...
1154
+
1155
+ // ❌ WRONG - Discover errors during processing
1156
+ const sftp = new SftpDataSource(config, log);
1157
+ const files = await sftp.listFiles(); // Might fail here!
1158
+ ```
1159
+
1160
+ ### 3. Error Handling with Recommendations
1161
+
1162
+ Provide actionable recommendations in error logs:
1163
+
1164
+ ```typescript
1165
+ catch (error) {
1166
+ log.error('❌ [Processing] File processing failed', {
1167
+ fileName: file.name,
1168
+ error: error instanceof Error ? error.message : String(error),
1169
+ recommendation: error.message?.includes('authentication')
1170
+ ? 'Check connection credentials in Connections section'
1171
+ : error.message?.includes('timeout')
1172
+ ? 'Check network connectivity and increase timeout settings'
1173
+ : error.message?.includes('parse')
1174
+ ? 'Verify file format matches expected structure'
1175
+ : 'Review error details and check file processing logic'
1176
+ });
1177
+ }
1178
+ ```
1179
+
1180
+ ### 4. Resource Cleanup Pattern
1181
+
1182
+ Always dispose data sources in finally blocks:
1183
+
1184
+ ```typescript
1185
+ const sftp = new SftpDataSource(config, log);
1186
+ try {
1187
+ await sftp.validateConnection();
1188
+ // Process files...
1189
+ } finally {
1190
+ await sftp.dispose();
1191
+ log.info('🧹 [Cleanup] SFTP connection disposed');
1192
+ }
1193
+ ```
1194
+
1195
+ ### 5. MemoryInterpreter State Management
1196
+
1197
+ Use MemoryInterpreter for connection pools and session state:
1198
+
1199
+ ```typescript
1200
+ // Persist complex objects to KV
1201
+ await kvAdapter.set(['memory-interpreter', 'state'], {
1202
+ connections: Array.from(connectionMap.entries()),
1203
+ lastActivity: Array.from(activityMap.entries()),
1204
+ pool: connectionPool
1205
+ });
1206
+
1207
+ // Restore from KV
1208
+ const state = await kvAdapter.get(['memory-interpreter', 'state']);
1209
+ const connectionMap = new Map(state.value.connections);
1210
+ ```
1211
+
1212
+ ### 6. JobTracker Integration
1213
+
1214
+ Use JobTracker for production monitoring:
1215
+
1216
+ ```typescript
1217
+ const tracker = new JobTracker(openKv(':project:'), log);
1218
+ const jobId = `job-${Date.now()}`;
1219
+
1220
+ await tracker.createJob(jobId, {
1221
+ triggeredBy: 'schedule',
1222
+ stage: 'initialization',
1223
+ startTime: Date.now()
1224
+ });
1225
+
1226
+ try {
1227
+ // Process...
1228
+ await tracker.markCompleted(jobId, { filesProcessed: 10 });
1229
+ } catch (error) {
1230
+ await tracker.markFailed(jobId, error.message);
1231
+ }
1232
+ ```
1233
+
1234
+ ---
1235
+
1236
+ ## Testing State Management
1237
+
1238
+ ### Local Testing Pattern
1239
+
1240
+ ```typescript
1241
+ /**
1242
+ * Test state management locally
1243
+ */
1244
+ export const testStateManagement = webhook('test-state', {
1245
+ response: { mode: 'sync' }
1246
+ })
1247
+ .then(fn('test-operations', async (ctx) => {
1248
+ const { openKv, log } = ctx;
1249
+
1250
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
1251
+ const stateService = new StateService(log);
1252
+
1253
+ // Test 1: Lock acquire and release
1254
+ log.info('🧪 [Test] Test 1: Lock management');
1255
+ const lockAcquired = await stateService.acquireLock('test-lock', kvAdapter, 5);
1256
+ log.info(`🔐 [Test] Lock acquired: ${lockAcquired}`);
1257
+
1258
+ // Try to acquire again (should fail)
1259
+ const lockAcquired2 = await stateService.acquireLock('test-lock', kvAdapter, 5);
1260
+ log.info(`⚠️ [Test] Second lock acquired: ${lockAcquired2} (should be false)`);
1261
+
1262
+ // Release lock
1263
+ await stateService.releaseLock('test-lock', kvAdapter);
1264
+ log.info('🔓 [Test] Lock released');
1265
+
1266
+ // Test 2: File tracking
1267
+ log.info('🧪 [Test] Test 2: File tracking');
1268
+ const fileTracker = new VersoriFileTracker(openKv(':project:'), 'test');
1269
+ await fileTracker.markFileProcessed('test-file.csv', { recordCount: 100 });
1270
+ const wasProcessed = await fileTracker.wasFileProcessed('test-file.csv');
1271
+ log.info(`✅ [Test] File processed: ${wasProcessed} (should be true)`);
1272
+
1273
+ const lastFile = await fileTracker.getLastProcessedFile();
1274
+ await fileTracker.setLastProcessedFile('test-file.csv');
1275
+ const newLastFile = await fileTracker.getLastProcessedFile();
1276
+ log.info(`📋 [Test] Last file: ${newLastFile}`);
1277
+
1278
+ // Test 3: Sync state
1279
+ log.info('🧪 [Test] Test 3: Sync state');
1280
+ await stateService.updateSyncState(kvAdapter, [{
1281
+ fileName: 'test.csv',
1282
+ lastModified: new Date().toISOString(),
1283
+ recordCount: 100
1284
+ }], 'test-workflow');
1285
+
1286
+ const syncState = await stateService.getSyncState(kvAdapter, 'test-workflow');
1287
+ log.info('💾 [Test] Sync state', syncState);
1288
+
1289
+ // Test 4: Daily job
1290
+ log.info('🧪 [Test] Test 4: Daily job');
1291
+ await stateService.setDailyJob(kvAdapter, 'test-workflow', 'job-123', 24);
1292
+ const dailyJob = await stateService.getDailyJob(kvAdapter, 'test-workflow');
1293
+ log.info('📋 [Test] Daily job', dailyJob);
1294
+
1295
+ return {
1296
+ success: true,
1297
+ message: '✅ All state management tests passed'
1298
+ };
1299
+ }));
1300
+ ```
1301
+
1302
+ ---
1303
+
1304
+ ## Common Issues & Solutions
1305
+
1306
+ ### Issue 1: Lock Not Released After Error
1307
+
1308
+ **Problem**: Workflow crashes and lock is never released, blocking future runs.
1309
+
1310
+ **Solution**: Always use try/finally or .catch() to ensure lock release:
1311
+
1312
+ ```typescript
1313
+ export const safeWorkflow = schedule('safe-job', '0 * * * *')
1314
+ .then(fn('work', async (ctx) => {
1315
+ const { openKv, log } = ctx;
1316
+
1317
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
1318
+ const stateService = new StateService(log);
1319
+ const lockName = 'safe-lock';
1320
+
1321
+ try {
1322
+ await stateService.acquireLock(lockName, kvAdapter, 15);
1323
+ // Your work here
1324
+ await doWork();
1325
+ } finally {
1326
+ // Always release, even on error
1327
+ await stateService.releaseLock(lockName, kvAdapter);
1328
+ }
1329
+ }));
1330
+ ```
1331
+
1332
+ Or use the catch pattern:
1333
+
1334
+ ```typescript
1335
+ export const workflowWithCatch = schedule('job', '0 * * * *')
1336
+ .then(fn('acquire', async (ctx) => {
1337
+ const { openKv, log } = ctx;
1338
+
1339
+ const kvAdapter = new VersoriKVAdapter(openKv(':project:'));
1340
+ const stateService = new StateService(log);
1341
+ await stateService.acquireLock('my-lock', kvAdapter, 15);
1342
+ return { kvAdapter, stateService };
1343
+ }))
1344
+ .then(fn('work', async ({ data }) => {
1345
+ // Work here
1346
+ return data;
1347
+ }))
1348
+ .catch(fn('cleanup', async ({ data }) => {
1349
+ // Release lock on any error
1350
+ if (data?.stateService) {
1351
+ await data.stateService.releaseLock('my-lock', data.kvAdapter);
1352
+ }
1353
+ throw new Error('Workflow failed');
1354
+ }));
1355
+ ```
1356
+
1357
+ ---
1358
+
1359
+ ### Issue 2: Stale Lock Detection
1360
+
1361
+ **Problem**: Lock holder crashed and never released lock.
1362
+
1363
+ **Solution**: Use lock timeout - StateService automatically overrides stale locks:
1364
+
1365
+ ```typescript
1366
+ // Lock with 15 minute timeout
1367
+ const acquired = await stateService.acquireLock('my-lock', kvAdapter, 15);
1368
+
1369
+ // If lock is older than 15 minutes, it will be overridden automatically
1370
+ // This prevents permanent deadlocks from crashed processes
1371
+ ```
1372
+
1373
+ **How it works**:
1374
+
1375
+ - Each lock stores `expiresAt` timestamp
1376
+ - `acquireLock()` checks if existing lock is expired
1377
+ - Expired locks are automatically overridden
1378
+ - Recent locks return false (lock not acquired)
1379
+
1380
+ ---
1381
+
1382
+ ### Issue 3: Cannot List Processed Files
1383
+
1384
+ **Problem**: Versori KV doesn't support list operations, can't enumerate processed files.
1385
+
1386
+ **Solution**: Use `VersoriIndexedFileTracker` which maintains an index:
1387
+
1388
+ ```typescript
1389
+ // Instead of VersoriFileTracker
1390
+ const fileTracker = new VersoriFileTracker(openKv(':project:'));
1391
+
1392
+ // Use VersoriIndexedFileTracker
1393
+ const indexedTracker = new VersoriIndexedFileTracker(openKv(':project:'));
1394
+
1395
+ // Now you can list files
1396
+ const allFiles = await indexedTracker.listProcessedFiles();
1397
+ const stats = await indexedTracker.getStats();
1398
+ ```
1399
+
1400
+ **How it works**:
1401
+
1402
+ - Maintains separate index key with list of all file names
1403
+ - Index updated atomically when files are marked/removed
1404
+ - Enables listing without KV list support
1405
+
1406
+ ---
1407
+
1408
+ ### Issue 4: Checkpoint Corruption
1409
+
1410
+ **Problem**: Checkpoint data gets corrupted or becomes invalid.
1411
+
1412
+ **Solution**: Add validation and version to checkpoint data:
1413
+
1414
+ ```typescript
1415
+ interface VersionedCheckpoint {
1416
+ version: number; // Schema version
1417
+ data: CheckpointData;
1418
+ checksum?: string; // Optional integrity check
1419
+ savedAt: string;
1420
+ }
1421
+
1422
+ async function saveCheckpoint(
1423
+ kvAdapter: VersoriKVAdapter,
1424
+ key: string[],
1425
+ data: CheckpointData
1426
+ ): Promise<void> {
1427
+ const checkpoint: VersionedCheckpoint = {
1428
+ version: 1,
1429
+ data,
1430
+ savedAt: new Date().toISOString()
1431
+ };
1432
+
1433
+ await kvAdapter.set(key, checkpoint);
1434
+ }
1435
+
1436
+ async function loadCheckpoint(
1437
+ kvAdapter: VersoriKVAdapter,
1438
+ key: string[]
1439
+ ): Promise<CheckpointData | null> {
1440
+ try {
1441
+ const result = await kvAdapter.get(key);
1442
+ if (!result?.value) return null;
1443
+
1444
+ const checkpoint = result.value as VersionedCheckpoint;
1445
+
1446
+ // Validate version
1447
+ if (checkpoint.version !== 1) {
1448
+ console.warn('Checkpoint version mismatch, ignoring');
1449
+ return null;
1450
+ }
1451
+
1452
+ // Check if checkpoint is too old (e.g., > 7 days)
1453
+ const savedAt = new Date(checkpoint.savedAt);
1454
+ const age = Date.now() - savedAt.getTime();
1455
+ if (age > 7 * 24 * 60 * 60 * 1000) {
1456
+ console.warn('Checkpoint too old, starting fresh');
1457
+ return null;
1458
+ }
1459
+
1460
+ return checkpoint.data;
1461
+ } catch (error) {
1462
+ console.error('Failed to load checkpoint', error);
1463
+ return null; // Start fresh on corruption
1464
+ }
1465
+ }
1466
+ ```
1467
+
1468
+ ---
1469
+
1470
+ ## Best Practices Summary
1471
+
1472
+ ### Core State Management Practices
1473
+
1474
+ 1. **Always Release Locks**: Use try/finally or .catch() to ensure locks are released
1475
+ 2. **Use Lock Timeouts**: Set appropriate timeouts (15-30 minutes typical)
1476
+ 3. **Checkpoint Periodically**: Not too often (overhead) or too rarely (lost progress)
1477
+ 4. **Validate Restored State**: Check checkpoint age and integrity before using
1478
+ 5. **Clear Completed Checkpoints**: Remove checkpoint data after successful completion
1479
+ 6. **Use Indexed Tracker for Listing**: VersoriKV doesn't support list operations natively
1480
+ 7. **Track Error State**: Implement exponential backoff for retries
1481
+ 8. **Monitor Lock Ages**: Alert on locks held longer than expected
1482
+ 9. **Namespace Your Keys**: Use prefixes to organize KV data (`workflow:resource:identifier`)
1483
+ 10. **Document Key Schemas**: Keep track of what data is stored under which keys
1484
+
1485
+ ### Production Patterns (NEW)
1486
+
1487
+ 11. **Use Emoji Logging**: Add emoji prefixes for quick visual scanning in logs (see Pattern 8)
1488
+ 12. **Validate Connections Early**: Always call `validateConnection()` before processing (see Pattern 8)
1489
+ 13. **Provide Error Recommendations**: Include actionable recommendations in error logs
1490
+ 14. **Dispose Resources**: Always dispose data sources in finally blocks
1491
+ 15. **Use MemoryInterpreter**: For connection pools and session state (see Pattern 7)
1492
+ 16. **Integrate JobTracker**: Track job lifecycle for production monitoring
1493
+
1494
+ ---
1495
+
1496
+ ## Related Guides
1497
+
1498
+ - **01-inventory-ingestion.md** - Complete ingestion workflow using state management
1499
+ - **02-inventory-extraction.md** - Extraction workflow with checkpoints
1500
+ - **04-batch-archival.md** - Archiving batch data to S3 (uses daily jobs)
1501
+ - **05-error-handling.md** - Advanced error handling patterns
1502
+ - **StateService full reference**: `../../02-CORE-GUIDES/ingestion/modules/07-state-management.md`
1503
+
1504
+ ---
1505
+
1506
+ ## Summary
1507
+
1508
+ This guide covered comprehensive state management patterns for Versori connectors:
1509
+
1510
+ 1. **Simple File Tracking** - Prevent duplicate processing with VersoriFileTracker
1511
+ 2. **Distributed Locking** - Prevent concurrent executions with StateService
1512
+ 3. **Checkpoints** - Save progress and resume long-running workflows
1513
+ 4. **Daily Jobs** - Reuse jobs across batches throughout the day
1514
+ 5. **Error States** - Track failures and implement smart retry logic
1515
+ 6. **Indexed Tracking** - List and manage processed files
1516
+ 7. **MemoryInterpreter** (NEW) - In-memory state for connection pools
1517
+ 8. **Connection Validation** (NEW) - Validate connections before processing
1518
+
1519
+ **Key Takeaways**:
1520
+
1521
+ - VersoriFileTracker for simple duplicate prevention
1522
+ - StateService for distributed locking and sync state
1523
+ - VersoriIndexedFileTracker when you need to list files
1524
+ - Always release locks in finally blocks
1525
+ - Checkpoint periodically to enable resume
1526
+ - Validate restored state before using
1527
+ - Use exponential backoff for retries
1528
+ - **Use emoji logging for production visibility** (NEW)
1529
+ - **Validate connections early with `validateConnection()`** (NEW)
1530
+ - **Use MemoryInterpreter for connection pool state** (NEW)
1531
+ - **Provide actionable error recommendations** (NEW)
1532
+
1533
+ State management is critical for robust production workflows. These patterns ensure your connectors are reliable, resumable, and prevent duplicate processing.