@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.56

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