@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/clients/fluent-client.js +13 -6
  3. package/dist/cjs/utils/pagination-helpers.js +38 -2
  4. package/dist/cjs/versori/fluent-versori-client.js +11 -5
  5. package/dist/esm/clients/fluent-client.js +13 -6
  6. package/dist/esm/utils/pagination-helpers.js +38 -2
  7. package/dist/esm/versori/fluent-versori-client.js +11 -5
  8. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  9. package/dist/tsconfig.tsbuildinfo +1 -1
  10. package/dist/tsconfig.types.tsbuildinfo +1 -1
  11. package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
  12. package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
  13. package/docs/00-START-HERE/cli-documentation-index.md +202 -202
  14. package/docs/00-START-HERE/cli-quick-reference.md +252 -252
  15. package/docs/00-START-HERE/decision-tree.md +552 -552
  16. package/docs/00-START-HERE/getting-started.md +1070 -1070
  17. package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
  18. package/docs/00-START-HERE/readme.md +237 -237
  19. package/docs/00-START-HERE/retailerid-configuration.md +404 -404
  20. package/docs/00-START-HERE/sdk-philosophy.md +794 -794
  21. package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
  22. package/docs/01-TEMPLATES/faq.md +686 -686
  23. package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
  24. package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
  25. package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
  26. package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
  27. package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
  28. package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
  29. package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
  30. package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
  31. package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
  32. package/docs/01-TEMPLATES/readme.md +957 -957
  33. package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
  34. package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
  35. package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
  36. package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
  37. package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
  38. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
  39. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
  40. package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
  41. package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
  42. package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
  43. package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
  44. package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
  45. package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
  46. package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
  47. package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
  48. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
  49. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
  50. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
  51. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
  52. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
  53. package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
  54. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
  55. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
  56. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
  57. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
  58. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
  59. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
  60. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
  61. package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
  62. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
  63. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
  64. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
  65. package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
  66. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
  67. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
  68. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
  69. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
  70. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
  71. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
  72. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
  73. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
  74. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
  75. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
  76. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
  77. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
  78. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
  79. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
  80. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
  81. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
  82. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
  83. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
  84. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
  85. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
  86. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
  87. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
  88. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
  89. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
  90. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
  91. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
  92. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
  93. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
  94. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
  95. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
  96. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
  97. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
  98. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
  99. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
  100. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
  101. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
  102. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
  103. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
  104. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
  105. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
  106. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
  107. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
  108. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
  109. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
  110. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
  111. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
  112. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
  113. package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
  114. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
  115. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
  116. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
  117. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
  118. package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
  119. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
  120. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
  121. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
  122. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
  123. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
  124. package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
  125. package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
  126. package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
  127. package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
  128. package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
  129. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
  130. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
  131. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
  132. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
  133. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
  134. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
  135. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
  136. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
  137. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
  138. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
  139. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
  140. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
  141. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
  142. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
  143. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
  144. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
  145. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
  146. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
  147. package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
  148. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
  149. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
  150. package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
  151. package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
  152. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
  153. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
  154. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
  155. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
  156. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
  157. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
  158. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
  159. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
  160. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
  161. package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
  162. package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
  163. package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
  164. package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
  165. package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
  166. package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
  167. package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
  168. package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
  169. package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
  170. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
  171. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
  172. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
  173. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
  174. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
  175. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
  176. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
  177. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
  178. package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
  179. package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
  180. package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
  181. package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
  182. package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
  183. package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
  184. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
  185. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
  186. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
  187. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
  188. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
  189. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
  190. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
  191. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
  192. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
  193. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
  194. package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
  195. package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
  196. package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
  197. package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
  198. package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
  199. package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
  200. package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
  201. package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
  202. package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
  203. package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
  204. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
  205. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
  206. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
  207. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
  208. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
  209. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
  210. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
  211. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
  212. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
  213. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
  214. package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
  215. package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
  216. package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
  217. package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
  218. package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
  219. package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
  220. package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
  221. package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
  222. package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
  223. package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
  224. package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
  225. package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
  226. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
  227. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
  228. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
  229. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
  230. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
  231. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
  232. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
  233. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
  234. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
  235. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
  236. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
  237. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
  238. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
  239. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
  240. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
  241. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
  242. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
  243. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
  244. package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
  245. package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
  246. package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
  247. package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
  248. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
  249. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
  250. package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
  251. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
  252. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
  253. package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
  254. package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
  255. package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
  256. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
  257. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
  258. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
  259. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
  260. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
  261. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
  262. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
  263. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
  264. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
  265. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
  266. package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
  267. package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
  268. package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
  269. package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
  270. package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
  271. package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
  272. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
  273. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
  274. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
  275. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
  276. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
  277. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
  278. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
  279. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
  280. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
  281. package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
  282. package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
  283. package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
  284. package/docs/02-CORE-GUIDES/readme.md +194 -194
  285. package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
  286. package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
  287. package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
  288. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
  289. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
  290. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
  291. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
  292. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
  293. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
  294. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
  295. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
  296. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
  297. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
  298. package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
  299. package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
  300. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
  301. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
  302. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
  303. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
  304. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
  305. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
  306. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
  307. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
  308. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
  309. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
  310. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
  311. package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
  312. package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
  313. package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
  314. package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
  315. package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
  316. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
  317. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
  318. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
  319. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
  320. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
  321. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
  322. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
  323. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
  324. package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
  325. package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
  326. package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
  327. package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
  328. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
  329. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
  330. package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
  331. package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
  332. package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
  333. package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
  334. package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
  335. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
  336. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
  337. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
  338. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
  339. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
  340. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
  341. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
  342. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
  343. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
  344. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
  345. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
  346. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
  347. package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
  348. package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
  349. package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
  350. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
  351. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
  352. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
  353. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
  354. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
  355. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
  356. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
  357. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
  358. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
  359. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
  360. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
  361. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
  362. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
  363. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
  364. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
  365. package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
  366. package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
  367. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
  368. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
  369. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
  370. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
  371. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
  372. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
  373. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
  374. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
  375. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
  376. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
  377. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
  378. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
  379. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
  380. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
  381. package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
  382. package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
  383. package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
  384. package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
  385. package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
  386. package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
  387. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
  388. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
  389. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
  390. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
  391. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
  392. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
  393. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
  394. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
  395. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
  396. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
  397. package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
  398. package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
  399. package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
  400. package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
  401. package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
  402. package/docs/03-PATTERN-GUIDES/readme.md +159 -159
  403. package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
  404. package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
  405. package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
  406. package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
  407. package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
  408. package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
  409. package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
  410. package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
  411. package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
  412. package/docs/04-REFERENCE/architecture/readme.md +279 -279
  413. package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
  414. package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
  415. package/docs/04-REFERENCE/platforms/readme.md +135 -135
  416. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
  417. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
  418. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
  419. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
  420. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
  421. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
  422. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
  423. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
  424. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
  425. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
  426. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
  427. package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
  428. package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
  429. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
  430. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
  431. package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
  432. package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
  433. package/docs/04-REFERENCE/readme.md +148 -148
  434. package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
  435. package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
  436. package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
  437. package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
  438. package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
  439. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
  440. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
  441. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
  442. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
  443. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
  444. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
  445. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
  446. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
  447. package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
  448. package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
  449. package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
  450. package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
  451. package/docs/04-REFERENCE/schema/readme.md +141 -141
  452. package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
  453. package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
  454. package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
  455. package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
  456. package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
  457. package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
  458. package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
  459. package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
  460. package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
  461. package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
  462. package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
  463. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
  464. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
  465. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
  466. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
  467. package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
  468. package/docs/04-REFERENCE/testing/readme.md +86 -86
  469. package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
  470. package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
  471. package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
  472. package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
  473. package/docs/template-loading-matrix.md +242 -242
  474. package/package.json +5 -3
  475. package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
