@fluentcommerce/fc-connect-sdk 0.1.54 → 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 (475) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/clients/fluent-client.js +13 -6
  3. package/dist/cjs/utils/pagination-helpers.js +38 -2
  4. package/dist/cjs/versori/fluent-versori-client.js +11 -5
  5. package/dist/esm/clients/fluent-client.js +13 -6
  6. package/dist/esm/utils/pagination-helpers.js +38 -2
  7. package/dist/esm/versori/fluent-versori-client.js +11 -5
  8. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  9. package/dist/tsconfig.tsbuildinfo +1 -1
  10. package/dist/tsconfig.types.tsbuildinfo +1 -1
  11. package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
  12. package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
  13. package/docs/00-START-HERE/cli-documentation-index.md +202 -202
  14. package/docs/00-START-HERE/cli-quick-reference.md +252 -252
  15. package/docs/00-START-HERE/decision-tree.md +552 -552
  16. package/docs/00-START-HERE/getting-started.md +1070 -1070
  17. package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
  18. package/docs/00-START-HERE/readme.md +237 -237
  19. package/docs/00-START-HERE/retailerid-configuration.md +404 -404
  20. package/docs/00-START-HERE/sdk-philosophy.md +794 -794
  21. package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
  22. package/docs/01-TEMPLATES/faq.md +686 -686
  23. package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
  24. package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
  25. package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
  26. package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
  27. package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
  28. package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
  29. package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
  30. package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
  31. package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
  32. package/docs/01-TEMPLATES/readme.md +957 -957
  33. package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
  34. package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
  35. package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
  36. package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
  37. package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
  38. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
  39. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
  40. package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
  41. package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
  42. package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
  43. package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
  44. package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
  45. package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
  46. package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
  47. package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
  48. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
  49. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
  50. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
  51. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
  52. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
  53. package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
  54. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
  55. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
  56. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
  57. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
  58. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
  59. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
  60. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
  61. package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
  62. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
  63. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
  64. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
  65. package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
  66. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
  67. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
  68. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
  69. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
  70. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
  71. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
  72. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
  73. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
  74. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
  75. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
  76. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
  77. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
  78. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
  79. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
  80. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
  81. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
  82. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
  83. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
  84. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
  85. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
  86. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
  87. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
  88. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
  89. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
  90. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
  91. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
  92. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
  93. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
  94. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
  95. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
  96. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
  97. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
  98. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
  99. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
  100. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
  101. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
  102. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
  103. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
  104. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
  105. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
  106. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
  107. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
  108. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
  109. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
  110. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
  111. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
  112. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
  113. package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
  114. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
  115. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
  116. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
  117. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
  118. package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
  119. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
  120. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
  121. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
  122. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
  123. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
  124. package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
  125. package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
  126. package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
  127. package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
  128. package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
  129. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
  130. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
  131. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
  132. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
  133. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
  134. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
  135. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
  136. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
  137. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
  138. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
  139. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
  140. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
  141. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
  142. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
  143. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
  144. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
  145. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
  146. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
  147. package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
  148. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
  149. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
  150. package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
  151. package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
  152. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
  153. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
  154. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
  155. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
  156. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
  157. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
  158. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
  159. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
  160. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
  161. package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
  162. package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
  163. package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
  164. package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
  165. package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
  166. package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
  167. package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
  168. package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
  169. package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
  170. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
  171. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
  172. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
  173. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
  174. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
  175. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
  176. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
  177. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
  178. package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
  179. package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
  180. package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
  181. package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
  182. package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
  183. package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
  184. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
  185. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
  186. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
  187. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
  188. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
  189. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
  190. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
  191. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
  192. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
  193. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
  194. package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
  195. package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
  196. package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
  197. package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
  198. package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
  199. package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
  200. package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
  201. package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
  202. package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
  203. package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
  204. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
  205. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
  206. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
  207. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
  208. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
  209. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
  210. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
  211. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
  212. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
  213. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
  214. package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
  215. package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
  216. package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
  217. package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
  218. package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
  219. package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
  220. package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
  221. package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
  222. package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
  223. package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
  224. package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
  225. package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
  226. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
  227. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
  228. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
  229. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
  230. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
  231. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
  232. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
  233. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
  234. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
  235. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
  236. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
  237. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
  238. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
  239. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
  240. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
  241. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
  242. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
  243. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
  244. package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
  245. package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
  246. package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
  247. package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
  248. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
  249. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
  250. package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
  251. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
  252. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
  253. package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
  254. package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
  255. package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
  256. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
  257. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
  258. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
  259. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
  260. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
  261. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
  262. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
  263. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
  264. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
  265. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
  266. package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
  267. package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
  268. package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
  269. package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
  270. package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
  271. package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
  272. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
  273. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
  274. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
  275. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
  276. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
  277. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
  278. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
  279. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
  280. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
  281. package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
  282. package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
  283. package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
  284. package/docs/02-CORE-GUIDES/readme.md +194 -194
  285. package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
  286. package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
  287. package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
  288. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
  289. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
  290. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
  291. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
  292. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
  293. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
  294. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
  295. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
  296. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
  297. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
  298. package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
  299. package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
  300. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
  301. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
  302. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
  303. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
  304. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
  305. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
  306. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
  307. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
  308. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
  309. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
  310. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
  311. package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
  312. package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
  313. package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
  314. package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
  315. package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
  316. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
  317. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
  318. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
  319. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
  320. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
  321. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
  322. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
  323. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
  324. package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
  325. package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
  326. package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
  327. package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
  328. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
  329. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
  330. package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
  331. package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
  332. package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
  333. package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
  334. package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
  335. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
  336. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
  337. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
  338. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
  339. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
  340. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
  341. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
  342. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
  343. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
  344. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
  345. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
  346. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
  347. package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
  348. package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
  349. package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
  350. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
  351. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
  352. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
  353. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
  354. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
  355. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
  356. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
  357. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
  358. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
  359. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
  360. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
  361. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
  362. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
  363. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
  364. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
  365. package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
  366. package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
  367. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
  368. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
  369. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
  370. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
  371. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
  372. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
  373. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
  374. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
  375. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
  376. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
  377. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
  378. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
  379. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
  380. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
  381. package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
  382. package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
  383. package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
  384. package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
  385. package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
  386. package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
  387. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
  388. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
  389. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
  390. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
  391. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
  392. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
  393. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
  394. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
  395. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
  396. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
  397. package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
  398. package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
  399. package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
  400. package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
  401. package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
  402. package/docs/03-PATTERN-GUIDES/readme.md +159 -159
  403. package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
  404. package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
  405. package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
  406. package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
  407. package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
  408. package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
  409. package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
  410. package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
  411. package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
  412. package/docs/04-REFERENCE/architecture/readme.md +279 -279
  413. package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
  414. package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
  415. package/docs/04-REFERENCE/platforms/readme.md +135 -135
  416. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
  417. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
  418. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
  419. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
  420. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
  421. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
  422. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
  423. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
  424. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
  425. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
  426. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
  427. package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
  428. package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
  429. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
  430. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
  431. package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
  432. package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
  433. package/docs/04-REFERENCE/readme.md +148 -148
  434. package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
  435. package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
  436. package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
  437. package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
  438. package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
  439. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
  440. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
  441. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
  442. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
  443. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
  444. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
  445. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
  446. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
  447. package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
  448. package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
  449. package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
  450. package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
  451. package/docs/04-REFERENCE/schema/readme.md +141 -141
  452. package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
  453. package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
  454. package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
  455. package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
  456. package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
  457. package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
  458. package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
  459. package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
  460. package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
  461. package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
  462. package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
  463. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
  464. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
  465. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
  466. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
  467. package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
  468. package/docs/04-REFERENCE/testing/readme.md +86 -86
  469. package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
  470. package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
  471. package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
  472. package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
  473. package/docs/template-loading-matrix.md +242 -242
  474. package/package.json +5 -3
  475. package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
@@ -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.