@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.56

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