@@ -1,1906 +1,1906 @@
1
- ---
2
- template_id: tpl-webhook-asn-purchase-order
3
- canonical_filename: template-webhook-asn-purchase-order.md
4
- version: 2.0.0
5
- sdk_version: ^0.1.39
6
- runtime: versori
7
- direction: ingestion
8
- source: webhook-xml-json-asn
9
- destination: fluent-graphql
10
- entity: inventory-receipt
11
- format: xml-json
12
- logging: versori
13
- status: stable
14
- features:
15
- - webhook-signature-validation
16
- - batched-events
17
- - attribute-transformation
18
- - memory-management
19
- - enhanced-logging
20
- ---
21
-
22
- # Template: Webhook - ASN & Purchase Order Processing
23
-
24
- **Template Version:** 2.0.0
25
- **SDK Version:** @fluentcommerce/fc-connect-sdk@^0.1.39
26
- **Last Updated:** 2025-01-24
27
- **Deployment Target:** Versori Platform
28
-
29
- **🆕 Version 2.0.0 Enhancements:**
30
- - ✅ **Webhook Signature Validation** - Secure webhook verification with HMAC-SHA256
31
- - ✅ **Batched Events** - Process events in optimized batches to reduce API calls
32
- - ✅ **Attribute Transformation** - Handle nested arrays and complex data structures
33
- - ✅ **Memory Management** - Clear large arrays after processing batches
34
- - ✅ **Enhanced Logging** - Track batch processing and event submission with emoji indicators
35
-
36
- **FC Connect SDK Use Case Guide**
37
-
38
- > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
39
- > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
40
-
41
- **Context**: Process Advanced Ship Notices (ASN) from 3PL warehouse and create expected inventory receipts in Fluent Commerce
42
-
43
- **Complexity**: Medium-High
44
-
45
- **Runtime**: Versori Platform
46
-
47
- **Estimated Lines**: ~750 lines (modular structure)
48
-
49
- ---
50
-
51
- ## STEP 1: Understand This Template
52
-
53
- **What This Template Does:**
54
-
55
- - HTTP webhook endpoint receiving ASN data from 3PL/WMS
56
- - XML/JSON parsing for ASN payload (EDI 856 compatible structure)
57
- - GraphQL mutation to create expected inventory receipts
58
- - Container/pallet tracking support
59
- - Cross-dock scenario handling (direct fulfillment without storage)
60
- - Expected date calculation (carrier transit time)
61
- - Custom resolvers for address normalization and SKU validation
62
- - Audit trail and notification system
63
- - Error handling for duplicate ASN prevention
64
- - **Sync + Fire-and-Forget Pattern**: Fast webhook response, background processing
65
-
66
- **Key SDK Components:**
67
-
68
- - `createClient()` - Universal client factory (auto-detects Versori context)
69
- - `GraphQLMutationMapper` - ASN → GraphQL mutation mapping
70
- - `XMLParserService` / `JSONParserService` - ASN parsing (EDI 856 format)
71
- - Native Versori `log` - Use `log` from context
72
-
73
- **Entity Type:**
74
-
75
- - **InventoryReceipt** - Fluent entity for expected inventory
76
- - **GraphQL Mutation** - Uses `createInventoryReceipt` mutation
77
-
78
- **Critical Patterns:**
79
-
80
- - **Sync + Fire-and-Forget**: Webhook validates quickly, returns immediately, processes ASN in background
81
- - **External JSON Config**: Mapping configuration in separate JSON file (`config/asn-mapping.json`)
82
- - **Modular Architecture**: Separate services, workflows, config, types folders
83
- - **Background Processing**: Long-running operations (GraphQL mutations, validation) happen asynchronously
84
- - **Error Handling**: Comprehensive error handling with duplicate detection
85
-
86
- **When to Use This Template:**
87
-
88
- - ✅ ASN processing from 3PL/WMS systems
89
- - ✅ EDI 856 format (XML or JSON)
90
- - ✅ Need fast webhook response (don't wait for receipt creation)
91
- - ✅ Container/pallet tracking
92
- - ✅ Cross-dock scenarios
93
-
94
- **When NOT to Use:**
95
-
96
- - ❌ Bulk ASN processing (use Batch API or scheduled workflows)
97
- - ❌ Real-time inventory updates (use Event API)
98
- - ❌ Need synchronous receipt creation (wait for result before responding)
99
-
100
- ---
101
-
102
- ## STEP 2: Implementation Prompt for Claude Code
103
-
104
- **Copy this prompt and send to Claude Code to generate the complete implementation:**
105
-
106
- ```
107
- Create a Versori webhook workflow for ASN (Advanced Ship Notice) processing to Fluent Commerce.
108
-
109
- REQUIREMENTS:
110
- 1. Runtime: Versori Platform (HTTP webhook)
111
- 2. Source: ASN data via HTTP POST webhook (XML or JSON, EDI 856 format)
112
- 3. Destination: Fluent Commerce GraphQL API (createInventoryReceipt mutation)
113
- 4. Format: XML or JSON (EDI 856 compatible)
114
- 5. Entity: InventoryReceipt (GraphQL mutation)
115
-
116
- KEY FEATURES:
117
- - Sync + fire-and-forget pattern (fast webhook response, background processing)
118
- - External JSON mapping configuration (config/asn-mapping.json)
119
- - Modular architecture (workflows/, services/, config/, types/)
120
- - XML/JSON parsing with EDI 856 structure support
121
- - GraphQLMutationMapper for ASN → GraphQL transformation
122
- - Custom resolvers for address normalization and SKU validation
123
- - Duplicate ASN detection and prevention
124
- - Audit trail (save input/output files)
125
-
126
- CRITICAL REQUIREMENTS:
127
- 1. Webhook Mode: response: { mode: 'sync' } (fast response)
128
- 2. Background Processing: Fire-and-forget pattern (no await on long operations)
129
- 3. Mapping Config: External JSON file (config/asn-mapping.json)
130
- 4. Modular Structure: Separate services/, config/, types/ folders
131
- 5. Native Logging: Use log from context (no LoggingService)
132
- 6. Error Handling: Duplicate detection, comprehensive error responses
133
-
134
- SDK METHODS TO USE:
135
- - createClient({ ...ctx, log }) - Pass full Versori context
136
- - new GraphQLMutationMapper(mappingConfig, log, { fluentClient: client }) - Initialize mapper
137
- - mapper.mapWithNodes(asnData, customResolvers, context) - Map ASN with custom resolvers (REQUIRED for custom resolvers)
138
- - mapWithNodes() now returns query automatically (no need for buildMutation())
139
- - client.graphql({ query, variables }) - Execute GraphQL mutation
140
-
141
- FORBIDDEN PATTERNS:
142
- - ❌ Inline mapping config (use external JSON)
143
- - ❌ await on background processing (use fire-and-forget)
144
- - ❌ LoggingService (use native log from context)
145
- - ❌ All code in one file (use modular structure)
146
- - ❌ async mode webhook (use sync + fire-and-forget)
147
- ```
148
-
149
- ---
150
-
151
- ## STEP 3: Detailed Flow Documentation
152
-
153
- ### Complete Processing Flow
154
-
155
- ```
156
- ┌─────────────────────────────────────────────────────────────┐
157
- │ 1. WEBHOOK RECEIVED │
158
- │ POST https://{workspace}.versori.run/process-asn │
159
- │ Content-Type: application/xml or application/json │
160
- │ Body: <ShipNotice>...</ShipNotice> or { ... } │
161
- └────────────────────┬────────────────────────────────────────┘
162
-
163
-
164
- ┌─────────────────────────────────────────────────────────────┐
165
- │ 2. QUICK VALIDATION (Synchronous, ~10-50ms) │
166
- │ - Check fluent_commerce connection exists │
167
- │ - Validate ASN payload present │
168
- │ - Return HTTP 200 OK immediately │
169
- └────────────────────┬────────────────────────────────────────┘
170
-
171
-
172
- ┌─────────────────────────────────────────────────────────────┐
173
- │ 3. BACKGROUND PROCESSING (Fire-and-Forget) │
174
- │ ┌─────────────────────────────────────────────────────┐ │
175
- │ │ 3a. Initialize Fluent Client │ │
176
- │ │ - createClient({ ...ctx, log }) │ │
177
- │ └─────────────────────────────────────────────────────┘ │
178
- │ ┌─────────────────────────────────────────────────────┐ │
179
- │ │ 3b. Parse ASN (XML or JSON) │ │
180
- │ │ - XMLParserService or JSONParserService │ │
181
- │ │ - Extract ASN identifier │ │
182
- │ └─────────────────────────────────────────────────────┘ │
183
- │ ┌─────────────────────────────────────────────────────┐ │
184
- │ │ 3c. Check for Duplicates │ │
185
- │ │ - Query Fluent for existing receipt │ │
186
- │ │ - Return early if duplicate │ │
187
- │ └─────────────────────────────────────────────────────┘ │
188
- │ ┌─────────────────────────────────────────────────────┐ │
189
- │ │ 3d. Map ASN to GraphQL Variables │ │
190
- │ │ - Load mapping config from JSON │ │
191
- │ │ - GraphQLMutationMapper.map() │ │
192
- │ │ - Apply custom resolvers │ │
193
- │ │ - Returns { success, data, query, context } │ │
194
- │ └─────────────────────────────────────────────────────┘ │
195
- │ ┌─────────────────────────────────────────────────────┐ │
196
- │ │ 3e. Execute GraphQL Mutation │ │
197
- │ │ - client.graphql({ query: result.query, │ │
198
- │ │ variables: result.variables })│ │
199
- │ └─────────────────────────────────────────────────────┘ │
200
- │ ┌─────────────────────────────────────────────────────┐ │
201
- │ │ 3g. Save Audit Trail (Optional) │ │
202
- │ │ - asn-input.json │ │
203
- │ │ - mapped-variables.json │ │
204
- │ │ - graphql-response.json │ │
205
- │ └─────────────────────────────────────────────────────┘ │
206
- └─────────────────────────────────────────────────────────────┘
207
- ```
208
-
209
- ### Response Timing
210
-
211
- | Stage | Timing | Blocking |
212
- |-------|--------|----------|
213
- | **Webhook Validation** | ~10-50ms | ✅ Yes (blocks response) |
214
- | **Background Processing** | ~1000-3000ms | ❌ No (fire-and-forget) |
215
- | **Total Response Time** | ~10-50ms | ✅ Fast response |
216
-
217
- **Key Benefit**: Webhook caller receives immediate acknowledgment (~50ms) while ASN processing happens in background (~2-3s).
218
-
219
- ---
220
-
221
- ## STEP 4: Production Modular Structure
222
-
223
- > **✅ This section shows the COMPLETE production-ready modular structure.**
224
- > All files are shown with proper imports/exports and folder organization.
225
-
226
- ### Complete Project Structure
227
-
228
- ```
229
- asn-purchase-order-processing/
230
- ├── package.json # Dependencies and Versori config
231
- ├── index.ts # Entry point - exports all workflows
232
- └── src/
233
- ├── workflows/
234
- │ └── webhook/
235
- │ └── asn-receipt.ts # Webhook: Receive ASN notifications
236
-
237
- ├── services/
238
- │ └── asn-processing.service.ts # Shared orchestration logic (reusable)
239
-
240
- ├── resolvers/
241
- │ └── asn-resolvers.ts # Custom resolvers for transformations
242
-
243
- ├── config/
244
- │ └── asn-mapping.json # Mapping configuration (external JSON)
245
-
246
- └── types/
247
- └── asn.types.ts # TypeScript interfaces
248
- ```
249
-
250
- **Why This Structure?**
251
-
252
- - ✅ **Clear separation**: Webhook handlers vs business logic
253
- - ✅ **Reusable services**: ASN processing logic can be reused
254
- - ✅ **External config**: Mapping changes don't require code changes
255
- - ✅ **Custom resolvers**: Separate file for complex transformations
256
- - ✅ **Type safety**: TypeScript interfaces for better IDE support
257
- - ✅ **Scalable**: Easy to add new ASN formats or processing steps
258
-
259
- ---
260
-
261
- ## SDK Methods Used
262
-
263
- ```typescript
264
- // Core SDK imports
265
- import {
266
- createClient,
267
- GraphQLMutationMapper,
268
- XMLParserService,
269
- JSONParserService,
270
- } from '@fluentcommerce/fc-connect-sdk';
271
-
272
- // Versori imports
273
- import { webhook } from '@versori/run';
274
- import { Buffer } from 'node:buffer'; // Required for Versori runtime
275
-
276
- // Key methods
277
- const logger = { info, warn, error, debug }; // Map Versori log to SDK Logger interface
278
- const client = await createClient({ ...ctx, log: logger }); // Auto-detects Versori context
279
- new GraphQLMutationMapper(config, logger, { fluentClient: client }); // Map ASN to GraphQL (uses logger adapter)
280
- new XMLParserService(); // Parse ASN XML (EDI 856)
281
- mapper.mapWithNodes(asnData, resolvers, context); // Apply mapping with custom logic
282
- // mapWithNodes() now returns query automatically - use result.query
283
- client.graphql({ query, variables }); // Execute mutation
284
- ```
285
-
286
- ---
287
-
288
- ## Versori Workflows Structure
289
-
290
- **Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
291
-
292
- **Trigger Types:**
293
- - **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
294
- - **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
295
- - **`http()`** → External API calls (chained from webhook/schedule)
296
- - **`fn()`** → Internal processing (chained from webhook/schedule)
297
-
298
- ### Recommended Project Structure
299
-
300
- ```
301
- asn-purchase-order-processing/
302
- ├── index.ts # Entry point - exports all workflows
303
- └── src/
304
- ├── workflows/
305
- │ └── webhook/
306
- │ └── asn-receipt.ts # Webhook: Receive ASN notifications
307
-
308
- ├── services/
309
- │ └── asn-processing.service.ts # Shared orchestration logic (reusable)
310
-
311
- └── config/
312
- └── asn-mapping.json # Mapping configuration
313
- ```
314
-
315
- **Benefits:**
316
- - ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
317
- - ✅ Descriptive file names (easy to browse and understand)
318
- - ✅ Scalable (add new workflows without cluttering)
319
- - ✅ Reusable code in `services/` (DRY principle)
320
- - ✅ Easy to modify individual workflows without affecting others
321
-
322
- ---
323
-
324
- ## Complete Working Code
325
-
326
- ### 1. Entry Point: `index.ts`
327
-
328
- ```typescript
329
- /**
330
- * Entry point - Export all workflows for Versori platform
331
- *
332
- * This file exports all workflows to be registered with Versori.
333
- * Each workflow is defined in its own file for better organization.
334
- *
335
- * MEMORY INTERPRETER PATTERN:
336
- * Versori's interpreter reads this file and registers all exported workflows.
337
- * Do NOT use dynamic imports or conditional exports.
338
- */
339
-
340
- // Webhook workflows
341
- export { processAsnWebhook } from './workflows/webhook/asn-receipt';
342
- export { manualAsnTest } from './workflows/webhook/asn-receipt';
343
- export { healthCheck } from './workflows/webhook/asn-receipt';
344
- ```
345
-
346
- ### 2. Main Webhook Workflow: `workflows/webhook/asn-receipt.ts`
347
-
348
- ```typescript
349
- /**
350
- * ASN (Advanced Ship Notice) Processing Webhook
351
- *
352
- * Receives ASN notifications from Acme 3PL/WMS and creates expected inventory
353
- * receipts in Fluent Commerce.
354
- *
355
- * ASN = Advanced Ship Notice (EDI 856) - notification that shipment is on the way
356
- *
357
- * Flow:
358
- * 1. Receive ASN webhook (XML or JSON payload from 3PL)
359
- * 2. Parse ASN structure (shipment header, containers, items)
360
- * 3. Map to Fluent expected receipt format
361
- * 4. Calculate expected arrival date (based on carrier and transit time)
362
- * 5. Create inventory receipt mutation (creates expected inventory)
363
- * 6. Send confirmation response to 3PL
364
- * 7. Optional: Send notification to warehouse ops team
365
- */
366
- import { webhook } from '@versori/run';
367
- import { Buffer } from 'node:buffer'; // Required for Versori runtime
368
- import {
369
- createClient,
370
- GraphQLMutationMapper,
371
- XMLParserService,
372
- JSONParserService,
373
- } from '@fluentcommerce/fc-connect-sdk';
374
- import { asnResolvers } from '../../resolvers/asn-resolvers';
375
- import asnMappingConfig from '../../mappings/asn-to-fluent-receipt.json' with { type: 'json' };
376
-
377
- /**
378
- * Main ASN webhook endpoint
379
- * Expects POST with XML or JSON ASN payload
380
- */
381
- export const processAsnWebhook = webhook('process-asn', {
382
- response: {
383
- mode: 'sync',
384
- onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
385
- status: 200,
386
- headers: { 'Content-Type': 'application/json' }
387
- }),
388
- onError: (ctx, error) => new Response(JSON.stringify({
389
- success: false,
390
- error: error.message,
391
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
392
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
393
- : error.message?.includes('mapping') || error.message?.includes('field')
394
- ? 'Check mapping configuration JSON and verify source paths match incoming ASN structure'
395
- : error.message?.includes('connection') || error.message?.includes('timeout')
396
- ? 'Check network connectivity and Fluent Commerce API availability'
397
- : 'Review error details and check ASN payload structure',
398
- timestamp: new Date().toISOString()
399
- }), {
400
- status: 500,
401
- headers: { 'Content-Type': 'application/json' }
402
- })
403
- }
404
- }, async (ctx) => {
405
- const { log, fetch, activation, connections, openKv } = ctx;
406
- const startTime = Date.now();
407
-
408
- log.info('🚚 [ASN] Processing Advanced Ship Notice');
409
-
410
- // ? Enhanced: Configuration validation
411
- if (!connections || !connections.fluent_commerce) {
412
- log.error('❌ [ASN] Configuration error: Missing fluent_commerce connection', {
413
- recommendation: 'Configure fluent_commerce connection in Connections section with OAuth2 credentials'
414
- });
415
- throw new Error('Missing fluent_commerce connection');
416
- }
417
-
418
- try {
419
- // =================================================================
420
- // STEP 1: EXTRACT AND PARSE ASN PAYLOAD
421
- // =================================================================
422
- // Get webhook payload
423
- // Supports both XML (EDI 856 format) and JSON
424
- const rawPayload = activation?.body;
425
- const contentType = activation?.headers?.['content-type'] || 'application/json';
426
-
427
- log.info('📦 [ASN] Payload received', {
428
- contentType,
429
- payloadSize: JSON.stringify(rawPayload).length,
430
- });
431
-
432
- // Determine format and parse
433
- let asnData: any;
434
- if (contentType.includes('xml') || contentType.includes('text')) {
435
- // Parse XML ASN (EDI 856 format)
436
- log.info('📄 [ASN] Parsing XML payload');
437
- const xmlParser = new XMLParserService();
438
- asnData = await xmlParser.parse(rawPayload);
439
- log.debug('✅ [ASN] XML parsed successfully', { rootKeys: Object.keys(asnData) });
440
- } else {
441
- // Assume JSON format
442
- log.info('📝 [ASN] Processing JSON payload');
443
- asnData = rawPayload;
444
- }
445
-
446
- // Extract ASN identifier for tracking
447
- // Common paths: ASN.shipment_id, ShipNotice.shipment_number, etc.
448
- const asnId =
449
- asnData?.ShipNotice?.['@shipment_id'] ||
450
- asnData?.ASN?.shipment_number ||
451
- asnData?.shipmentId ||
452
- 'UNKNOWN';
453
-
454
- log.info(`🔍 [ASN] Processing ASN: ${asnId}`);
455
-
456
- // =================================================================
457
- // STEP 2: CREATE FLUENT CLIENT
458
- // =================================================================
459
- // Create SDK logger adapter to map Versori log to SDK Logger interface
460
- const logger = {
461
- info: (msg: string, ...args: any[]) => log.info(msg, ...args),
462
- warn: (msg: string, ...args: any[]) => log.warn(msg, ...args),
463
- error: (msg: string, ...args: any[]) => log.error(msg, ...args),
464
- debug: (msg: string, ...args: any[]) => log.debug?.(msg, ...args) || log.info(msg, ...args)
465
- };
466
-
467
- // Create context for SDK client factory
468
- const fluentClient = await createClient({ ...ctx, log: logger }); // Auto-detects Versori context
469
-
470
- log.info('✅ [ASN] Fluent client initialized');
471
-
472
- // =================================================================
473
- // STEP 3: VALIDATE CONNECTION
474
- // =================================================================
475
- log.info('🔌 [ASN] Validating Fluent connection');
476
- await fluentClient.validateConnection();
477
- log.info('✅ [ASN] Connection validated successfully');
478
-
479
- // =================================================================
480
- // STEP 4: VALIDATE ASN (PREVENT DUPLICATES)
481
- // =================================================================
482
- // Query existing receipts to check if ASN already processed
483
- // IMPORTANT: Prevents duplicate expected inventory from same ASN
484
- log.info('🔍 [ASN] Checking for duplicate ASN');
485
- const duplicateCheckQuery = `
486
- query CheckExistingReceipt($ref: String!) {
487
- inventoryQuantities(ref: $ref, first: 1) {
488
- edges {
489
- node {
490
- id
491
- ref
492
- status
493
- }
494
- }
495
- }
496
- }`;
497
-
498
- const duplicateCheck = await fluentClient.graphql({
499
- query: duplicateCheckQuery,
500
- variables: {
501
- ref: `ASN-${asnId}`, // Use ASN ID as ref
502
- },
503
- });
504
-
505
- const existingReceipt = duplicateCheck.data?.inventoryQuantities?.edges?.[0]?.node;
506
-
507
- if (existingReceipt) {
508
- log.warn('⚠️ [ASN] Duplicate detected - ASN already processed', {
509
- asnId,
510
- existingReceiptId: existingReceipt.id,
511
- existingStatus: existingReceipt.status,
512
- });
513
-
514
- return {
515
- success: false,
516
- message: 'ASN already processed (duplicate)',
517
- asnId,
518
- existingReceiptId: existingReceipt.id,
519
- timestamp: new Date().toISOString(),
520
- duration: `${Date.now() - startTime}ms`,
521
- };
522
- }
523
-
524
- // =================================================================
525
- // STEP 5: SAVE RAW ASN FOR AUDIT TRAIL (KV Storage - Versori-compatible)
526
- // =================================================================
527
- log.info('Saving raw payload for audit trail');
528
- try {
529
- const kv = openKv(':project:');
530
- const timestamp = new Date().toISOString();
531
- const auditKey = ['asn', 'audit', asnId, timestamp];
532
-
533
- // Save original payload
534
- await kv.set([...auditKey, 'asn-raw'], asnData);
535
-
536
- log.info('Raw ASN saved to KV storage', { asnId, timestamp });
537
- } catch (kvError: any) {
538
- log.warn('Failed to save ASN to KV storage', { error: kvError.message });
539
- }
540
-
541
- // =================================================================
542
- // STEP 6: MAP ASN TO FLUENT EXPECTED RECEIPT FORMAT
543
- // =================================================================
544
- log.info('🗺️ [ASN] Starting ASN mapping');
545
- const mappingStartTime = Date.now();
546
-
547
- const mapper = new GraphQLMutationMapper(
548
- asnMappingConfig as any,
549
- logger,
550
- { fluentClient: fluentClient as any }
551
- );
552
-
553
- // Apply mapping with custom resolvers
554
- // Context includes fluentClient for API calls (SKU validation, etc.)
555
- // Returns MapWithNodesResult with query auto-generated!
556
- const mappingResult = await mapper.mapWithNodes(asnData, asnResolvers, {
557
- fluentClient: fluentClient as any,
558
- asnId,
559
- config: {
560
- retailerId: process.env.RETAILER_ID || '1',
561
- defaultLocation: process.env.DEFAULT_RECEIVING_LOCATION || 'DC-RECEIVING',
562
- },
563
- helpers: {
564
- fluentClient: fluentClient as any,
565
- },
566
- });
567
-
568
- if (!mappingResult.success) {
569
- log.error('❌ [ASN] Mapping failed', {
570
- errors: mappingResult.errors,
571
- recommendation: 'Check mapping configuration JSON and verify source paths match incoming ASN structure'
572
- });
573
- throw new Error(`ASN mapping failed: ${mappingResult.errors?.join(', ')}`);
574
- }
575
-
576
- log.info('✅ [ASN] Mapping successful', {
577
- asnId,
578
- itemCount: mappingResult.data?.items?.length || 0,
579
- duration: `${Date.now() - mappingStartTime}ms`,
580
- });
581
-
582
- // Save mapped data to KV storage
583
- try {
584
- const kv = openKv(':project:');
585
- const timestamp = new Date().toISOString();
586
- const auditKey = ['asn', 'audit', asnId, timestamp, 'fluent-mapped'];
587
- await kv.set(auditKey, mappingResult.data);
588
- } catch (kvError: any) {
589
- log.warn('Failed to save mapped data to KV', { error: kvError.message });
590
- }
591
-
592
- // =================================================================
593
- // STEP 7: CREATE EXPECTED RECEIPT IN FLUENT
594
- // =================================================================
595
- log.info('🚀 [ASN] Creating expected inventory receipt in Fluent');
596
- const mutationStartTime = Date.now();
597
-
598
- // mapWithNodes() auto-generates query - no need to call buildMutation()!
599
- // Execute mutation (use result.variables for GraphQL execution)
600
- const receiptResult = await fluentClient.graphql({
601
- query: mappingResult.query,
602
- variables: mappingResult.variables // ✅ Use variables (wrapped if fields pattern)
603
- });
604
-
605
- if (receiptResult.errors) {
606
- log.error('❌ [ASN] Receipt creation failed', {
607
- errors: receiptResult.errors,
608
- recommendation: 'Check GraphQL mutation structure and field types',
609
- });
610
- throw new Error(`Receipt creation failed: ${JSON.stringify(receiptResult.errors)}`);
611
- }
612
-
613
- const createdReceipt = receiptResult.data?.createInventoryQuantity;
614
-
615
- log.info('✅ [ASN] Expected receipt created successfully', {
616
- receiptId: createdReceipt?.id,
617
- asnId,
618
- duration: `${Date.now() - mutationStartTime}ms`,
619
- });
620
-
621
- // Save Fluent response to KV storage
622
- try {
623
- const kv = openKv(':project:');
624
- const timestamp = new Date().toISOString();
625
- const auditKey = ['asn', 'audit', asnId, timestamp, 'fluent-response'];
626
- await kv.set(auditKey, receiptResult);
627
- } catch (kvError: any) {
628
- log.warn('Failed to save Fluent response to KV', { error: kvError.message });
629
- }
630
-
631
- // =================================================================
632
- // STEP 8: RETURN SUCCESS RESPONSE TO 3PL
633
- // =================================================================
634
- const totalDuration = Date.now() - startTime;
635
- log.info('🎉 [ASN] Processing completed successfully', {
636
- asnId,
637
- totalDuration: `${totalDuration}ms`,
638
- });
639
-
640
- return {
641
- success: true,
642
- message: 'ASN processed successfully',
643
- data: {
644
- asnId,
645
- receiptId: createdReceipt?.id,
646
- receiptRef: createdReceipt?.ref,
647
- itemCount: mappingResult.data?.items?.length || 0,
648
- expectedDate: mappingResult.data?.expectedOn,
649
- timestamp: new Date().toISOString(),
650
- duration: `${totalDuration}ms`,
651
- },
652
- };
653
- } catch (error: any) {
654
- // ? Enhanced: Error logging with recommendations
655
- const totalDuration = Date.now() - startTime;
656
- const errorDetails = {
657
- message: error instanceof Error ? error.message : String(error),
658
- stack: error instanceof Error ? error.stack : undefined,
659
- errorType: error instanceof Error ? error.constructor.name : 'Error',
660
- duration: `${totalDuration}ms`,
661
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
662
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
663
- : error.message?.includes('mapping') || error.message?.includes('field')
664
- ? 'Check mapping configuration JSON and verify source paths match incoming ASN structure'
665
- : error.message?.includes('connection') || error.message?.includes('timeout')
666
- ? 'Check network connectivity and Fluent Commerce API availability'
667
- : error.message?.includes('validation') || error.message?.includes('required')
668
- ? 'Ensure all required fields are present in the ASN webhook payload'
669
- : error.message?.includes('duplicate') || error.message?.includes('already processed')
670
- ? 'ASN may have been processed already - check existing receipts'
671
- : 'Review error details and check ASN payload structure',
672
- };
673
- log.error('❌ [ASN] Processing failed', errorDetails);
674
- throw error; // Let response handler catch it
675
- }
676
- });
677
-
678
- /**
679
- * Manual test endpoint - Upload ASN file for testing
680
- */
681
- export const manualAsnTest = webhook('manual-asn-test', {
682
- response: {
683
- mode: 'sync',
684
- onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
685
- status: 200,
686
- headers: { 'Content-Type': 'application/json' }
687
- }),
688
- onError: (ctx, error) => new Response(JSON.stringify({
689
- success: false,
690
- error: error.message,
691
- recommendation: 'Check that test ASN file exists at expected path'
692
- }), {
693
- status: 400,
694
- headers: { 'Content-Type': 'application/json' }
695
- })
696
- }
697
- }, async (ctx) => {
698
- const { log } = ctx;
699
- const startTime = Date.now();
700
-
701
- log.info('🧪 [TEST] Manual ASN test triggered');
702
-
703
- // Load test ASN data
704
- const testAsnPath = path.join(__dirname, '../../data/test-asn-sample.json');
705
- if (!fs.existsSync(testAsnPath)) {
706
- log.error('❌ [TEST] Test ASN file not found', { expectedPath: testAsnPath });
707
- return {
708
- success: false,
709
- error: 'Test ASN file not found',
710
- expectedPath: testAsnPath,
711
- recommendation: 'Create test-asn-sample.json in the data/ directory',
712
- };
713
- }
714
-
715
- const testAsnData = JSON.parse(fs.readFileSync(testAsnPath, 'utf-8'));
716
-
717
- log.info('✅ [TEST] Test ASN loaded', {
718
- asnId: testAsnData.shipmentId,
719
- duration: `${Date.now() - startTime}ms`,
720
- });
721
-
722
- // Note: In production, you would trigger the main workflow via HTTP call
723
- // This is a simplified test endpoint for development
724
- return {
725
- success: true,
726
- message: 'Test ASN loaded - trigger processAsnWebhook endpoint to process',
727
- asnId: testAsnData.shipmentId,
728
- testDataPath: testAsnPath,
729
- duration: `${Date.now() - startTime}ms`,
730
- };
731
- });
732
-
733
- /**
734
- * Health check endpoint
735
- */
736
- export const healthCheck = webhook('health-check', {
737
- response: {
738
- mode: 'sync',
739
- onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
740
- status: 200,
741
- headers: { 'Content-Type': 'application/json' }
742
- })
743
- }
744
- }, async (ctx) => {
745
- const { log } = ctx;
746
- log.info('💚 [HEALTH] Health check requested');
747
-
748
- return {
749
- success: true,
750
- service: 'ASN Processing',
751
- status: 'healthy',
752
- timestamp: new Date().toISOString(),
753
- };
754
- });
755
- ```
756
-
757
- ### 2. Mapping Configuration: `mappings/asn-to-fluent-receipt.json`
758
-
759
- ```json
760
- {
761
- "direction": "ingest",
762
- "sourceFormat": "json",
763
- "mutation": "createInventoryQuantity",
764
- "fields": {
765
- "ref": {
766
- "resolver": "custom.generateReceiptRef",
767
- "comment": "Generate unique receipt ref from ASN ID (REQUIRED)"
768
- },
769
- "locationRef": {
770
- "source": "destination.location_code",
771
- "resolver": "custom.normalizeLocationRef",
772
- "comment": "Receiving location (REQUIRED)"
773
- },
774
- "type": {
775
- "value": "EXPECTED",
776
- "comment": "Expected inventory type (REQUIRED)"
777
- },
778
- "status": {
779
- "value": "EXPECTED",
780
- "comment": "Status is EXPECTED until physically received (REQUIRED)"
781
- },
782
- "expectedOn": {
783
- "resolver": "custom.calculateExpectedDate",
784
- "comment": "Calculate based on ship date + carrier transit time (REQUIRED)"
785
- },
786
- "carrier": {
787
- "source": "shipment.carrier.name",
788
- "resolver": "sdk.trim",
789
- "comment": "Shipping carrier name"
790
- },
791
- "trackingNumber": {
792
- "source": "shipment.tracking_number",
793
- "resolver": "sdk.trim",
794
- "comment": "Carrier tracking number"
795
- },
796
- "retailer.id": {
797
- "resolver": "custom.getRetailerId",
798
- "comment": "Retailer ID from config (REQUIRED)"
799
- },
800
- "items": {
801
- "source": "shipment.items",
802
- "isArray": true,
803
- "comment": "Line items in the shipment",
804
- "fields": {
805
- "skuRef": {
806
- "source": "$.sku",
807
- "resolver": "custom.validateAndNormalizeSku",
808
- "comment": "Product SKU - must exist in Fluent (REQUIRED)"
809
- },
810
- "qty": {
811
- "source": "$.quantity",
812
- "resolver": "sdk.parseInt",
813
- "comment": "Expected quantity (REQUIRED)"
814
- },
815
- "lotNumber": {
816
- "source": "$.lot_number",
817
- "required": false,
818
- "comment": "Lot/batch number if applicable"
819
- },
820
- "serialNumbers": {
821
- "source": "$.serial_numbers",
822
- "isArray": true,
823
- "required": false,
824
- "comment": "Serial numbers for serialized items"
825
- },
826
- "expiryDate": {
827
- "source": "$.expiry_date",
828
- "resolver": "sdk.formatDate",
829
- "required": false,
830
- "comment": "Expiration date for perishable goods"
831
- },
832
- "containerRef": {
833
- "source": "$.container_id",
834
- "required": false,
835
- "comment": "Container/pallet ID for tracking"
836
- }
837
- }
838
- },
839
- "containers": {
840
- "source": "shipment.containers",
841
- "isArray": true,
842
- "required": false,
843
- "comment": "Container/pallet tracking information",
844
- "fields": {
845
- "containerRef": {
846
- "source": "$.container_id",
847
- "resolver": "sdk.toString"
848
- },
849
- "containerType": {
850
- "source": "$.type",
851
- "resolver": "sdk.uppercase",
852
- "comment": "PALLET, CARTON, TOTE, etc."
853
- },
854
- "weight": {
855
- "source": "$.weight",
856
- "resolver": "sdk.parseFloat"
857
- },
858
- "weightUnit": {
859
- "source": "$.weight_unit",
860
- "defaultValue": "LBS"
861
- },
862
- "dimensions": {
863
- "resolver": "custom.formatDimensions",
864
- "comment": "Format as length x width x height"
865
- }
866
- }
867
- },
868
- "attributes": {
869
- "resolver": "custom.buildReceiptAttributes",
870
- "comment": "Custom attributes for audit trail and tracking"
871
- }
872
- }
873
- }
874
- ```
875
-
876
- ### 3. Custom Resolvers: `src/resolvers/asn-resolvers.ts`
877
-
878
- ```typescript
879
- /**
880
- * ASN Processing Custom Resolvers
881
- */
882
- import type { ResolverMap } from './types';
883
-
884
- export const asnResolvers: ResolverMap = {
885
- /**
886
- * Generate unique receipt reference from ASN ID
887
- */
888
- 'custom.generateReceiptRef': (value: any, data: any, config: any, helpers: any): string => {
889
- const asnId = data.shipmentId || data.shipment_id || data.ASN?.id || 'UNKNOWN';
890
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
891
- return `ASN-${asnId}-${timestamp}`;
892
- },
893
-
894
- /**
895
- * Normalize location reference
896
- * Handles different location code formats from various 3PLs
897
- */
898
- 'custom.normalizeLocationRef': (value: any, data: any, config: any, helpers: any): string => {
899
- if (!value) {
900
- return config.defaultLocation || 'DC-RECEIVING';
901
- }
902
-
903
- // Normalize format: uppercase, replace spaces with hyphens
904
- return String(value)
905
- .toUpperCase()
906
- .trim()
907
- .replace(/\s+/g, '-');
908
- },
909
-
910
- /**
911
- * Calculate expected arrival date
912
- * Based on ship date + carrier transit time
913
- */
914
- 'custom.calculateExpectedDate': (value: any, data: any, config: any, helpers: any): string => {
915
- const shipDate = data.shipment?.ship_date || data.shipDate;
916
- const carrier = data.shipment?.carrier?.name || data.carrier;
917
-
918
- // Default transit times by carrier (in days)
919
- const transitTimes: Record<string, number> = {
920
- 'FEDEX_GROUND': 3,
921
- 'FEDEX_EXPRESS': 1,
922
- 'UPS_GROUND': 3,
923
- 'UPS_NEXT_DAY': 1,
924
- 'USPS': 5,
925
- 'DHL': 2,
926
- 'FREIGHT': 7,
927
- 'DEFAULT': 3,
928
- };
929
-
930
- // Get transit time
931
- const carrierKey = String(carrier).toUpperCase().replace(/\s+/g, '_');
932
- const transitDays = transitTimes[carrierKey] || transitTimes.DEFAULT;
933
-
934
- // Calculate expected date
935
- const shipDateObj = shipDate ? new Date(shipDate) : new Date();
936
- shipDateObj.setDate(shipDateObj.getDate() + transitDays);
937
-
938
- return shipDateObj.toISOString();
939
- },
940
-
941
- /**
942
- * Validate and normalize SKU
943
- * Ensures SKU exists in Fluent Commerce (ASYNC)
944
- */
945
- 'custom.validateAndNormalizeSku': async (
946
- value: any,
947
- data: any,
948
- config: any,
949
- helpers: any
950
- ): Promise<string> => {
951
- const sku = String(value).trim().toUpperCase();
952
-
953
- if (!helpers.fluentClient) {
954
- // If no client, just return normalized SKU
955
- return sku;
956
- }
957
-
958
- // Query Fluent to validate SKU exists
959
- const query = `
960
- query ValidateSku($skuRef: [String!]) {
961
- products(ref: $skuRef, first: 1) {
962
- edges {
963
- node {
964
- id
965
- ref
966
- }
967
- }
968
- }
969
- }`;
970
-
971
- try {
972
- const result = await helpers.fluentClient.graphql({
973
- query,
974
- variables: { skuRef: [sku] },
975
- });
976
-
977
- const product = result.data?.products?.edges?.[0]?.node;
978
-
979
- if (!product) {
980
- helpers.logger?.warn('SKU not found in Fluent Commerce', { sku });
981
- // Option 1: Throw error to halt processing
982
- // throw new Error(`SKU not found: ${sku}`);
983
-
984
- // Option 2: Return SKU anyway (let Fluent validation handle it)
985
- return sku;
986
- }
987
-
988
- helpers.logger?.debug('SKU validated successfully', { sku, productId: product.id });
989
- return sku;
990
- } catch (error: any) {
991
- helpers.logger?.error('SKU validation failed', { sku, error: error.message });
992
- return sku; // Return SKU anyway, let Fluent handle validation
993
- }
994
- },
995
-
996
- /**
997
- * Get retailer ID from configuration
998
- */
999
- 'custom.getRetailerId': (value: any, data: any, config: any, helpers: any): string => {
1000
- return config.retailerId || process.env.RETAILER_ID || '1';
1001
- },
1002
-
1003
- /**
1004
- * Format dimensions as string
1005
- */
1006
- 'custom.formatDimensions': (value: any, data: any, config: any, helpers: any): string => {
1007
- const container = data; // Current container object
1008
- const length = container.length || 0;
1009
- const width = container.width || 0;
1010
- const height = container.height || 0;
1011
- const unit = container.dimension_unit || 'IN';
1012
-
1013
- if (!length && !width && !height) {
1014
- return '';
1015
- }
1016
-
1017
- return `${length} x ${width} x ${height} ${unit}`;
1018
- },
1019
-
1020
- /**
1021
- * Build receipt attributes for audit trail
1022
- */
1023
- 'custom.buildReceiptAttributes': (
1024
- value: any,
1025
- data: any,
1026
- config: any,
1027
- helpers: any
1028
- ): Array<{ name: string; type: string; value: any }> => {
1029
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1030
-
1031
- // Add ASN metadata
1032
- attributes.push({
1033
- name: 'asn_id',
1034
- type: 'STRING',
1035
- value: config.asnId || data.shipmentId || 'UNKNOWN',
1036
- });
1037
-
1038
- attributes.push({
1039
- name: 'asn_received_date',
1040
- type: 'STRING',
1041
- value: new Date().toISOString(),
1042
- });
1043
-
1044
- // Add carrier tracking
1045
- if (data.shipment?.tracking_number) {
1046
- attributes.push({
1047
- name: 'tracking_number',
1048
- type: 'STRING',
1049
- value: data.shipment.tracking_number,
1050
- });
1051
- }
1052
-
1053
- // Add origin information
1054
- if (data.shipment?.origin) {
1055
- attributes.push({
1056
- name: 'origin_location',
1057
- type: 'STRING',
1058
- value: JSON.stringify(data.shipment.origin),
1059
- });
1060
- }
1061
-
1062
- // Add PO number if present
1063
- if (data.purchase_order_number) {
1064
- attributes.push({
1065
- name: 'po_number',
1066
- type: 'STRING',
1067
- value: data.purchase_order_number,
1068
- });
1069
- }
1070
-
1071
- // Cross-dock flag
1072
- if (data.shipment?.is_crossdock) {
1073
- attributes.push({
1074
- name: 'is_crossdock',
1075
- type: 'BOOLEAN',
1076
- value: 'true',
1077
- });
1078
- }
1079
-
1080
- return attributes;
1081
- },
1082
- };
1083
- ```
1084
-
1085
- ### 4. Sample ASN Test Data: `data/test-asn-sample.json`
1086
-
1087
- ```json
1088
- {
1089
- "shipmentId": "ASN-20250117-001",
1090
- "purchase_order_number": "PO-12345",
1091
- "shipment": {
1092
- "ship_date": "2025-01-17T10:00:00Z",
1093
- "carrier": {
1094
- "name": "FEDEX_GROUND",
1095
- "scac": "FXFE"
1096
- },
1097
- "tracking_number": "123456789012",
1098
- "origin": {
1099
- "name": "Acme Distribution Center",
1100
- "address": "123 Warehouse Dr",
1101
- "city": "Memphis",
1102
- "state": "TN",
1103
- "zip": "38101"
1104
- },
1105
- "is_crossdock": false,
1106
- "items": [
1107
- {
1108
- "sku": "ACME-WIDGET-100",
1109
- "quantity": 50,
1110
- "lot_number": "LOT20250115",
1111
- "container_id": "PALLET-001"
1112
- },
1113
- {
1114
- "sku": "ACME-GADGET-200",
1115
- "quantity": 100,
1116
- "container_id": "PALLET-001"
1117
- },
1118
- {
1119
- "sku": "ACME-TOOL-300",
1120
- "quantity": 25,
1121
- "lot_number": "LOT20250110",
1122
- "expiry_date": "2026-01-10T00:00:00Z",
1123
- "container_id": "PALLET-002"
1124
- }
1125
- ],
1126
- "containers": [
1127
- {
1128
- "container_id": "PALLET-001",
1129
- "type": "PALLET",
1130
- "weight": 250.5,
1131
- "weight_unit": "LBS",
1132
- "length": 48,
1133
- "width": 40,
1134
- "height": 60,
1135
- "dimension_unit": "IN"
1136
- },
1137
- {
1138
- "container_id": "PALLET-002",
1139
- "type": "PALLET",
1140
- "weight": 180.0,
1141
- "weight_unit": "LBS",
1142
- "length": 48,
1143
- "width": 40,
1144
- "height": 48,
1145
- "dimension_unit": "IN"
1146
- }
1147
- ]
1148
- },
1149
- "destination": {
1150
- "location_code": "DC-01-RECEIVING",
1151
- "name": "Distribution Center 01",
1152
- "address": "789 Commerce Pkwy",
1153
- "city": "Atlanta",
1154
- "state": "GA",
1155
- "zip": "30301"
1156
- }
1157
- }
1158
- ```
1159
-
1160
- ### 5. Package Configuration: `package.json`
1161
-
1162
- ```json
1163
- {
1164
- "name": "asn-processing-connector",
1165
- "version": "1.0.0",
1166
- "description": "ASN/Purchase Order processing connector for Acme 3PL integration",
1167
- "versori": {
1168
- "workflows": "./workflows/asn-receipt.ts"
1169
- },
1170
- "dependencies": {
1171
- "@fluentcommerce/fc-connect-sdk": "^0.1.39",
1172
- "@versori/run": "latest"
1173
- },
1174
- "devDependencies": {
1175
- "@types/node": "^20.0.0",
1176
- "typescript": "^5.0.0"
1177
- },
1178
- "scripts": {
1179
- "deploy": "versori deploy",
1180
- "logs": "versori logs",
1181
- "test": "node -r ts-node/register workflows/asn-receipt.ts"
1182
- }
1183
- }
1184
- ```
1185
-
1186
- ---
1187
-
1188
- ## Key Patterns Explained
1189
-
1190
- ### Pattern 1: ASN Duplicate Prevention
1191
-
1192
- **Check for Existing Receipt Before Creating:**
1193
-
1194
- ```typescript
1195
- // Query existing receipts using ASN ID as ref
1196
- const duplicateCheckQuery = `
1197
- query CheckExistingReceipt($ref: String!) {
1198
- inventoryQuantities(ref: $ref, first: 1) {
1199
- edges {
1200
- node {
1201
- id
1202
- ref
1203
- status
1204
- }
1205
- }
1206
- }
1207
- }`;
1208
-
1209
- const duplicateCheck = await fluentClient.graphql({
1210
- query: duplicateCheckQuery,
1211
- variables: {
1212
- ref: `ASN-${asnId}`, // Unique ref from ASN shipment ID
1213
- },
1214
- });
1215
-
1216
- const existingReceipt = duplicateCheck.data?.inventoryQuantities?.edges?.[0]?.node;
1217
-
1218
- if (existingReceipt) {
1219
- // ASN already processed - return early
1220
- return {
1221
- status: 200,
1222
- body: {
1223
- success: false,
1224
- message: 'ASN already processed (duplicate)',
1225
- existingReceiptId: existingReceipt.id,
1226
- },
1227
- };
1228
- }
1229
- ```
1230
-
1231
- **Why this matters**: 3PLs may send duplicate ASN notifications due to:
1232
- - Network retries
1233
- - System glitches
1234
- - Manual re-sends
1235
- - Webhook replay
1236
-
1237
- **Without duplicate prevention**: Creates duplicate expected inventory, causing:
1238
- - Inflated ATP calculations
1239
- - Incorrect stock levels
1240
- - Failed physical receipts (already expected)
1241
-
1242
- **Best practices**:
1243
- - Always use ASN shipment ID as part of receipt ref
1244
- - Query before creating
1245
- - Log duplicate attempts for monitoring
1246
-
1247
- ### Pattern 2: Expected Date Calculation
1248
-
1249
- **Calculate Arrival Date Based on Carrier Transit Time:**
1250
-
1251
- ```typescript
1252
- 'custom.calculateExpectedDate': (value, data, config, helpers) => {
1253
- const shipDate = data.shipment?.ship_date || data.shipDate;
1254
- const carrier = data.shipment?.carrier?.name || data.carrier;
1255
-
1256
- // Transit time lookup by carrier
1257
- const transitTimes = {
1258
- 'FEDEX_GROUND': 3,
1259
- 'FEDEX_EXPRESS': 1,
1260
- 'UPS_GROUND': 3,
1261
- 'UPS_NEXT_DAY': 1,
1262
- 'USPS': 5,
1263
- 'DHL': 2,
1264
- 'FREIGHT': 7,
1265
- 'DEFAULT': 3,
1266
- };
1267
-
1268
- const carrierKey = String(carrier).toUpperCase().replace(/\s+/g, '_');
1269
- const transitDays = transitTimes[carrierKey] || transitTimes.DEFAULT;
1270
-
1271
- // Add transit days to ship date
1272
- const shipDateObj = shipDate ? new Date(shipDate) : new Date();
1273
- shipDateObj.setDate(shipDateObj.getDate() + transitDays);
1274
-
1275
- return shipDateObj.toISOString();
1276
- };
1277
- ```
1278
-
1279
- **Why this matters**: Expected date drives:
1280
- - ATP calculations (Available To Promise)
1281
- - Allocation timing
1282
- - Customer promise dates
1283
- - Warehouse receiving schedules
1284
-
1285
- **Improvements for production:**
1286
-
1287
- ```typescript
1288
- // Add business days logic (skip weekends)
1289
- function addBusinessDays(date: Date, days: number): Date {
1290
- let result = new Date(date);
1291
- let addedDays = 0;
1292
-
1293
- while (addedDays < days) {
1294
- result.setDate(result.getDate() + 1);
1295
- // Skip weekends (0 = Sunday, 6 = Saturday)
1296
- if (result.getDay() !== 0 && result.getDay() !== 6) {
1297
- addedDays++;
1298
- }
1299
- }
1300
-
1301
- return result;
1302
- }
1303
-
1304
- // Add holiday logic
1305
- const holidays = ['2025-01-01', '2025-07-04', '2025-12-25'];
1306
-
1307
- function isHoliday(date: Date): boolean {
1308
- const dateStr = date.toISOString().substring(0, 10);
1309
- return holidays.includes(dateStr);
1310
- }
1311
- ```
1312
-
1313
- ### Pattern 3: SKU Validation (Async Resolver)
1314
-
1315
- **Validate SKU Exists in Fluent Before Creating Receipt:**
1316
-
1317
- ```typescript
1318
- 'custom.validateAndNormalizeSku': async (value, data, config, helpers) => {
1319
- const sku = String(value).trim().toUpperCase();
1320
-
1321
- // Query Fluent to check if SKU exists
1322
- const query = `
1323
- query ValidateSku($skuRef: [String!]) {
1324
- products(ref: $skuRef, first: 1) {
1325
- edges {
1326
- node {
1327
- id
1328
- ref
1329
- }
1330
- }
1331
- }
1332
- }
1333
- `;
1334
-
1335
- const result = await helpers.fluentClient.graphql({
1336
- query,
1337
- variables: { skuRef: [sku] },
1338
- });
1339
-
1340
- const product = result.data?.products?.edges?.[0]?.node;
1341
-
1342
- if (!product) {
1343
- // SKU not found - options:
1344
- // 1. Throw error (halt processing)
1345
- throw new Error(`SKU not found: ${sku}`);
1346
-
1347
- // 2. Log warning and continue (let Fluent handle validation)
1348
- helpers.logger?.warn('SKU not found', { sku });
1349
- return sku;
1350
- }
1351
-
1352
- return sku;
1353
- };
1354
- ```
1355
-
1356
- **When to use SKU validation**:
1357
- - ✅ **3PL sends ASN before products are created** (prevents invalid receipts)
1358
- - ✅ **SKU format differs between systems** (can normalize)
1359
- - ✅ **Need early warning** (alert on unknown SKUs)
1360
-
1361
- **When to skip**:
1362
- - ❌ **High volume** (100+ SKUs per ASN = slow)
1363
- - ❌ **Products always exist** (pre-synced)
1364
- - ❌ **Performance critical** (let Fluent validation handle it)
1365
-
1366
- **Performance optimization**:
1367
-
1368
- ```typescript
1369
- // Cache SKU validation results in resolver context
1370
- const skuCache = new Map<string, boolean>();
1371
-
1372
- if (skuCache.has(sku)) {
1373
- return sku; // Already validated
1374
- }
1375
-
1376
- const isValid = await validateSku(sku);
1377
- skuCache.set(sku, isValid);
1378
- ```
1379
-
1380
- ### Pattern 4: Container/Pallet Tracking
1381
-
1382
- **Track Containers for Efficient Receiving:**
1383
-
1384
- ```json
1385
- {
1386
- "containers": {
1387
- "source": "shipment.containers",
1388
- "isArray": true,
1389
- "fields": {
1390
- "containerRef": {
1391
- "source": "$.container_id"
1392
- },
1393
- "containerType": {
1394
- "source": "$.type",
1395
- "comment": "PALLET, CARTON, TOTE"
1396
- },
1397
- "weight": { "source": "$.weight" },
1398
- "dimensions": {
1399
- "resolver": "custom.formatDimensions"
1400
- }
1401
- }
1402
- }
1403
- }
1404
- ```
1405
-
1406
- **Why container tracking matters**:
1407
- - **Warehouse efficiency**: Receive entire pallets at once
1408
- - **Put-away optimization**: Route containers to correct zones
1409
- - **Cross-docking**: Direct routing without storage
1410
- - **Audit trail**: Track physical container movement
1411
-
1412
- **Real-world example**:
1413
-
1414
- ```
1415
- ASN contains:
1416
- - PALLET-001: 50x WIDGET + 100x GADGET
1417
- - PALLET-002: 25x TOOL (refrigerated)
1418
-
1419
- Warehouse receives PALLET-001 first:
1420
- → Scan PALLET-001 barcode
1421
- → System shows all 2 SKUs on pallet
1422
- → Receive all at once (no item-by-item scan)
1423
- → PALLET-002 still expected
1424
- ```
1425
-
1426
- ### Pattern 5: Cross-Dock Scenario Handling
1427
-
1428
- **Handle Direct Fulfillment Without Storage:**
1429
-
1430
- ```typescript
1431
- // In ASN data
1432
- {
1433
- "shipment": {
1434
- "is_crossdock": true, // Flag for cross-dock shipment
1435
- "destination_order_ref": "ORDER-12345"
1436
- }
1437
- }
1438
-
1439
- // Custom resolver for cross-dock logic
1440
- 'custom.buildReceiptAttributes': (value, data, config, helpers) => {
1441
- const attributes = [];
1442
-
1443
- if (data.shipment?.is_crossdock) {
1444
- attributes.push({
1445
- name: 'is_crossdock',
1446
- type: 'BOOLEAN',
1447
- value: 'true',
1448
- });
1449
-
1450
- // Link to destination order
1451
- if (data.shipment.destination_order_ref) {
1452
- attributes.push({
1453
- name: 'crossdock_order_ref',
1454
- type: 'STRING',
1455
- value: data.shipment.destination_order_ref,
1456
- });
1457
- }
1458
-
1459
- // Mark for expedited processing
1460
- attributes.push({
1461
- name: 'receiving_priority',
1462
- type: 'STRING',
1463
- value: 'HIGH',
1464
- });
1465
- }
1466
-
1467
- return attributes;
1468
- };
1469
- ```
1470
-
1471
- **Cross-dock workflow**:
1472
- 1. **ASN received** with `is_crossdock: true`
1473
- 2. **Expected receipt created** with crossdock flag
1474
- 3. **Physical receipt** → immediately allocated to order
1475
- 4. **Skip put-away** → direct to packing station
1476
- 5. **Ship within hours** (not days)
1477
-
1478
- **Benefits**:
1479
- - Faster order fulfillment (same-day ship)
1480
- - Reduced warehouse touches
1481
- - Lower storage costs
1482
- - Improved customer satisfaction
1483
-
1484
- ### Pattern 6: XML vs JSON Payload Handling
1485
-
1486
- **Support Multiple Payload Formats:**
1487
-
1488
- ```typescript
1489
- // Detect content type from header
1490
- const contentType = ctx.activation?.headers?.['content-type'] || 'application/json';
1491
-
1492
- let asnData: any;
1493
-
1494
- if (contentType.includes('xml') || contentType.includes('text')) {
1495
- // Parse XML (EDI 856 format)
1496
- const xmlParser = new XMLParserService();
1497
- asnData = await xmlParser.parse(rawPayload);
1498
- } else {
1499
- // Assume JSON
1500
- asnData = rawPayload;
1501
- }
1502
- ```
1503
-
1504
- **Why support both**:
1505
- - **Enterprise 3PLs**: Often use EDI 856 XML format
1506
- - **Modern 3PLs**: Use JSON REST APIs
1507
- - **Flexibility**: Handle both without separate connectors
1508
-
1509
- **EDI 856 XML example**:
1510
-
1511
- ```xml
1512
- <?xml version="1.0"?>
1513
- <ShipNotice shipment_id="ASN-001">
1514
- <Shipment>
1515
- <ShipDate>2025-01-17</ShipDate>
1516
- <Carrier name="FEDEX_GROUND"/>
1517
- <Items>
1518
- <Item>
1519
- <SKU>WIDGET-100</SKU>
1520
- <Quantity>50</Quantity>
1521
- </Item>
1522
- </Items>
1523
- </Shipment>
1524
- </ShipNotice>
1525
- ```
1526
-
1527
- **JSON equivalent**:
1528
-
1529
- ```json
1530
- {
1531
- "shipmentId": "ASN-001",
1532
- "shipment": {
1533
- "ship_date": "2025-01-17",
1534
- "carrier": { "name": "FEDEX_GROUND" },
1535
- "items": [
1536
- { "sku": "WIDGET-100", "quantity": 50 }
1537
- ]
1538
- }
1539
- }
1540
- ```
1541
-
1542
- ---
1543
-
1544
- ## Testing the Workflow
1545
-
1546
- ### 1. Create Test ASN File
1547
-
1548
- Already provided in `data/test-asn-sample.json` above.
1549
-
1550
- ### 2. Deploy to Versori Platform
1551
-
1552
- ```bash
1553
- cd asn-processing-connector
1554
- npm install
1555
-
1556
- # Deploy to Versori
1557
- versori deploy
1558
-
1559
- # Or if using Versori CLI v2+
1560
- npx @versori/cli deploy
1561
- ```
1562
-
1563
- ### 3. Configure Versori Connections
1564
-
1565
- In the Versori console:
1566
-
1567
- 1. Add **Fluent Commerce** connection:
1568
- - Connection name: `fluent_commerce`
1569
- - Base URL: `https://api.fluentcommerce.com/graphql`
1570
- - Auth: OAuth2 (Client Credentials or Password Grant)
1571
- - Client ID, Client Secret, Username, Password
1572
-
1573
- 2. Configure activation variables (optional):
1574
- - `OUTPUT_DIR`: Where to save audit files
1575
- - `RETAILER_ID`: Fluent retailer ID
1576
- - `DEFAULT_RECEIVING_LOCATION`: Default location code
1577
-
1578
- ### 4. Test via Webhook
1579
-
1580
- ```bash
1581
- # Get webhook URL from Versori console (usually https://{workspace}.versori.io/{workflow})
1582
- # Example: https://acme.versori.io/processAsnWebhook
1583
-
1584
- # Send test ASN via HTTP POST
1585
- curl -X POST https://acme.versori.io/processAsnWebhook \
1586
- -H "Content-Type: application/json" \
1587
- -d @data/test-asn-sample.json
1588
- ```
1589
-
1590
- ### 5. Use Manual Test Endpoint
1591
-
1592
- ```bash
1593
- # Trigger manual test (loads test-asn-sample.json from filesystem)
1594
- curl https://acme.versori.io/manualAsnTest
1595
- ```
1596
-
1597
- ### 6. Check Health Endpoint
1598
-
1599
- ```bash
1600
- # Verify service is running
1601
- curl https://acme.versori.io/healthCheck
1602
- ```
1603
-
1604
- ### 7. Verify in Fluent Commerce
1605
-
1606
- 1. Log into Fluent Console
1607
- 2. Navigate to Inventory → Inventory Quantities
1608
- 3. Filter by status: `EXPECTED`
1609
- 4. Find receipt with ref `ASN-20250117-001-*`
1610
- 5. Verify:
1611
- - Quantity matches ASN
1612
- - Expected date calculated correctly
1613
- - Attributes contain ASN metadata
1614
-
1615
- ### 8. Monitor Logs
1616
-
1617
- ```bash
1618
- # View real-time logs in Versori console
1619
- # Or use Versori CLI
1620
- versori logs --workflow=processAsnWebhook --tail
1621
-
1622
- # Check for errors
1623
- versori logs --workflow=processAsnWebhook --level=error
1624
- ```
1625
-
1626
- ---
1627
-
1628
- ## Common Issues and Solutions
1629
-
1630
- ### Issue 1: "Duplicate entity" Error from Fluent
1631
-
1632
- **Symptoms:**
1633
- - GraphQL error: "Entity with ref already exists"
1634
- - Second ASN attempt fails
1635
-
1636
- **Root Cause:**
1637
- - Receipt ref not unique
1638
- - Missing timestamp in ref generation
1639
-
1640
- **Solution:**
1641
-
1642
- ```typescript
1643
- // Ensure unique ref with timestamp
1644
- 'custom.generateReceiptRef': (value, data, config, helpers) => {
1645
- const asnId = data.shipmentId || 'UNKNOWN';
1646
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
1647
- const randomSuffix = Math.random().toString(36).substring(7);
1648
-
1649
- // Format: ASN-{asnId}-{timestamp}-{random}
1650
- return `ASN-${asnId}-${timestamp}-${randomSuffix}`;
1651
- };
1652
- ```
1653
-
1654
- ### Issue 2: Expected Date in the Past
1655
-
1656
- **Symptoms:**
1657
- - Expected date shows yesterday or earlier
1658
- - ATP calculation incorrect
1659
-
1660
- **Root Cause:**
1661
- - Ship date from ASN is in the past
1662
- - Transit time calculation starts from old date
1663
-
1664
- **Solution:**
1665
-
1666
- ```typescript
1667
- 'custom.calculateExpectedDate': (value, data, config, helpers) => {
1668
- const shipDate = data.shipment?.ship_date;
1669
- const carrier = data.shipment?.carrier?.name;
1670
-
1671
- // Get transit days
1672
- const transitDays = getTransitDays(carrier);
1673
-
1674
- // Use ship date or TODAY (whichever is later)
1675
- const baseDate = shipDate ? new Date(shipDate) : new Date();
1676
- const today = new Date();
1677
-
1678
- // If ship date is in the past, use today instead
1679
- if (baseDate < today) {
1680
- baseDate.setTime(today.getTime());
1681
- }
1682
-
1683
- // Add transit days
1684
- baseDate.setDate(baseDate.getDate() + transitDays);
1685
-
1686
- return baseDate.toISOString();
1687
- };
1688
- ```
1689
-
1690
- ### Issue 3: SKU Validation Slows Processing
1691
-
1692
- **Symptoms:**
1693
- - Webhook timeouts (>30 seconds)
1694
- - ASN with 100+ SKUs fails
1695
-
1696
- **Root Cause:**
1697
- - Sequential SKU validation queries
1698
- - No caching
1699
-
1700
- **Solution:**
1701
-
1702
- ```typescript
1703
- // Batch validate all SKUs in single query
1704
- 'custom.validateAllSkus': async (value, data, config, helpers) => {
1705
- const allSkus = data.shipment?.items?.map((item: any) => item.sku) || [];
1706
-
1707
- // Single query for all SKUs
1708
- const query = `
1709
- query ValidateSkus($skuRefs: [String!]) {
1710
- products(ref: $skuRefs, first: 200) {
1711
- edges {
1712
- node {
1713
- id
1714
- ref
1715
- }
1716
- }
1717
- }
1718
- }
1719
- `;
1720
-
1721
- const result = await helpers.fluentClient.graphql({
1722
- query,
1723
- variables: { skuRefs: allSkus },
1724
- });
1725
-
1726
- const validSkus = new Set(
1727
- result.data?.products?.edges?.map((e: any) => e.node.ref) || []
1728
- );
1729
-
1730
- // Check for missing SKUs
1731
- const missingSkus = allSkus.filter(sku => !validSkus.has(sku));
1732
-
1733
- if (missingSkus.length > 0) {
1734
- helpers.logger?.warn('Invalid SKUs found', { missingSkus });
1735
- // Option: throw error or continue
1736
- }
1737
-
1738
- return validSkus;
1739
- };
1740
- ```
1741
-
1742
- ### Issue 4: Cross-Dock Flag Not Honored
1743
-
1744
- **Symptoms:**
1745
- - Cross-dock inventory goes to storage
1746
- - Fulfillment delayed
1747
-
1748
- **Root Cause:**
1749
- - Warehouse receiving app doesn't check attributes
1750
- - Missing workflow trigger
1751
-
1752
- **Solution:**
1753
-
1754
- **Option 1: Create Fluent Event**
1755
-
1756
- ```typescript
1757
- // After creating receipt, send event if cross-dock
1758
- if (data.shipment?.is_crossdock) {
1759
- await fluentClient.sendEvent({
1760
- name: 'InventoryReceiptCrossDock',
1761
- entityType: 'INVENTORY',
1762
- entityRef: createdReceipt.ref,
1763
- data: {
1764
- destinationOrderRef: data.shipment.destination_order_ref,
1765
- priority: 'HIGH',
1766
- },
1767
- });
1768
- }
1769
- ```
1770
-
1771
- **Option 2: Use Different Location**
1772
-
1773
- ```typescript
1774
- // Route cross-dock receipts to special location
1775
- 'custom.normalizeLocationRef': (value, data, config, helpers) => {
1776
- if (data.shipment?.is_crossdock) {
1777
- return 'DC-CROSSDOCK-RECEIVING'; // Special receiving zone
1778
- }
1779
- return value || config.defaultLocation;
1780
- };
1781
- ```
1782
-
1783
- ### Issue 5: Container Data Not Appearing in Fluent
1784
-
1785
- **Symptoms:**
1786
- - Container/pallet info missing in Fluent
1787
- - Can't track containers
1788
-
1789
- **Root Cause:**
1790
- - Fluent schema doesn't support container arrays directly
1791
- - Need to use attributes
1792
-
1793
- **Solution:**
1794
-
1795
- ```typescript
1796
- // Store containers in attributes instead of separate field
1797
- 'custom.buildReceiptAttributes': (value, data, config, helpers) => {
1798
- const attributes = [];
1799
-
1800
- // Serialize containers to JSON attribute
1801
- if (data.shipment?.containers) {
1802
- attributes.push({
1803
- name: 'containers',
1804
- type: 'STRING',
1805
- value: JSON.stringify(data.shipment.containers),
1806
- });
1807
-
1808
- // Also add container count for quick reference
1809
- attributes.push({
1810
- name: 'container_count',
1811
- type: 'INTEGER',
1812
- value: String(data.shipment.containers.length),
1813
- });
1814
- }
1815
-
1816
- return attributes;
1817
- };
1818
- ```
1819
-
1820
- ---
1821
-
1822
- ## Related Guides
1823
-
1824
- - **[Connector Platform Scheduled: Cycle Count Reconciliation](../workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md)** - Reconciliation patterns
1825
- - **[Universal Mapping Guide](../../../02-CORE-GUIDES/advanced-services/advanced-services-readme.md)** - Field mapping patterns
1826
- - **[GraphQL Mutation Mapper](../../../02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md)** - Mutation generation
1827
- - **[XML Parsing Guide](../../../02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md)** - EDI 856 XML handling
1828
-
1829
- ---
1830
-
1831
- ## Production Checklist
1832
-
1833
- Before deploying to production:
1834
- - [ ] Test with real 3PL ASN payloads (XML and JSON)
1835
- - [ ] Configure carrier transit times based on real data
1836
- - [ ] Set up duplicate ASN monitoring/alerting
1837
- - [ ] Verify SKU validation strategy (batch vs individual)
1838
- - [ ] Test cross-dock scenario if applicable
1839
- - [ ] Configure webhook retry policy in 3PL system
1840
- - [ ] Set up error notifications (email/Slack)
1841
- - [ ] Document ASN format variations by 3PL
1842
- - [ ] Test large ASNs (100+ SKUs, 10+ containers)
1843
- - [ ] Verify expected date business days logic
1844
-
1845
- ---
1846
-
1847
- ## Performance Considerations
1848
-
1849
- **Large ASNs (100+ SKUs)**:
1850
-
1851
- ```typescript
1852
- // Process items in chunks to avoid timeouts
1853
- const CHUNK_SIZE = 50;
1854
-
1855
- for (let i = 0; i < items.length; i += CHUNK_SIZE) {
1856
- const chunk = items.slice(i, i + CHUNK_SIZE);
1857
- await processItemChunk(chunk);
1858
- }
1859
- ```
1860
-
1861
- **High Volume (1000+ ASNs/day)**:
1862
-
1863
- ```typescript
1864
- // Use background processing with queue
1865
- export const processAsnWebhookQueued = webhook('queue-asn', {
1866
- response: {
1867
- mode: 'sync',
1868
- onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
1869
- status: 202, // Accepted
1870
- headers: { 'Content-Type': 'application/json' }
1871
- })
1872
- }
1873
- }, async (ctx) => {
1874
- const { activation } = ctx;
1875
-
1876
- // Immediately acknowledge receipt
1877
- const asnId = activation.body.shipmentId;
1878
-
1879
- // Queue for async processing (implement queueAsnForProcessing function)
1880
- await queueAsnForProcessing(asnId, activation.body);
1881
-
1882
- return {
1883
- success: true,
1884
- message: 'ASN queued for processing',
1885
- asnId,
1886
- };
1887
- });
1888
- ```
1889
-
1890
- ---
1891
-
1892
- ## Next Steps
1893
-
1894
- 1. **Add Receipt Confirmation**: Send confirmation back to 3PL when receipt created
1895
- 2. **Implement Discrepancy Handling**: Compare expected vs actual receipt quantities
1896
- 3. **Add Serial Number Tracking**: Full serial number lifecycle
1897
- 4. **Integrate with WMS**: Push ASN to warehouse management system
1898
- 5. **Build Dashboard**: Real-time ASN status monitoring
1899
- 6. **Add Carrier Tracking Integration**: Real-time shipment tracking via carrier APIs
1900
-
1901
- ---
1902
-
1903
- **Need Help?**
1904
- - Review SDK documentation: `/docs/readme.md`
1905
- - Check example connectors: `/connectors/Sample versori connectors/`
1906
- - Connector platform documentation: Check your platform's docs
1
+ ---
2
+ template_id: tpl-webhook-asn-purchase-order
3
+ canonical_filename: template-webhook-asn-purchase-order.md
4
+ version: 2.0.0
5
+ sdk_version: ^0.1.39
6
+ runtime: versori
7
+ direction: ingestion
8
+ source: webhook-xml-json-asn
9
+ destination: fluent-graphql
10
+ entity: inventory-receipt
11
+ format: xml-json
12
+ logging: versori
13
+ status: stable
14
+ features:
15
+ - webhook-signature-validation
16
+ - batched-events
17
+ - attribute-transformation
18
+ - memory-management
19
+ - enhanced-logging
20
+ ---
21
+
22
+ # Template: Webhook - ASN & Purchase Order Processing
23
+
24
+ **Template Version:** 2.0.0
25
+ **SDK Version:** @fluentcommerce/fc-connect-sdk@^0.1.39
26
+ **Last Updated:** 2025-01-24
27
+ **Deployment Target:** Versori Platform
28
+
29
+ **🆕 Version 2.0.0 Enhancements:**
30
+ - ✅ **Webhook Signature Validation** - Secure webhook verification with HMAC-SHA256
31
+ - ✅ **Batched Events** - Process events in optimized batches to reduce API calls
32
+ - ✅ **Attribute Transformation** - Handle nested arrays and complex data structures
33
+ - ✅ **Memory Management** - Clear large arrays after processing batches
34
+ - ✅ **Enhanced Logging** - Track batch processing and event submission with emoji indicators
35
+
36
+ **FC Connect SDK Use Case Guide**
37
+
38
+ > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
39
+ > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
40
+
41
+ **Context**: Process Advanced Ship Notices (ASN) from 3PL warehouse and create expected inventory receipts in Fluent Commerce
42
+
43
+ **Complexity**: Medium-High
44
+
45
+ **Runtime**: Versori Platform
46
+
47
+ **Estimated Lines**: ~750 lines (modular structure)
48
+
49
+ ---
50
+
51
+ ## STEP 1: Understand This Template
52
+
53
+ **What This Template Does:**
54
+
55
+ - HTTP webhook endpoint receiving ASN data from 3PL/WMS
56
+ - XML/JSON parsing for ASN payload (EDI 856 compatible structure)
57
+ - GraphQL mutation to create expected inventory receipts
58
+ - Container/pallet tracking support
59
+ - Cross-dock scenario handling (direct fulfillment without storage)
60
+ - Expected date calculation (carrier transit time)
61
+ - Custom resolvers for address normalization and SKU validation
62
+ - Audit trail and notification system
63
+ - Error handling for duplicate ASN prevention
64
+ - **Sync + Fire-and-Forget Pattern**: Fast webhook response, background processing
65
+
66
+ **Key SDK Components:**
67
+
68
+ - `createClient()` - Universal client factory (auto-detects Versori context)
69
+ - `GraphQLMutationMapper` - ASN → GraphQL mutation mapping
70
+ - `XMLParserService` / `JSONParserService` - ASN parsing (EDI 856 format)
71
+ - Native Versori `log` - Use `log` from context
72
+
73
+ **Entity Type:**
74
+
75
+ - **InventoryReceipt** - Fluent entity for expected inventory
76
+ - **GraphQL Mutation** - Uses `createInventoryReceipt` mutation
77
+
78
+ **Critical Patterns:**
79
+
80
+ - **Sync + Fire-and-Forget**: Webhook validates quickly, returns immediately, processes ASN in background
81
+ - **External JSON Config**: Mapping configuration in separate JSON file (`config/asn-mapping.json`)
82
+ - **Modular Architecture**: Separate services, workflows, config, types folders
83
+ - **Background Processing**: Long-running operations (GraphQL mutations, validation) happen asynchronously
84
+ - **Error Handling**: Comprehensive error handling with duplicate detection
85
+
86
+ **When to Use This Template:**
87
+
88
+ - ✅ ASN processing from 3PL/WMS systems
89
+ - ✅ EDI 856 format (XML or JSON)
90
+ - ✅ Need fast webhook response (don't wait for receipt creation)
91
+ - ✅ Container/pallet tracking
92
+ - ✅ Cross-dock scenarios
93
+
94
+ **When NOT to Use:**
95
+
96
+ - ❌ Bulk ASN processing (use Batch API or scheduled workflows)
97
+ - ❌ Real-time inventory updates (use Event API)
98
+ - ❌ Need synchronous receipt creation (wait for result before responding)
99
+
100
+ ---
101
+
102
+ ## STEP 2: Implementation Prompt for Claude Code
103
+
104
+ **Copy this prompt and send to Claude Code to generate the complete implementation:**
105
+
106
+ ```
107
+ Create a Versori webhook workflow for ASN (Advanced Ship Notice) processing to Fluent Commerce.
108
+
109
+ REQUIREMENTS:
110
+ 1. Runtime: Versori Platform (HTTP webhook)
111
+ 2. Source: ASN data via HTTP POST webhook (XML or JSON, EDI 856 format)
112
+ 3. Destination: Fluent Commerce GraphQL API (createInventoryReceipt mutation)
113
+ 4. Format: XML or JSON (EDI 856 compatible)
114
+ 5. Entity: InventoryReceipt (GraphQL mutation)
115
+
116
+ KEY FEATURES:
117
+ - Sync + fire-and-forget pattern (fast webhook response, background processing)
118
+ - External JSON mapping configuration (config/asn-mapping.json)
119
+ - Modular architecture (workflows/, services/, config/, types/)
120
+ - XML/JSON parsing with EDI 856 structure support
121
+ - GraphQLMutationMapper for ASN → GraphQL transformation
122
+ - Custom resolvers for address normalization and SKU validation
123
+ - Duplicate ASN detection and prevention
124
+ - Audit trail (save input/output files)
125
+
126
+ CRITICAL REQUIREMENTS:
127
+ 1. Webhook Mode: response: { mode: 'sync' } (fast response)
128
+ 2. Background Processing: Fire-and-forget pattern (no await on long operations)
129
+ 3. Mapping Config: External JSON file (config/asn-mapping.json)
130
+ 4. Modular Structure: Separate services/, config/, types/ folders
131
+ 5. Native Logging: Use log from context (no LoggingService)
132
+ 6. Error Handling: Duplicate detection, comprehensive error responses
133
+
134
+ SDK METHODS TO USE:
135
+ - createClient({ ...ctx, log }) - Pass full Versori context
136
+ - new GraphQLMutationMapper(mappingConfig, log, { fluentClient: client }) - Initialize mapper
137
+ - mapper.mapWithNodes(asnData, customResolvers, context) - Map ASN with custom resolvers (REQUIRED for custom resolvers)
138
+ - mapWithNodes() now returns query automatically (no need for buildMutation())
139
+ - client.graphql({ query, variables }) - Execute GraphQL mutation
140
+
141
+ FORBIDDEN PATTERNS:
142
+ - ❌ Inline mapping config (use external JSON)
143
+ - ❌ await on background processing (use fire-and-forget)
144
+ - ❌ LoggingService (use native log from context)
145
+ - ❌ All code in one file (use modular structure)
146
+ - ❌ async mode webhook (use sync + fire-and-forget)
147
+ ```
148
+
149
+ ---
150
+
151
+ ## STEP 3: Detailed Flow Documentation
152
+
153
+ ### Complete Processing Flow
154
+
155
+ ```
156
+ ┌─────────────────────────────────────────────────────────────┐
157
+ │ 1. WEBHOOK RECEIVED │
158
+ │ POST https://{workspace}.versori.run/process-asn │
159
+ │ Content-Type: application/xml or application/json │
160
+ │ Body: <ShipNotice>...</ShipNotice> or { ... } │
161
+ └────────────────────┬────────────────────────────────────────┘
162
+
163
+
164
+ ┌─────────────────────────────────────────────────────────────┐
165
+ │ 2. QUICK VALIDATION (Synchronous, ~10-50ms) │
166
+ │ - Check fluent_commerce connection exists │
167
+ │ - Validate ASN payload present │
168
+ │ - Return HTTP 200 OK immediately │
169
+ └────────────────────┬────────────────────────────────────────┘
170
+
171
+
172
+ ┌─────────────────────────────────────────────────────────────┐
173
+ │ 3. BACKGROUND PROCESSING (Fire-and-Forget) │
174
+ │ ┌─────────────────────────────────────────────────────┐ │
175
+ │ │ 3a. Initialize Fluent Client │ │
176
+ │ │ - createClient({ ...ctx, log }) │ │
177
+ │ └─────────────────────────────────────────────────────┘ │
178
+ │ ┌─────────────────────────────────────────────────────┐ │
179
+ │ │ 3b. Parse ASN (XML or JSON) │ │
180
+ │ │ - XMLParserService or JSONParserService │ │
181
+ │ │ - Extract ASN identifier │ │
182
+ │ └─────────────────────────────────────────────────────┘ │
183
+ │ ┌─────────────────────────────────────────────────────┐ │
184
+ │ │ 3c. Check for Duplicates │ │
185
+ │ │ - Query Fluent for existing receipt │ │
186
+ │ │ - Return early if duplicate │ │
187
+ │ └─────────────────────────────────────────────────────┘ │
188
+ │ ┌─────────────────────────────────────────────────────┐ │
189
+ │ │ 3d. Map ASN to GraphQL Variables │ │
190
+ │ │ - Load mapping config from JSON │ │
191
+ │ │ - GraphQLMutationMapper.map() │ │
192
+ │ │ - Apply custom resolvers │ │
193
+ │ │ - Returns { success, data, query, context } │ │
194
+ │ └─────────────────────────────────────────────────────┘ │
195
+ │ ┌─────────────────────────────────────────────────────┐ │
196
+ │ │ 3e. Execute GraphQL Mutation │ │
197
+ │ │ - client.graphql({ query: result.query, │ │
198
+ │ │ variables: result.variables })│ │
199
+ │ └─────────────────────────────────────────────────────┘ │
200
+ │ ┌─────────────────────────────────────────────────────┐ │
201
+ │ │ 3g. Save Audit Trail (Optional) │ │
202
+ │ │ - asn-input.json │ │
203
+ │ │ - mapped-variables.json │ │
204
+ │ │ - graphql-response.json │ │
205
+ │ └─────────────────────────────────────────────────────┘ │
206
+ └─────────────────────────────────────────────────────────────┘
207
+ ```
208
+
209
+ ### Response Timing
210
+
211
+ | Stage | Timing | Blocking |
212
+ |-------|--------|----------|
213
+ | **Webhook Validation** | ~10-50ms | ✅ Yes (blocks response) |
214
+ | **Background Processing** | ~1000-3000ms | ❌ No (fire-and-forget) |
215
+ | **Total Response Time** | ~10-50ms | ✅ Fast response |
216
+
217
+ **Key Benefit**: Webhook caller receives immediate acknowledgment (~50ms) while ASN processing happens in background (~2-3s).
218
+
219
+ ---
220
+
221
+ ## STEP 4: Production Modular Structure
222
+
223
+ > **✅ This section shows the COMPLETE production-ready modular structure.**
224
+ > All files are shown with proper imports/exports and folder organization.
225
+
226
+ ### Complete Project Structure
227
+
228
+ ```
229
+ asn-purchase-order-processing/
230
+ ├── package.json # Dependencies and Versori config
231
+ ├── index.ts # Entry point - exports all workflows
232
+ └── src/
233
+ ├── workflows/
234
+ │ └── webhook/
235
+ │ └── asn-receipt.ts # Webhook: Receive ASN notifications
236
+
237
+ ├── services/
238
+ │ └── asn-processing.service.ts # Shared orchestration logic (reusable)
239
+
240
+ ├── resolvers/
241
+ │ └── asn-resolvers.ts # Custom resolvers for transformations
242
+
243
+ ├── config/
244
+ │ └── asn-mapping.json # Mapping configuration (external JSON)
245
+
246
+ └── types/
247
+ └── asn.types.ts # TypeScript interfaces
248
+ ```
249
+
250
+ **Why This Structure?**
251
+
252
+ - ✅ **Clear separation**: Webhook handlers vs business logic
253
+ - ✅ **Reusable services**: ASN processing logic can be reused
254
+ - ✅ **External config**: Mapping changes don't require code changes
255
+ - ✅ **Custom resolvers**: Separate file for complex transformations
256
+ - ✅ **Type safety**: TypeScript interfaces for better IDE support
257
+ - ✅ **Scalable**: Easy to add new ASN formats or processing steps
258
+
259
+ ---
260
+
261
+ ## SDK Methods Used
262
+
263
+ ```typescript
264
+ // Core SDK imports
265
+ import {
266
+ createClient,
267
+ GraphQLMutationMapper,
268
+ XMLParserService,
269
+ JSONParserService,
270
+ } from '@fluentcommerce/fc-connect-sdk';
271
+
272
+ // Versori imports
273
+ import { webhook } from '@versori/run';
274
+ import { Buffer } from 'node:buffer'; // Required for Versori runtime
275
+
276
+ // Key methods
277
+ const logger = { info, warn, error, debug }; // Map Versori log to SDK Logger interface
278
+ const client = await createClient({ ...ctx, log: logger }); // Auto-detects Versori context
279
+ new GraphQLMutationMapper(config, logger, { fluentClient: client }); // Map ASN to GraphQL (uses logger adapter)
280
+ new XMLParserService(); // Parse ASN XML (EDI 856)
281
+ mapper.mapWithNodes(asnData, resolvers, context); // Apply mapping with custom logic
282
+ // mapWithNodes() now returns query automatically - use result.query
283
+ client.graphql({ query, variables }); // Execute mutation
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Versori Workflows Structure
289
+
290
+ **Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
291
+
292
+ **Trigger Types:**
293
+ - **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
294
+ - **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
295
+ - **`http()`** → External API calls (chained from webhook/schedule)
296
+ - **`fn()`** → Internal processing (chained from webhook/schedule)
297
+
298
+ ### Recommended Project Structure
299
+
300
+ ```
301
+ asn-purchase-order-processing/
302
+ ├── index.ts # Entry point - exports all workflows
303
+ └── src/
304
+ ├── workflows/
305
+ │ └── webhook/
306
+ │ └── asn-receipt.ts # Webhook: Receive ASN notifications
307
+
308
+ ├── services/
309
+ │ └── asn-processing.service.ts # Shared orchestration logic (reusable)
310
+
311
+ └── config/
312
+ └── asn-mapping.json # Mapping configuration
313
+ ```
314
+
315
+ **Benefits:**
316
+ - ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
317
+ - ✅ Descriptive file names (easy to browse and understand)
318
+ - ✅ Scalable (add new workflows without cluttering)
319
+ - ✅ Reusable code in `services/` (DRY principle)
320
+ - ✅ Easy to modify individual workflows without affecting others
321
+
322
+ ---
323
+
324
+ ## Complete Working Code
325
+
326
+ ### 1. Entry Point: `index.ts`
327
+
328
+ ```typescript
329
+ /**
330
+ * Entry point - Export all workflows for Versori platform
331
+ *
332
+ * This file exports all workflows to be registered with Versori.
333
+ * Each workflow is defined in its own file for better organization.
334
+ *
335
+ * MEMORY INTERPRETER PATTERN:
336
+ * Versori's interpreter reads this file and registers all exported workflows.
337
+ * Do NOT use dynamic imports or conditional exports.
338
+ */
339
+
340
+ // Webhook workflows
341
+ export { processAsnWebhook } from './workflows/webhook/asn-receipt';
342
+ export { manualAsnTest } from './workflows/webhook/asn-receipt';
343
+ export { healthCheck } from './workflows/webhook/asn-receipt';
344
+ ```
345
+
346
+ ### 2. Main Webhook Workflow: `workflows/webhook/asn-receipt.ts`
347
+
348
+ ```typescript
349
+ /**
350
+ * ASN (Advanced Ship Notice) Processing Webhook
351
+ *
352
+ * Receives ASN notifications from Acme 3PL/WMS and creates expected inventory
353
+ * receipts in Fluent Commerce.
354
+ *
355
+ * ASN = Advanced Ship Notice (EDI 856) - notification that shipment is on the way
356
+ *
357
+ * Flow:
358
+ * 1. Receive ASN webhook (XML or JSON payload from 3PL)
359
+ * 2. Parse ASN structure (shipment header, containers, items)
360
+ * 3. Map to Fluent expected receipt format
361
+ * 4. Calculate expected arrival date (based on carrier and transit time)
362
+ * 5. Create inventory receipt mutation (creates expected inventory)
363
+ * 6. Send confirmation response to 3PL
364
+ * 7. Optional: Send notification to warehouse ops team
365
+ */
366
+ import { webhook } from '@versori/run';
367
+ import { Buffer } from 'node:buffer'; // Required for Versori runtime
368
+ import {
369
+ createClient,
370
+ GraphQLMutationMapper,
371
+ XMLParserService,
372
+ JSONParserService,
373
+ } from '@fluentcommerce/fc-connect-sdk';
374
+ import { asnResolvers } from '../../resolvers/asn-resolvers';
375
+ import asnMappingConfig from '../../mappings/asn-to-fluent-receipt.json' with { type: 'json' };
376
+
377
+ /**
378
+ * Main ASN webhook endpoint
379
+ * Expects POST with XML or JSON ASN payload
380
+ */
381
+ export const processAsnWebhook = webhook('process-asn', {
382
+ response: {
383
+ mode: 'sync',
384
+ onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
385
+ status: 200,
386
+ headers: { 'Content-Type': 'application/json' }
387
+ }),
388
+ onError: (ctx, error) => new Response(JSON.stringify({
389
+ success: false,
390
+ error: error.message,
391
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
392
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
393
+ : error.message?.includes('mapping') || error.message?.includes('field')
394
+ ? 'Check mapping configuration JSON and verify source paths match incoming ASN structure'
395
+ : error.message?.includes('connection') || error.message?.includes('timeout')
396
+ ? 'Check network connectivity and Fluent Commerce API availability'
397
+ : 'Review error details and check ASN payload structure',
398
+ timestamp: new Date().toISOString()
399
+ }), {
400
+ status: 500,
401
+ headers: { 'Content-Type': 'application/json' }
402
+ })
403
+ }
404
+ }, async (ctx) => {
405
+ const { log, fetch, activation, connections, openKv } = ctx;
406
+ const startTime = Date.now();
407
+
408
+ log.info('🚚 [ASN] Processing Advanced Ship Notice');
409
+
410
+ // ? Enhanced: Configuration validation
411
+ if (!connections || !connections.fluent_commerce) {
412
+ log.error('❌ [ASN] Configuration error: Missing fluent_commerce connection', {
413
+ recommendation: 'Configure fluent_commerce connection in Connections section with OAuth2 credentials'
414
+ });
415
+ throw new Error('Missing fluent_commerce connection');
416
+ }
417
+
418
+ try {
419
+ // =================================================================
420
+ // STEP 1: EXTRACT AND PARSE ASN PAYLOAD
421
+ // =================================================================
422
+ // Get webhook payload
423
+ // Supports both XML (EDI 856 format) and JSON
424
+ const rawPayload = activation?.body;
425
+ const contentType = activation?.headers?.['content-type'] || 'application/json';
426
+
427
+ log.info('📦 [ASN] Payload received', {
428
+ contentType,
429
+ payloadSize: JSON.stringify(rawPayload).length,
430
+ });
431
+
432
+ // Determine format and parse
433
+ let asnData: any;
434
+ if (contentType.includes('xml') || contentType.includes('text')) {
435
+ // Parse XML ASN (EDI 856 format)
436
+ log.info('📄 [ASN] Parsing XML payload');
437
+ const xmlParser = new XMLParserService();
438
+ asnData = await xmlParser.parse(rawPayload);
439
+ log.debug('✅ [ASN] XML parsed successfully', { rootKeys: Object.keys(asnData) });
440
+ } else {
441
+ // Assume JSON format
442
+ log.info('📝 [ASN] Processing JSON payload');
443
+ asnData = rawPayload;
444
+ }
445
+
446
+ // Extract ASN identifier for tracking
447
+ // Common paths: ASN.shipment_id, ShipNotice.shipment_number, etc.
448
+ const asnId =
449
+ asnData?.ShipNotice?.['@shipment_id'] ||
450
+ asnData?.ASN?.shipment_number ||
451
+ asnData?.shipmentId ||
452
+ 'UNKNOWN';
453
+
454
+ log.info(`🔍 [ASN] Processing ASN: ${asnId}`);
455
+
456
+ // =================================================================
457
+ // STEP 2: CREATE FLUENT CLIENT
458
+ // =================================================================
459
+ // Create SDK logger adapter to map Versori log to SDK Logger interface
460
+ const logger = {
461
+ info: (msg: string, ...args: any[]) => log.info(msg, ...args),
462
+ warn: (msg: string, ...args: any[]) => log.warn(msg, ...args),
463
+ error: (msg: string, ...args: any[]) => log.error(msg, ...args),
464
+ debug: (msg: string, ...args: any[]) => log.debug?.(msg, ...args) || log.info(msg, ...args)
465
+ };
466
+
467
+ // Create context for SDK client factory
468
+ const fluentClient = await createClient({ ...ctx, log: logger }); // Auto-detects Versori context
469
+
470
+ log.info('✅ [ASN] Fluent client initialized');
471
+
472
+ // =================================================================
473
+ // STEP 3: VALIDATE CONNECTION
474
+ // =================================================================
475
+ log.info('🔌 [ASN] Validating Fluent connection');
476
+ await fluentClient.validateConnection();
477
+ log.info('✅ [ASN] Connection validated successfully');
478
+
479
+ // =================================================================
480
+ // STEP 4: VALIDATE ASN (PREVENT DUPLICATES)
481
+ // =================================================================
482
+ // Query existing receipts to check if ASN already processed
483
+ // IMPORTANT: Prevents duplicate expected inventory from same ASN
484
+ log.info('🔍 [ASN] Checking for duplicate ASN');
485
+ const duplicateCheckQuery = `
486
+ query CheckExistingReceipt($ref: String!) {
487
+ inventoryQuantities(ref: $ref, first: 1) {
488
+ edges {
489
+ node {
490
+ id
491
+ ref
492
+ status
493
+ }
494
+ }
495
+ }
496
+ }`;
497
+
498
+ const duplicateCheck = await fluentClient.graphql({
499
+ query: duplicateCheckQuery,
500
+ variables: {
501
+ ref: `ASN-${asnId}`, // Use ASN ID as ref
502
+ },
503
+ });
504
+
505
+ const existingReceipt = duplicateCheck.data?.inventoryQuantities?.edges?.[0]?.node;
506
+
507
+ if (existingReceipt) {
508
+ log.warn('⚠️ [ASN] Duplicate detected - ASN already processed', {
509
+ asnId,
510
+ existingReceiptId: existingReceipt.id,
511
+ existingStatus: existingReceipt.status,
512
+ });
513
+
514
+ return {
515
+ success: false,
516
+ message: 'ASN already processed (duplicate)',
517
+ asnId,
518
+ existingReceiptId: existingReceipt.id,
519
+ timestamp: new Date().toISOString(),
520
+ duration: `${Date.now() - startTime}ms`,
521
+ };
522
+ }
523
+
524
+ // =================================================================
525
+ // STEP 5: SAVE RAW ASN FOR AUDIT TRAIL (KV Storage - Versori-compatible)
526
+ // =================================================================
527
+ log.info('Saving raw payload for audit trail');
528
+ try {
529
+ const kv = openKv(':project:');
530
+ const timestamp = new Date().toISOString();
531
+ const auditKey = ['asn', 'audit', asnId, timestamp];
532
+
533
+ // Save original payload
534
+ await kv.set([...auditKey, 'asn-raw'], asnData);
535
+
536
+ log.info('Raw ASN saved to KV storage', { asnId, timestamp });
537
+ } catch (kvError: any) {
538
+ log.warn('Failed to save ASN to KV storage', { error: kvError.message });
539
+ }
540
+
541
+ // =================================================================
542
+ // STEP 6: MAP ASN TO FLUENT EXPECTED RECEIPT FORMAT
543
+ // =================================================================
544
+ log.info('🗺️ [ASN] Starting ASN mapping');
545
+ const mappingStartTime = Date.now();
546
+
547
+ const mapper = new GraphQLMutationMapper(
548
+ asnMappingConfig as any,
549
+ logger,
550
+ { fluentClient: fluentClient as any }
551
+ );
552
+
553
+ // Apply mapping with custom resolvers
554
+ // Context includes fluentClient for API calls (SKU validation, etc.)
555
+ // Returns MapWithNodesResult with query auto-generated!
556
+ const mappingResult = await mapper.mapWithNodes(asnData, asnResolvers, {
557
+ fluentClient: fluentClient as any,
558
+ asnId,
559
+ config: {
560
+ retailerId: process.env.RETAILER_ID || '1',
561
+ defaultLocation: process.env.DEFAULT_RECEIVING_LOCATION || 'DC-RECEIVING',
562
+ },
563
+ helpers: {
564
+ fluentClient: fluentClient as any,
565
+ },
566
+ });
567
+
568
+ if (!mappingResult.success) {
569
+ log.error('❌ [ASN] Mapping failed', {
570
+ errors: mappingResult.errors,
571
+ recommendation: 'Check mapping configuration JSON and verify source paths match incoming ASN structure'
572
+ });
573
+ throw new Error(`ASN mapping failed: ${mappingResult.errors?.join(', ')}`);
574
+ }
575
+
576
+ log.info('✅ [ASN] Mapping successful', {
577
+ asnId,
578
+ itemCount: mappingResult.data?.items?.length || 0,
579
+ duration: `${Date.now() - mappingStartTime}ms`,
580
+ });
581
+
582
+ // Save mapped data to KV storage
583
+ try {
584
+ const kv = openKv(':project:');
585
+ const timestamp = new Date().toISOString();
586
+ const auditKey = ['asn', 'audit', asnId, timestamp, 'fluent-mapped'];
587
+ await kv.set(auditKey, mappingResult.data);
588
+ } catch (kvError: any) {
589
+ log.warn('Failed to save mapped data to KV', { error: kvError.message });
590
+ }
591
+
592
+ // =================================================================
593
+ // STEP 7: CREATE EXPECTED RECEIPT IN FLUENT
594
+ // =================================================================
595
+ log.info('🚀 [ASN] Creating expected inventory receipt in Fluent');
596
+ const mutationStartTime = Date.now();
597
+
598
+ // mapWithNodes() auto-generates query - no need to call buildMutation()!
599
+ // Execute mutation (use result.variables for GraphQL execution)
600
+ const receiptResult = await fluentClient.graphql({
601
+ query: mappingResult.query,
602
+ variables: mappingResult.variables // ✅ Use variables (wrapped if fields pattern)
603
+ });
604
+
605
+ if (receiptResult.errors) {
606
+ log.error('❌ [ASN] Receipt creation failed', {
607
+ errors: receiptResult.errors,
608
+ recommendation: 'Check GraphQL mutation structure and field types',
609
+ });
610
+ throw new Error(`Receipt creation failed: ${JSON.stringify(receiptResult.errors)}`);
611
+ }
612
+
613
+ const createdReceipt = receiptResult.data?.createInventoryQuantity;
614
+
615
+ log.info('✅ [ASN] Expected receipt created successfully', {
616
+ receiptId: createdReceipt?.id,
617
+ asnId,
618
+ duration: `${Date.now() - mutationStartTime}ms`,
619
+ });
620
+
621
+ // Save Fluent response to KV storage
622
+ try {
623
+ const kv = openKv(':project:');
624
+ const timestamp = new Date().toISOString();
625
+ const auditKey = ['asn', 'audit', asnId, timestamp, 'fluent-response'];
626
+ await kv.set(auditKey, receiptResult);
627
+ } catch (kvError: any) {
628
+ log.warn('Failed to save Fluent response to KV', { error: kvError.message });
629
+ }
630
+
631
+ // =================================================================
632
+ // STEP 8: RETURN SUCCESS RESPONSE TO 3PL
633
+ // =================================================================
634
+ const totalDuration = Date.now() - startTime;
635
+ log.info('🎉 [ASN] Processing completed successfully', {
636
+ asnId,
637
+ totalDuration: `${totalDuration}ms`,
638
+ });
639
+
640
+ return {
641
+ success: true,
642
+ message: 'ASN processed successfully',
643
+ data: {
644
+ asnId,
645
+ receiptId: createdReceipt?.id,
646
+ receiptRef: createdReceipt?.ref,
647
+ itemCount: mappingResult.data?.items?.length || 0,
648
+ expectedDate: mappingResult.data?.expectedOn,
649
+ timestamp: new Date().toISOString(),
650
+ duration: `${totalDuration}ms`,
651
+ },
652
+ };
653
+ } catch (error: any) {
654
+ // ? Enhanced: Error logging with recommendations
655
+ const totalDuration = Date.now() - startTime;
656
+ const errorDetails = {
657
+ message: error instanceof Error ? error.message : String(error),
658
+ stack: error instanceof Error ? error.stack : undefined,
659
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
660
+ duration: `${totalDuration}ms`,
661
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
662
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
663
+ : error.message?.includes('mapping') || error.message?.includes('field')
664
+ ? 'Check mapping configuration JSON and verify source paths match incoming ASN structure'
665
+ : error.message?.includes('connection') || error.message?.includes('timeout')
666
+ ? 'Check network connectivity and Fluent Commerce API availability'
667
+ : error.message?.includes('validation') || error.message?.includes('required')
668
+ ? 'Ensure all required fields are present in the ASN webhook payload'
669
+ : error.message?.includes('duplicate') || error.message?.includes('already processed')
670
+ ? 'ASN may have been processed already - check existing receipts'
671
+ : 'Review error details and check ASN payload structure',
672
+ };
673
+ log.error('❌ [ASN] Processing failed', errorDetails);
674
+ throw error; // Let response handler catch it
675
+ }
676
+ });
677
+
678
+ /**
679
+ * Manual test endpoint - Upload ASN file for testing
680
+ */
681
+ export const manualAsnTest = webhook('manual-asn-test', {
682
+ response: {
683
+ mode: 'sync',
684
+ onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
685
+ status: 200,
686
+ headers: { 'Content-Type': 'application/json' }
687
+ }),
688
+ onError: (ctx, error) => new Response(JSON.stringify({
689
+ success: false,
690
+ error: error.message,
691
+ recommendation: 'Check that test ASN file exists at expected path'
692
+ }), {
693
+ status: 400,
694
+ headers: { 'Content-Type': 'application/json' }
695
+ })
696
+ }
697
+ }, async (ctx) => {
698
+ const { log } = ctx;
699
+ const startTime = Date.now();
700
+
701
+ log.info('🧪 [TEST] Manual ASN test triggered');
702
+
703
+ // Load test ASN data
704
+ const testAsnPath = path.join(__dirname, '../../data/test-asn-sample.json');
705
+ if (!fs.existsSync(testAsnPath)) {
706
+ log.error('❌ [TEST] Test ASN file not found', { expectedPath: testAsnPath });
707
+ return {
708
+ success: false,
709
+ error: 'Test ASN file not found',
710
+ expectedPath: testAsnPath,
711
+ recommendation: 'Create test-asn-sample.json in the data/ directory',
712
+ };
713
+ }
714
+
715
+ const testAsnData = JSON.parse(fs.readFileSync(testAsnPath, 'utf-8'));
716
+
717
+ log.info('✅ [TEST] Test ASN loaded', {
718
+ asnId: testAsnData.shipmentId,
719
+ duration: `${Date.now() - startTime}ms`,
720
+ });
721
+
722
+ // Note: In production, you would trigger the main workflow via HTTP call
723
+ // This is a simplified test endpoint for development
724
+ return {
725
+ success: true,
726
+ message: 'Test ASN loaded - trigger processAsnWebhook endpoint to process',
727
+ asnId: testAsnData.shipmentId,
728
+ testDataPath: testAsnPath,
729
+ duration: `${Date.now() - startTime}ms`,
730
+ };
731
+ });
732
+
733
+ /**
734
+ * Health check endpoint
735
+ */
736
+ export const healthCheck = webhook('health-check', {
737
+ response: {
738
+ mode: 'sync',
739
+ onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
740
+ status: 200,
741
+ headers: { 'Content-Type': 'application/json' }
742
+ })
743
+ }
744
+ }, async (ctx) => {
745
+ const { log } = ctx;
746
+ log.info('💚 [HEALTH] Health check requested');
747
+
748
+ return {
749
+ success: true,
750
+ service: 'ASN Processing',
751
+ status: 'healthy',
752
+ timestamp: new Date().toISOString(),
753
+ };
754
+ });
755
+ ```
756
+
757
+ ### 2. Mapping Configuration: `mappings/asn-to-fluent-receipt.json`
758
+
759
+ ```json
760
+ {
761
+ "direction": "ingest",
762
+ "sourceFormat": "json",
763
+ "mutation": "createInventoryQuantity",
764
+ "fields": {
765
+ "ref": {
766
+ "resolver": "custom.generateReceiptRef",
767
+ "comment": "Generate unique receipt ref from ASN ID (REQUIRED)"
768
+ },
769
+ "locationRef": {
770
+ "source": "destination.location_code",
771
+ "resolver": "custom.normalizeLocationRef",
772
+ "comment": "Receiving location (REQUIRED)"
773
+ },
774
+ "type": {
775
+ "value": "EXPECTED",
776
+ "comment": "Expected inventory type (REQUIRED)"
777
+ },
778
+ "status": {
779
+ "value": "EXPECTED",
780
+ "comment": "Status is EXPECTED until physically received (REQUIRED)"
781
+ },
782
+ "expectedOn": {
783
+ "resolver": "custom.calculateExpectedDate",
784
+ "comment": "Calculate based on ship date + carrier transit time (REQUIRED)"
785
+ },
786
+ "carrier": {
787
+ "source": "shipment.carrier.name",
788
+ "resolver": "sdk.trim",
789
+ "comment": "Shipping carrier name"
790
+ },
791
+ "trackingNumber": {
792
+ "source": "shipment.tracking_number",
793
+ "resolver": "sdk.trim",
794
+ "comment": "Carrier tracking number"
795
+ },
796
+ "retailer.id": {
797
+ "resolver": "custom.getRetailerId",
798
+ "comment": "Retailer ID from config (REQUIRED)"
799
+ },
800
+ "items": {
801
+ "source": "shipment.items",
802
+ "isArray": true,
803
+ "comment": "Line items in the shipment",
804
+ "fields": {
805
+ "skuRef": {
806
+ "source": "$.sku",
807
+ "resolver": "custom.validateAndNormalizeSku",
808
+ "comment": "Product SKU - must exist in Fluent (REQUIRED)"
809
+ },
810
+ "qty": {
811
+ "source": "$.quantity",
812
+ "resolver": "sdk.parseInt",
813
+ "comment": "Expected quantity (REQUIRED)"
814
+ },
815
+ "lotNumber": {
816
+ "source": "$.lot_number",
817
+ "required": false,
818
+ "comment": "Lot/batch number if applicable"
819
+ },
820
+ "serialNumbers": {
821
+ "source": "$.serial_numbers",
822
+ "isArray": true,
823
+ "required": false,
824
+ "comment": "Serial numbers for serialized items"
825
+ },
826
+ "expiryDate": {
827
+ "source": "$.expiry_date",
828
+ "resolver": "sdk.formatDate",
829
+ "required": false,
830
+ "comment": "Expiration date for perishable goods"
831
+ },
832
+ "containerRef": {
833
+ "source": "$.container_id",
834
+ "required": false,
835
+ "comment": "Container/pallet ID for tracking"
836
+ }
837
+ }
838
+ },
839
+ "containers": {
840
+ "source": "shipment.containers",
841
+ "isArray": true,
842
+ "required": false,
843
+ "comment": "Container/pallet tracking information",
844
+ "fields": {
845
+ "containerRef": {
846
+ "source": "$.container_id",
847
+ "resolver": "sdk.toString"
848
+ },
849
+ "containerType": {
850
+ "source": "$.type",
851
+ "resolver": "sdk.uppercase",
852
+ "comment": "PALLET, CARTON, TOTE, etc."
853
+ },
854
+ "weight": {
855
+ "source": "$.weight",
856
+ "resolver": "sdk.parseFloat"
857
+ },
858
+ "weightUnit": {
859
+ "source": "$.weight_unit",
860
+ "defaultValue": "LBS"
861
+ },
862
+ "dimensions": {
863
+ "resolver": "custom.formatDimensions",
864
+ "comment": "Format as length x width x height"
865
+ }
866
+ }
867
+ },
868
+ "attributes": {
869
+ "resolver": "custom.buildReceiptAttributes",
870
+ "comment": "Custom attributes for audit trail and tracking"
871
+ }
872
+ }
873
+ }
874
+ ```
875
+
876
+ ### 3. Custom Resolvers: `src/resolvers/asn-resolvers.ts`
877
+
878
+ ```typescript
879
+ /**
880
+ * ASN Processing Custom Resolvers
881
+ */
882
+ import type { ResolverMap } from './types';
883
+
884
+ export const asnResolvers: ResolverMap = {
885
+ /**
886
+ * Generate unique receipt reference from ASN ID
887
+ */
888
+ 'custom.generateReceiptRef': (value: any, data: any, config: any, helpers: any): string => {
889
+ const asnId = data.shipmentId || data.shipment_id || data.ASN?.id || 'UNKNOWN';
890
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
891
+ return `ASN-${asnId}-${timestamp}`;
892
+ },
893
+
894
+ /**
895
+ * Normalize location reference
896
+ * Handles different location code formats from various 3PLs
897
+ */
898
+ 'custom.normalizeLocationRef': (value: any, data: any, config: any, helpers: any): string => {
899
+ if (!value) {
900
+ return config.defaultLocation || 'DC-RECEIVING';
901
+ }
902
+
903
+ // Normalize format: uppercase, replace spaces with hyphens
904
+ return String(value)
905
+ .toUpperCase()
906
+ .trim()
907
+ .replace(/\s+/g, '-');
908
+ },
909
+
910
+ /**
911
+ * Calculate expected arrival date
912
+ * Based on ship date + carrier transit time
913
+ */
914
+ 'custom.calculateExpectedDate': (value: any, data: any, config: any, helpers: any): string => {
915
+ const shipDate = data.shipment?.ship_date || data.shipDate;
916
+ const carrier = data.shipment?.carrier?.name || data.carrier;
917
+
918
+ // Default transit times by carrier (in days)
919
+ const transitTimes: Record<string, number> = {
920
+ 'FEDEX_GROUND': 3,
921
+ 'FEDEX_EXPRESS': 1,
922
+ 'UPS_GROUND': 3,
923
+ 'UPS_NEXT_DAY': 1,
924
+ 'USPS': 5,
925
+ 'DHL': 2,
926
+ 'FREIGHT': 7,
927
+ 'DEFAULT': 3,
928
+ };
929
+
930
+ // Get transit time
931
+ const carrierKey = String(carrier).toUpperCase().replace(/\s+/g, '_');
932
+ const transitDays = transitTimes[carrierKey] || transitTimes.DEFAULT;
933
+
934
+ // Calculate expected date
935
+ const shipDateObj = shipDate ? new Date(shipDate) : new Date();
936
+ shipDateObj.setDate(shipDateObj.getDate() + transitDays);
937
+
938
+ return shipDateObj.toISOString();
939
+ },
940
+
941
+ /**
942
+ * Validate and normalize SKU
943
+ * Ensures SKU exists in Fluent Commerce (ASYNC)
944
+ */
945
+ 'custom.validateAndNormalizeSku': async (
946
+ value: any,
947
+ data: any,
948
+ config: any,
949
+ helpers: any
950
+ ): Promise<string> => {
951
+ const sku = String(value).trim().toUpperCase();
952
+
953
+ if (!helpers.fluentClient) {
954
+ // If no client, just return normalized SKU
955
+ return sku;
956
+ }
957
+
958
+ // Query Fluent to validate SKU exists
959
+ const query = `
960
+ query ValidateSku($skuRef: [String!]) {
961
+ products(ref: $skuRef, first: 1) {
962
+ edges {
963
+ node {
964
+ id
965
+ ref
966
+ }
967
+ }
968
+ }
969
+ }`;
970
+
971
+ try {
972
+ const result = await helpers.fluentClient.graphql({
973
+ query,
974
+ variables: { skuRef: [sku] },
975
+ });
976
+
977
+ const product = result.data?.products?.edges?.[0]?.node;
978
+
979
+ if (!product) {
980
+ helpers.logger?.warn('SKU not found in Fluent Commerce', { sku });
981
+ // Option 1: Throw error to halt processing
982
+ // throw new Error(`SKU not found: ${sku}`);
983
+
984
+ // Option 2: Return SKU anyway (let Fluent validation handle it)
985
+ return sku;
986
+ }
987
+
988
+ helpers.logger?.debug('SKU validated successfully', { sku, productId: product.id });
989
+ return sku;
990
+ } catch (error: any) {
991
+ helpers.logger?.error('SKU validation failed', { sku, error: error.message });
992
+ return sku; // Return SKU anyway, let Fluent handle validation
993
+ }
994
+ },
995
+
996
+ /**
997
+ * Get retailer ID from configuration
998
+ */
999
+ 'custom.getRetailerId': (value: any, data: any, config: any, helpers: any): string => {
1000
+ return config.retailerId || process.env.RETAILER_ID || '1';
1001
+ },
1002
+
1003
+ /**
1004
+ * Format dimensions as string
1005
+ */
1006
+ 'custom.formatDimensions': (value: any, data: any, config: any, helpers: any): string => {
1007
+ const container = data; // Current container object
1008
+ const length = container.length || 0;
1009
+ const width = container.width || 0;
1010
+ const height = container.height || 0;
1011
+ const unit = container.dimension_unit || 'IN';
1012
+
1013
+ if (!length && !width && !height) {
1014
+ return '';
1015
+ }
1016
+
1017
+ return `${length} x ${width} x ${height} ${unit}`;
1018
+ },
1019
+
1020
+ /**
1021
+ * Build receipt attributes for audit trail
1022
+ */
1023
+ 'custom.buildReceiptAttributes': (
1024
+ value: any,
1025
+ data: any,
1026
+ config: any,
1027
+ helpers: any
1028
+ ): Array<{ name: string; type: string; value: any }> => {
1029
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1030
+
1031
+ // Add ASN metadata
1032
+ attributes.push({
1033
+ name: 'asn_id',
1034
+ type: 'STRING',
1035
+ value: config.asnId || data.shipmentId || 'UNKNOWN',
1036
+ });
1037
+
1038
+ attributes.push({
1039
+ name: 'asn_received_date',
1040
+ type: 'STRING',
1041
+ value: new Date().toISOString(),
1042
+ });
1043
+
1044
+ // Add carrier tracking
1045
+ if (data.shipment?.tracking_number) {
1046
+ attributes.push({
1047
+ name: 'tracking_number',
1048
+ type: 'STRING',
1049
+ value: data.shipment.tracking_number,
1050
+ });
1051
+ }
1052
+
1053
+ // Add origin information
1054
+ if (data.shipment?.origin) {
1055
+ attributes.push({
1056
+ name: 'origin_location',
1057
+ type: 'STRING',
1058
+ value: JSON.stringify(data.shipment.origin),
1059
+ });
1060
+ }
1061
+
1062
+ // Add PO number if present
1063
+ if (data.purchase_order_number) {
1064
+ attributes.push({
1065
+ name: 'po_number',
1066
+ type: 'STRING',
1067
+ value: data.purchase_order_number,
1068
+ });
1069
+ }
1070
+
1071
+ // Cross-dock flag
1072
+ if (data.shipment?.is_crossdock) {
1073
+ attributes.push({
1074
+ name: 'is_crossdock',
1075
+ type: 'BOOLEAN',
1076
+ value: 'true',
1077
+ });
1078
+ }
1079
+
1080
+ return attributes;
1081
+ },
1082
+ };
1083
+ ```
1084
+
1085
+ ### 4. Sample ASN Test Data: `data/test-asn-sample.json`
1086
+
1087
+ ```json
1088
+ {
1089
+ "shipmentId": "ASN-20250117-001",
1090
+ "purchase_order_number": "PO-12345",
1091
+ "shipment": {
1092
+ "ship_date": "2025-01-17T10:00:00Z",
1093
+ "carrier": {
1094
+ "name": "FEDEX_GROUND",
1095
+ "scac": "FXFE"
1096
+ },
1097
+ "tracking_number": "123456789012",
1098
+ "origin": {
1099
+ "name": "Acme Distribution Center",
1100
+ "address": "123 Warehouse Dr",
1101
+ "city": "Memphis",
1102
+ "state": "TN",
1103
+ "zip": "38101"
1104
+ },
1105
+ "is_crossdock": false,
1106
+ "items": [
1107
+ {
1108
+ "sku": "ACME-WIDGET-100",
1109
+ "quantity": 50,
1110
+ "lot_number": "LOT20250115",
1111
+ "container_id": "PALLET-001"
1112
+ },
1113
+ {
1114
+ "sku": "ACME-GADGET-200",
1115
+ "quantity": 100,
1116
+ "container_id": "PALLET-001"
1117
+ },
1118
+ {
1119
+ "sku": "ACME-TOOL-300",
1120
+ "quantity": 25,
1121
+ "lot_number": "LOT20250110",
1122
+ "expiry_date": "2026-01-10T00:00:00Z",
1123
+ "container_id": "PALLET-002"
1124
+ }
1125
+ ],
1126
+ "containers": [
1127
+ {
1128
+ "container_id": "PALLET-001",
1129
+ "type": "PALLET",
1130
+ "weight": 250.5,
1131
+ "weight_unit": "LBS",
1132
+ "length": 48,
1133
+ "width": 40,
1134
+ "height": 60,
1135
+ "dimension_unit": "IN"
1136
+ },
1137
+ {
1138
+ "container_id": "PALLET-002",
1139
+ "type": "PALLET",
1140
+ "weight": 180.0,
1141
+ "weight_unit": "LBS",
1142
+ "length": 48,
1143
+ "width": 40,
1144
+ "height": 48,
1145
+ "dimension_unit": "IN"
1146
+ }
1147
+ ]
1148
+ },
1149
+ "destination": {
1150
+ "location_code": "DC-01-RECEIVING",
1151
+ "name": "Distribution Center 01",
1152
+ "address": "789 Commerce Pkwy",
1153
+ "city": "Atlanta",
1154
+ "state": "GA",
1155
+ "zip": "30301"
1156
+ }
1157
+ }
1158
+ ```
1159
+
1160
+ ### 5. Package Configuration: `package.json`
1161
+
1162
+ ```json
1163
+ {
1164
+ "name": "asn-processing-connector",
1165
+ "version": "1.0.0",
1166
+ "description": "ASN/Purchase Order processing connector for Acme 3PL integration",
1167
+ "versori": {
1168
+ "workflows": "./workflows/asn-receipt.ts"
1169
+ },
1170
+ "dependencies": {
1171
+ "@fluentcommerce/fc-connect-sdk": "^0.1.39",
1172
+ "@versori/run": "latest"
1173
+ },
1174
+ "devDependencies": {
1175
+ "@types/node": "^20.0.0",
1176
+ "typescript": "^5.0.0"
1177
+ },
1178
+ "scripts": {
1179
+ "deploy": "versori deploy",
1180
+ "logs": "versori logs",
1181
+ "test": "node -r ts-node/register workflows/asn-receipt.ts"
1182
+ }
1183
+ }
1184
+ ```
1185
+
1186
+ ---
1187
+
1188
+ ## Key Patterns Explained
1189
+
1190
+ ### Pattern 1: ASN Duplicate Prevention
1191
+
1192
+ **Check for Existing Receipt Before Creating:**
1193
+
1194
+ ```typescript
1195
+ // Query existing receipts using ASN ID as ref
1196
+ const duplicateCheckQuery = `
1197
+ query CheckExistingReceipt($ref: String!) {
1198
+ inventoryQuantities(ref: $ref, first: 1) {
1199
+ edges {
1200
+ node {
1201
+ id
1202
+ ref
1203
+ status
1204
+ }
1205
+ }
1206
+ }
1207
+ }`;
1208
+
1209
+ const duplicateCheck = await fluentClient.graphql({
1210
+ query: duplicateCheckQuery,
1211
+ variables: {
1212
+ ref: `ASN-${asnId}`, // Unique ref from ASN shipment ID
1213
+ },
1214
+ });
1215
+
1216
+ const existingReceipt = duplicateCheck.data?.inventoryQuantities?.edges?.[0]?.node;
1217
+
1218
+ if (existingReceipt) {
1219
+ // ASN already processed - return early
1220
+ return {
1221
+ status: 200,
1222
+ body: {
1223
+ success: false,
1224
+ message: 'ASN already processed (duplicate)',
1225
+ existingReceiptId: existingReceipt.id,
1226
+ },
1227
+ };
1228
+ }
1229
+ ```
1230
+
1231
+ **Why this matters**: 3PLs may send duplicate ASN notifications due to:
1232
+ - Network retries
1233
+ - System glitches
1234
+ - Manual re-sends
1235
+ - Webhook replay
1236
+
1237
+ **Without duplicate prevention**: Creates duplicate expected inventory, causing:
1238
+ - Inflated ATP calculations
1239
+ - Incorrect stock levels
1240
+ - Failed physical receipts (already expected)
1241
+
1242
+ **Best practices**:
1243
+ - Always use ASN shipment ID as part of receipt ref
1244
+ - Query before creating
1245
+ - Log duplicate attempts for monitoring
1246
+
1247
+ ### Pattern 2: Expected Date Calculation
1248
+
1249
+ **Calculate Arrival Date Based on Carrier Transit Time:**
1250
+
1251
+ ```typescript
1252
+ 'custom.calculateExpectedDate': (value, data, config, helpers) => {
1253
+ const shipDate = data.shipment?.ship_date || data.shipDate;
1254
+ const carrier = data.shipment?.carrier?.name || data.carrier;
1255
+
1256
+ // Transit time lookup by carrier
1257
+ const transitTimes = {
1258
+ 'FEDEX_GROUND': 3,
1259
+ 'FEDEX_EXPRESS': 1,
1260
+ 'UPS_GROUND': 3,
1261
+ 'UPS_NEXT_DAY': 1,
1262
+ 'USPS': 5,
1263
+ 'DHL': 2,
1264
+ 'FREIGHT': 7,
1265
+ 'DEFAULT': 3,
1266
+ };
1267
+
1268
+ const carrierKey = String(carrier).toUpperCase().replace(/\s+/g, '_');
1269
+ const transitDays = transitTimes[carrierKey] || transitTimes.DEFAULT;
1270
+
1271
+ // Add transit days to ship date
1272
+ const shipDateObj = shipDate ? new Date(shipDate) : new Date();
1273
+ shipDateObj.setDate(shipDateObj.getDate() + transitDays);
1274
+
1275
+ return shipDateObj.toISOString();
1276
+ };
1277
+ ```
1278
+
1279
+ **Why this matters**: Expected date drives:
1280
+ - ATP calculations (Available To Promise)
1281
+ - Allocation timing
1282
+ - Customer promise dates
1283
+ - Warehouse receiving schedules
1284
+
1285
+ **Improvements for production:**
1286
+
1287
+ ```typescript
1288
+ // Add business days logic (skip weekends)
1289
+ function addBusinessDays(date: Date, days: number): Date {
1290
+ let result = new Date(date);
1291
+ let addedDays = 0;
1292
+
1293
+ while (addedDays < days) {
1294
+ result.setDate(result.getDate() + 1);
1295
+ // Skip weekends (0 = Sunday, 6 = Saturday)
1296
+ if (result.getDay() !== 0 && result.getDay() !== 6) {
1297
+ addedDays++;
1298
+ }
1299
+ }
1300
+
1301
+ return result;
1302
+ }
1303
+
1304
+ // Add holiday logic
1305
+ const holidays = ['2025-01-01', '2025-07-04', '2025-12-25'];
1306
+
1307
+ function isHoliday(date: Date): boolean {
1308
+ const dateStr = date.toISOString().substring(0, 10);
1309
+ return holidays.includes(dateStr);
1310
+ }
1311
+ ```
1312
+
1313
+ ### Pattern 3: SKU Validation (Async Resolver)
1314
+
1315
+ **Validate SKU Exists in Fluent Before Creating Receipt:**
1316
+
1317
+ ```typescript
1318
+ 'custom.validateAndNormalizeSku': async (value, data, config, helpers) => {
1319
+ const sku = String(value).trim().toUpperCase();
1320
+
1321
+ // Query Fluent to check if SKU exists
1322
+ const query = `
1323
+ query ValidateSku($skuRef: [String!]) {
1324
+ products(ref: $skuRef, first: 1) {
1325
+ edges {
1326
+ node {
1327
+ id
1328
+ ref
1329
+ }
1330
+ }
1331
+ }
1332
+ }
1333
+ `;
1334
+
1335
+ const result = await helpers.fluentClient.graphql({
1336
+ query,
1337
+ variables: { skuRef: [sku] },
1338
+ });
1339
+
1340
+ const product = result.data?.products?.edges?.[0]?.node;
1341
+
1342
+ if (!product) {
1343
+ // SKU not found - options:
1344
+ // 1. Throw error (halt processing)
1345
+ throw new Error(`SKU not found: ${sku}`);
1346
+
1347
+ // 2. Log warning and continue (let Fluent handle validation)
1348
+ helpers.logger?.warn('SKU not found', { sku });
1349
+ return sku;
1350
+ }
1351
+
1352
+ return sku;
1353
+ };
1354
+ ```
1355
+
1356
+ **When to use SKU validation**:
1357
+ - ✅ **3PL sends ASN before products are created** (prevents invalid receipts)
1358
+ - ✅ **SKU format differs between systems** (can normalize)
1359
+ - ✅ **Need early warning** (alert on unknown SKUs)
1360
+
1361
+ **When to skip**:
1362
+ - ❌ **High volume** (100+ SKUs per ASN = slow)
1363
+ - ❌ **Products always exist** (pre-synced)
1364
+ - ❌ **Performance critical** (let Fluent validation handle it)
1365
+
1366
+ **Performance optimization**:
1367
+
1368
+ ```typescript
1369
+ // Cache SKU validation results in resolver context
1370
+ const skuCache = new Map<string, boolean>();
1371
+
1372
+ if (skuCache.has(sku)) {
1373
+ return sku; // Already validated
1374
+ }
1375
+
1376
+ const isValid = await validateSku(sku);
1377
+ skuCache.set(sku, isValid);
1378
+ ```
1379
+
1380
+ ### Pattern 4: Container/Pallet Tracking
1381
+
1382
+ **Track Containers for Efficient Receiving:**
1383
+
1384
+ ```json
1385
+ {
1386
+ "containers": {
1387
+ "source": "shipment.containers",
1388
+ "isArray": true,
1389
+ "fields": {
1390
+ "containerRef": {
1391
+ "source": "$.container_id"
1392
+ },
1393
+ "containerType": {
1394
+ "source": "$.type",
1395
+ "comment": "PALLET, CARTON, TOTE"
1396
+ },
1397
+ "weight": { "source": "$.weight" },
1398
+ "dimensions": {
1399
+ "resolver": "custom.formatDimensions"
1400
+ }
1401
+ }
1402
+ }
1403
+ }
1404
+ ```
1405
+
1406
+ **Why container tracking matters**:
1407
+ - **Warehouse efficiency**: Receive entire pallets at once
1408
+ - **Put-away optimization**: Route containers to correct zones
1409
+ - **Cross-docking**: Direct routing without storage
1410
+ - **Audit trail**: Track physical container movement
1411
+
1412
+ **Real-world example**:
1413
+
1414
+ ```
1415
+ ASN contains:
1416
+ - PALLET-001: 50x WIDGET + 100x GADGET
1417
+ - PALLET-002: 25x TOOL (refrigerated)
1418
+
1419
+ Warehouse receives PALLET-001 first:
1420
+ → Scan PALLET-001 barcode
1421
+ → System shows all 2 SKUs on pallet
1422
+ → Receive all at once (no item-by-item scan)
1423
+ → PALLET-002 still expected
1424
+ ```
1425
+
1426
+ ### Pattern 5: Cross-Dock Scenario Handling
1427
+
1428
+ **Handle Direct Fulfillment Without Storage:**
1429
+
1430
+ ```typescript
1431
+ // In ASN data
1432
+ {
1433
+ "shipment": {
1434
+ "is_crossdock": true, // Flag for cross-dock shipment
1435
+ "destination_order_ref": "ORDER-12345"
1436
+ }
1437
+ }
1438
+
1439
+ // Custom resolver for cross-dock logic
1440
+ 'custom.buildReceiptAttributes': (value, data, config, helpers) => {
1441
+ const attributes = [];
1442
+
1443
+ if (data.shipment?.is_crossdock) {
1444
+ attributes.push({
1445
+ name: 'is_crossdock',
1446
+ type: 'BOOLEAN',
1447
+ value: 'true',
1448
+ });
1449
+
1450
+ // Link to destination order
1451
+ if (data.shipment.destination_order_ref) {
1452
+ attributes.push({
1453
+ name: 'crossdock_order_ref',
1454
+ type: 'STRING',
1455
+ value: data.shipment.destination_order_ref,
1456
+ });
1457
+ }
1458
+
1459
+ // Mark for expedited processing
1460
+ attributes.push({
1461
+ name: 'receiving_priority',
1462
+ type: 'STRING',
1463
+ value: 'HIGH',
1464
+ });
1465
+ }
1466
+
1467
+ return attributes;
1468
+ };
1469
+ ```
1470
+
1471
+ **Cross-dock workflow**:
1472
+ 1. **ASN received** with `is_crossdock: true`
1473
+ 2. **Expected receipt created** with crossdock flag
1474
+ 3. **Physical receipt** → immediately allocated to order
1475
+ 4. **Skip put-away** → direct to packing station
1476
+ 5. **Ship within hours** (not days)
1477
+
1478
+ **Benefits**:
1479
+ - Faster order fulfillment (same-day ship)
1480
+ - Reduced warehouse touches
1481
+ - Lower storage costs
1482
+ - Improved customer satisfaction
1483
+
1484
+ ### Pattern 6: XML vs JSON Payload Handling
1485
+
1486
+ **Support Multiple Payload Formats:**
1487
+
1488
+ ```typescript
1489
+ // Detect content type from header
1490
+ const contentType = ctx.activation?.headers?.['content-type'] || 'application/json';
1491
+
1492
+ let asnData: any;
1493
+
1494
+ if (contentType.includes('xml') || contentType.includes('text')) {
1495
+ // Parse XML (EDI 856 format)
1496
+ const xmlParser = new XMLParserService();
1497
+ asnData = await xmlParser.parse(rawPayload);
1498
+ } else {
1499
+ // Assume JSON
1500
+ asnData = rawPayload;
1501
+ }
1502
+ ```
1503
+
1504
+ **Why support both**:
1505
+ - **Enterprise 3PLs**: Often use EDI 856 XML format
1506
+ - **Modern 3PLs**: Use JSON REST APIs
1507
+ - **Flexibility**: Handle both without separate connectors
1508
+
1509
+ **EDI 856 XML example**:
1510
+
1511
+ ```xml
1512
+ <?xml version="1.0"?>
1513
+ <ShipNotice shipment_id="ASN-001">
1514
+ <Shipment>
1515
+ <ShipDate>2025-01-17</ShipDate>
1516
+ <Carrier name="FEDEX_GROUND"/>
1517
+ <Items>
1518
+ <Item>
1519
+ <SKU>WIDGET-100</SKU>
1520
+ <Quantity>50</Quantity>
1521
+ </Item>
1522
+ </Items>
1523
+ </Shipment>
1524
+ </ShipNotice>
1525
+ ```
1526
+
1527
+ **JSON equivalent**:
1528
+
1529
+ ```json
1530
+ {
1531
+ "shipmentId": "ASN-001",
1532
+ "shipment": {
1533
+ "ship_date": "2025-01-17",
1534
+ "carrier": { "name": "FEDEX_GROUND" },
1535
+ "items": [
1536
+ { "sku": "WIDGET-100", "quantity": 50 }
1537
+ ]
1538
+ }
1539
+ }
1540
+ ```
1541
+
1542
+ ---
1543
+
1544
+ ## Testing the Workflow
1545
+
1546
+ ### 1. Create Test ASN File
1547
+
1548
+ Already provided in `data/test-asn-sample.json` above.
1549
+
1550
+ ### 2. Deploy to Versori Platform
1551
+
1552
+ ```bash
1553
+ cd asn-processing-connector
1554
+ npm install
1555
+
1556
+ # Deploy to Versori
1557
+ versori deploy
1558
+
1559
+ # Or if using Versori CLI v2+
1560
+ npx @versori/cli deploy
1561
+ ```
1562
+
1563
+ ### 3. Configure Versori Connections
1564
+
1565
+ In the Versori console:
1566
+
1567
+ 1. Add **Fluent Commerce** connection:
1568
+ - Connection name: `fluent_commerce`
1569
+ - Base URL: `https://api.fluentcommerce.com/graphql`
1570
+ - Auth: OAuth2 (Client Credentials or Password Grant)
1571
+ - Client ID, Client Secret, Username, Password
1572
+
1573
+ 2. Configure activation variables (optional):
1574
+ - `OUTPUT_DIR`: Where to save audit files
1575
+ - `RETAILER_ID`: Fluent retailer ID
1576
+ - `DEFAULT_RECEIVING_LOCATION`: Default location code
1577
+
1578
+ ### 4. Test via Webhook
1579
+
1580
+ ```bash
1581
+ # Get webhook URL from Versori console (usually https://{workspace}.versori.io/{workflow})
1582
+ # Example: https://acme.versori.io/processAsnWebhook
1583
+
1584
+ # Send test ASN via HTTP POST
1585
+ curl -X POST https://acme.versori.io/processAsnWebhook \
1586
+ -H "Content-Type: application/json" \
1587
+ -d @data/test-asn-sample.json
1588
+ ```
1589
+
1590
+ ### 5. Use Manual Test Endpoint
1591
+
1592
+ ```bash
1593
+ # Trigger manual test (loads test-asn-sample.json from filesystem)
1594
+ curl https://acme.versori.io/manualAsnTest
1595
+ ```
1596
+
1597
+ ### 6. Check Health Endpoint
1598
+
1599
+ ```bash
1600
+ # Verify service is running
1601
+ curl https://acme.versori.io/healthCheck
1602
+ ```
1603
+
1604
+ ### 7. Verify in Fluent Commerce
1605
+
1606
+ 1. Log into Fluent Console
1607
+ 2. Navigate to Inventory → Inventory Quantities
1608
+ 3. Filter by status: `EXPECTED`
1609
+ 4. Find receipt with ref `ASN-20250117-001-*`
1610
+ 5. Verify:
1611
+ - Quantity matches ASN
1612
+ - Expected date calculated correctly
1613
+ - Attributes contain ASN metadata
1614
+
1615
+ ### 8. Monitor Logs
1616
+
1617
+ ```bash
1618
+ # View real-time logs in Versori console
1619
+ # Or use Versori CLI
1620
+ versori logs --workflow=processAsnWebhook --tail
1621
+
1622
+ # Check for errors
1623
+ versori logs --workflow=processAsnWebhook --level=error
1624
+ ```
1625
+
1626
+ ---
1627
+
1628
+ ## Common Issues and Solutions
1629
+
1630
+ ### Issue 1: "Duplicate entity" Error from Fluent
1631
+
1632
+ **Symptoms:**
1633
+ - GraphQL error: "Entity with ref already exists"
1634
+ - Second ASN attempt fails
1635
+
1636
+ **Root Cause:**
1637
+ - Receipt ref not unique
1638
+ - Missing timestamp in ref generation
1639
+
1640
+ **Solution:**
1641
+
1642
+ ```typescript
1643
+ // Ensure unique ref with timestamp
1644
+ 'custom.generateReceiptRef': (value, data, config, helpers) => {
1645
+ const asnId = data.shipmentId || 'UNKNOWN';
1646
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
1647
+ const randomSuffix = Math.random().toString(36).substring(7);
1648
+
1649
+ // Format: ASN-{asnId}-{timestamp}-{random}
1650
+ return `ASN-${asnId}-${timestamp}-${randomSuffix}`;
1651
+ };
1652
+ ```
1653
+
1654
+ ### Issue 2: Expected Date in the Past
1655
+
1656
+ **Symptoms:**
1657
+ - Expected date shows yesterday or earlier
1658
+ - ATP calculation incorrect
1659
+
1660
+ **Root Cause:**
1661
+ - Ship date from ASN is in the past
1662
+ - Transit time calculation starts from old date
1663
+
1664
+ **Solution:**
1665
+
1666
+ ```typescript
1667
+ 'custom.calculateExpectedDate': (value, data, config, helpers) => {
1668
+ const shipDate = data.shipment?.ship_date;
1669
+ const carrier = data.shipment?.carrier?.name;
1670
+
1671
+ // Get transit days
1672
+ const transitDays = getTransitDays(carrier);
1673
+
1674
+ // Use ship date or TODAY (whichever is later)
1675
+ const baseDate = shipDate ? new Date(shipDate) : new Date();
1676
+ const today = new Date();
1677
+
1678
+ // If ship date is in the past, use today instead
1679
+ if (baseDate < today) {
1680
+ baseDate.setTime(today.getTime());
1681
+ }
1682
+
1683
+ // Add transit days
1684
+ baseDate.setDate(baseDate.getDate() + transitDays);
1685
+
1686
+ return baseDate.toISOString();
1687
+ };
1688
+ ```
1689
+
1690
+ ### Issue 3: SKU Validation Slows Processing
1691
+
1692
+ **Symptoms:**
1693
+ - Webhook timeouts (>30 seconds)
1694
+ - ASN with 100+ SKUs fails
1695
+
1696
+ **Root Cause:**
1697
+ - Sequential SKU validation queries
1698
+ - No caching
1699
+
1700
+ **Solution:**
1701
+
1702
+ ```typescript
1703
+ // Batch validate all SKUs in single query
1704
+ 'custom.validateAllSkus': async (value, data, config, helpers) => {
1705
+ const allSkus = data.shipment?.items?.map((item: any) => item.sku) || [];
1706
+
1707
+ // Single query for all SKUs
1708
+ const query = `
1709
+ query ValidateSkus($skuRefs: [String!]) {
1710
+ products(ref: $skuRefs, first: 200) {
1711
+ edges {
1712
+ node {
1713
+ id
1714
+ ref
1715
+ }
1716
+ }
1717
+ }
1718
+ }
1719
+ `;
1720
+
1721
+ const result = await helpers.fluentClient.graphql({
1722
+ query,
1723
+ variables: { skuRefs: allSkus },
1724
+ });
1725
+
1726
+ const validSkus = new Set(
1727
+ result.data?.products?.edges?.map((e: any) => e.node.ref) || []
1728
+ );
1729
+
1730
+ // Check for missing SKUs
1731
+ const missingSkus = allSkus.filter(sku => !validSkus.has(sku));
1732
+
1733
+ if (missingSkus.length > 0) {
1734
+ helpers.logger?.warn('Invalid SKUs found', { missingSkus });
1735
+ // Option: throw error or continue
1736
+ }
1737
+
1738
+ return validSkus;
1739
+ };
1740
+ ```
1741
+
1742
+ ### Issue 4: Cross-Dock Flag Not Honored
1743
+
1744
+ **Symptoms:**
1745
+ - Cross-dock inventory goes to storage
1746
+ - Fulfillment delayed
1747
+
1748
+ **Root Cause:**
1749
+ - Warehouse receiving app doesn't check attributes
1750
+ - Missing workflow trigger
1751
+
1752
+ **Solution:**
1753
+
1754
+ **Option 1: Create Fluent Event**
1755
+
1756
+ ```typescript
1757
+ // After creating receipt, send event if cross-dock
1758
+ if (data.shipment?.is_crossdock) {
1759
+ await fluentClient.sendEvent({
1760
+ name: 'InventoryReceiptCrossDock',
1761
+ entityType: 'INVENTORY',
1762
+ entityRef: createdReceipt.ref,
1763
+ data: {
1764
+ destinationOrderRef: data.shipment.destination_order_ref,
1765
+ priority: 'HIGH',
1766
+ },
1767
+ });
1768
+ }
1769
+ ```
1770
+
1771
+ **Option 2: Use Different Location**
1772
+
1773
+ ```typescript
1774
+ // Route cross-dock receipts to special location
1775
+ 'custom.normalizeLocationRef': (value, data, config, helpers) => {
1776
+ if (data.shipment?.is_crossdock) {
1777
+ return 'DC-CROSSDOCK-RECEIVING'; // Special receiving zone
1778
+ }
1779
+ return value || config.defaultLocation;
1780
+ };
1781
+ ```
1782
+
1783
+ ### Issue 5: Container Data Not Appearing in Fluent
1784
+
1785
+ **Symptoms:**
1786
+ - Container/pallet info missing in Fluent
1787
+ - Can't track containers
1788
+
1789
+ **Root Cause:**
1790
+ - Fluent schema doesn't support container arrays directly
1791
+ - Need to use attributes
1792
+
1793
+ **Solution:**
1794
+
1795
+ ```typescript
1796
+ // Store containers in attributes instead of separate field
1797
+ 'custom.buildReceiptAttributes': (value, data, config, helpers) => {
1798
+ const attributes = [];
1799
+
1800
+ // Serialize containers to JSON attribute
1801
+ if (data.shipment?.containers) {
1802
+ attributes.push({
1803
+ name: 'containers',
1804
+ type: 'STRING',
1805
+ value: JSON.stringify(data.shipment.containers),
1806
+ });
1807
+
1808
+ // Also add container count for quick reference
1809
+ attributes.push({
1810
+ name: 'container_count',
1811
+ type: 'INTEGER',
1812
+ value: String(data.shipment.containers.length),
1813
+ });
1814
+ }
1815
+
1816
+ return attributes;
1817
+ };
1818
+ ```
1819
+
1820
+ ---
1821
+
1822
+ ## Related Guides
1823
+
1824
+ - **[Connector Platform Scheduled: Cycle Count Reconciliation](../workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md)** - Reconciliation patterns
1825
+ - **[Universal Mapping Guide](../../../02-CORE-GUIDES/advanced-services/advanced-services-readme.md)** - Field mapping patterns
1826
+ - **[GraphQL Mutation Mapper](../../../02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md)** - Mutation generation
1827
+ - **[XML Parsing Guide](../../../02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md)** - EDI 856 XML handling
1828
+
1829
+ ---
1830
+
1831
+ ## Production Checklist
1832
+
1833
+ Before deploying to production:
1834
+ - [ ] Test with real 3PL ASN payloads (XML and JSON)
1835
+ - [ ] Configure carrier transit times based on real data
1836
+ - [ ] Set up duplicate ASN monitoring/alerting
1837
+ - [ ] Verify SKU validation strategy (batch vs individual)
1838
+ - [ ] Test cross-dock scenario if applicable
1839
+ - [ ] Configure webhook retry policy in 3PL system
1840
+ - [ ] Set up error notifications (email/Slack)
1841
+ - [ ] Document ASN format variations by 3PL
1842
+ - [ ] Test large ASNs (100+ SKUs, 10+ containers)
1843
+ - [ ] Verify expected date business days logic
1844
+
1845
+ ---
1846
+
1847
+ ## Performance Considerations
1848
+
1849
+ **Large ASNs (100+ SKUs)**:
1850
+
1851
+ ```typescript
1852
+ // Process items in chunks to avoid timeouts
1853
+ const CHUNK_SIZE = 50;
1854
+
1855
+ for (let i = 0; i < items.length; i += CHUNK_SIZE) {
1856
+ const chunk = items.slice(i, i + CHUNK_SIZE);
1857
+ await processItemChunk(chunk);
1858
+ }
1859
+ ```
1860
+
1861
+ **High Volume (1000+ ASNs/day)**:
1862
+
1863
+ ```typescript
1864
+ // Use background processing with queue
1865
+ export const processAsnWebhookQueued = webhook('queue-asn', {
1866
+ response: {
1867
+ mode: 'sync',
1868
+ onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
1869
+ status: 202, // Accepted
1870
+ headers: { 'Content-Type': 'application/json' }
1871
+ })
1872
+ }
1873
+ }, async (ctx) => {
1874
+ const { activation } = ctx;
1875
+
1876
+ // Immediately acknowledge receipt
1877
+ const asnId = activation.body.shipmentId;
1878
+
1879
+ // Queue for async processing (implement queueAsnForProcessing function)
1880
+ await queueAsnForProcessing(asnId, activation.body);
1881
+
1882
+ return {
1883
+ success: true,
1884
+ message: 'ASN queued for processing',
1885
+ asnId,
1886
+ };
1887
+ });
1888
+ ```
1889
+
1890
+ ---
1891
+
1892
+ ## Next Steps
1893
+
1894
+ 1. **Add Receipt Confirmation**: Send confirmation back to 3PL when receipt created
1895
+ 2. **Implement Discrepancy Handling**: Compare expected vs actual receipt quantities
1896
+ 3. **Add Serial Number Tracking**: Full serial number lifecycle
1897
+ 4. **Integrate with WMS**: Push ASN to warehouse management system
1898
+ 5. **Build Dashboard**: Real-time ASN status monitoring
1899
+ 6. **Add Carrier Tracking Integration**: Real-time shipment tracking via carrier APIs
1900
+
1901
+ ---
1902
+
1903
+ **Need Help?**
1904
+ - Review SDK documentation: `/docs/readme.md`
1905
+ - Check example connectors: `/connectors/Sample versori connectors/`
1906
+ - Connector platform documentation: Check your platform's docs