@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,2029 +1,2029 @@
1
- ---
2
- template_id: tpl-webhook-xml-order-ingestion-sfcc
3
- canonical_filename: template-webhook-xml-order-ingestion.md
4
- sdk_version: ^0.1.41
5
- runtime: versori
6
- direction: ingestion
7
- source: webhook-xml-sfcc
8
- destination: fluent-graphql
9
- entity: order
10
- format: xml
11
- logging: versori
12
- status: production-ready
13
- last_updated: 2025-11-13
14
- ---
15
-
16
- # Template: Webhook - SFCC XML Order Ingestion
17
-
18
- **FC Connect SDK Use Case Guide**
19
-
20
- > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
21
- > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
22
-
23
- **Context**: Receive SFCC XML orders via Versori HTTP webhook and create orders in Fluent Commerce using GraphQL mutations.
24
-
25
- **Complexity**: Medium
26
-
27
- **Runtime**: Versori Platform
28
-
29
- **Estimated Lines**: ~500 lines (modular structure)
30
-
31
- ---
32
-
33
- ## STEP 1: Understand This Template
34
-
35
- **What This Template Does:**
36
-
37
- - Versori HTTP webhook endpoint receiving XML order data (SFCC native format)
38
- - XML parsing with automatic Versori XML-to-object conversion (incoming XML)
39
- - GraphQL mutation mapping from SFCC native XML structure to Fluent schema
40
- - Uses ONLY SFCC native fields (orders.order.*) - no Radial XML dependency
41
- - Custom resolvers for data transformation, name parsing, and order type derivation
42
- - Error handling and structured response formatting
43
- - **Response Options**: JSON (default) or XML (with custom Response handler)
44
- - **Sync + Fire-and-Forget Pattern**: Fast webhook response, background processing
45
-
46
- **Key SDK Components:**
47
-
48
- - `createClient()` - Universal client factory (auto-detects Versori context)
49
- - `GraphQLMutationMapper` - XML/JSON → GraphQL mutation mapping
50
- - Native Versori `log` - Use `log` from context
51
- - No XML parsing needed (Versori handles automatically)
52
-
53
- **Entity Type:**
54
-
55
- - **Order** - Fluent entity for order creation
56
- - **GraphQL Mutation** - Uses `createOrder` mutation (not Event API)
57
-
58
- **Critical Patterns:**
59
-
60
- - **XML Response Pattern**: Production-ready XML response handling with proper Content-Type headers
61
- - **Logger Adapter**: Type-safe logging with SDK Logger interface
62
- - **Sync + Fire-and-Forget**: Webhook validates quickly, returns immediately, processes in background
63
- - **External JSON Config**: Mapping configuration in separate JSON file (`config/sfcc-to-fluent-order-mapping.json`)
64
- - **Modular Architecture**: Separate services, workflows, config, types folders
65
- - **Integration Variables Validation**: Fail-fast validation of required configuration
66
- - **Initial Deployment Monitoring**: Temporary full payload logging for troubleshooting
67
- - **SFCC Native Only**: No Radial XML dependency - simpler and more maintainable
68
-
69
- **When to Use This Template:**
70
-
71
- - ✅ SFCC native XML order ingestion (orders.order.* structure)
72
- - ✅ Need fast webhook response (don't wait for order creation)
73
- - ✅ Single order per webhook call
74
- - ✅ BOPIS (Buy Online Pick Up In Store) and standard shipping orders
75
- - ✅ Custom resolvers for complex transformations (name parsing, address normalization)
76
- - ✅ Want simpler template without Radial XML complexity
77
-
78
- **When NOT to Use:**
79
-
80
- - ❌ Bulk order processing (use Batch API or scheduled workflows)
81
- - ❌ Order updates (use GraphQL `updateOrder` mutation)
82
- - ❌ CSV/JSON formats (use appropriate format template)
83
- - ❌ Need synchronous order creation (wait for result before responding)
84
- - ❌ Orders requiring Radial-specific fields (use Radial template instead)
85
-
86
- ---
87
-
88
- ## STEP 2: Implementation Prompt for Claude Code
89
-
90
- **Copy this prompt and send to Claude Code to generate the complete implementation:**
91
-
92
- ```
93
- Create a Versori webhook workflow for SFCC XML order ingestion to Fluent Commerce GraphQL.
94
-
95
- REQUIREMENTS:
96
- 1. Runtime: Versori Platform (HTTP webhook)
97
- 2. Source: SFCC XML order data via HTTP POST webhook
98
- 3. Destination: Fluent Commerce GraphQL API (createOrder mutation)
99
- 4. Format: XML (Versori auto-parses to object)
100
- 5. Entity: Order (GraphQL mutation)
101
-
102
- KEY FEATURES:
103
- - Sync + fire-and-forget pattern (fast webhook response, background processing)
104
- - External JSON mapping configuration (config/sfcc-to-fluent-order-mapping.json)
105
- - Modular architecture (workflows/, services/, config/, types/)
106
- - SFCC native fields ONLY (no Radial XML)
107
- - GraphQLMutationMapper with standard map() method (no nodes)
108
- - Custom resolvers for data transformation
109
- - Audit trail (save input/output files)
110
- - Comprehensive error handling with structured logging
111
-
112
- CRITICAL REQUIREMENTS:
113
- 1. Webhook Mode: response: { mode: 'sync' } with XML onSuccess/onError handlers
114
- 2. Response Format: XML with proper Content-Type headers (production standard for SFCC)
115
- 3. Logger Adapter: Create typed Logger adapter from Versori log
116
- 4. Background Processing: Fire-and-forget pattern (no await on long operations)
117
- 5. Mapping Config: External JSON file (config/sfcc-to-fluent-order-mapping.json)
118
- 6. Custom Resolvers: USE mapWithNodes() when you have custom resolvers (REQUIRED)
119
- 7. Integration Variables: Fail-fast validation of required config (fluentRetailerId)
120
- 8. Modular Structure: Separate services/, config/, types/ folders
121
- 9. Step Documentation: Use visual separator blocks for each workflow step
122
- 10. Error Handling: Structured error responses with recommendations
123
-
124
- SDK METHODS TO USE (v0.1.41+):
125
- - createClient({ ...ctx, log }) - Pass full Versori context
126
- - Logger adapter pattern - Type-safe logging wrapper
127
- - new GraphQLMutationMapper(mappingConfig, logger, { customResolvers, fluentClient: client }) - Initialize mapper with resolvers (RECOMMENDED)
128
- - mapper.mapWithNodes(xmlData, undefined, context) - Map with constructor resolvers (auto-returns query)
129
- - result.query and result.variables - Auto-generated from mapWithNodes()
130
- - client.graphql({ query: result.query, variables: result.variables }) - Execute mutation
131
-
132
- FORBIDDEN PATTERNS:
133
- - ❌ Inline mapping config (use external JSON)
134
- - ❌ await on background processing (use fire-and-forget)
135
- - ❌ Passing log directly to SDK (use Logger adapter)
136
- - ❌ All code in one file (use modular structure)
137
- - ❌ async mode webhook (use sync + fire-and-forget)
138
- - ❌ Radial XML references (use SFCC native only)
139
- - ❌ buildMutation() calls (mapWithNodes() auto-generates query in v0.1.41+)
140
- - ❌ Silent config fallbacks (fail-fast on missing required variables)
141
- ```
142
-
143
- ---
144
-
145
- ## STEP 3: Detailed Flow Documentation
146
-
147
- ### Complete Processing Flow
148
-
149
- ```
150
- ┌─────────────────────────────────────────────────────────────┐
151
- │ 1. WEBHOOK RECEIVED │
152
- │ POST https://{workspace}.versori.run/sfcc-order-create │
153
- │ Content-Type: application/xml │
154
- │ Body: <orders xmlns="..."><order order-no="..."> │
155
- │ <original-order-no>...</original-order-no> │
156
- │ <customer>...</customer> │
157
- │ <product-lineitems>...</product-lineitems> │
158
- │ <shipments>...</shipments> │
159
- │ <totals>...</totals> │
160
- │ <payments>...</payments> │
161
- │ </order></orders> │
162
- └────────────────────┬────────────────────────────────────────┘
163
-
164
-
165
- ┌─────────────────────────────────────────────────────────────┐
166
- │ 2. QUICK VALIDATION (Synchronous, ~10-50ms) │
167
- │ - Check fluent_commerce connection exists │
168
- │ - Validate SFCC XML payload present │
169
- │ - Validate required fields (original-order-no, customer) │
170
- │ - Return HTTP 200 OK immediately │
171
- └────────────────────┬────────────────────────────────────────┘
172
-
173
-
174
- ┌─────────────────────────────────────────────────────────────┐
175
- │ 3. BACKGROUND PROCESSING (Fire-and-Forget) │
176
- │ ┌─────────────────────────────────────────────────────┐ │
177
- │ │ 3a. Initialize Fluent Client │ │
178
- │ │ - createClient({ ...ctx, log }) │ │
179
- │ └─────────────────────────────────────────────────────┘ │
180
- │ ┌─────────────────────────────────────────────────────┐ │
181
- │ │ 3b. Validate SFCC Structure │ │
182
- │ │ - Check required SFCC native fields │ │
183
- │ │ - Validate order structure │ │
184
- │ └─────────────────────────────────────────────────────┘ │
185
- │ ┌─────────────────────────────────────────────────────┐ │
186
- │ │ 3c. Map SFCC Native XML to GraphQL Variables │ │
187
- │ │ - Load mapping config from JSON │ │
188
- │ │ - Use SFCC native fields ONLY (orders.order.*) │ │
189
- │ │ - GraphQLMutationMapper.mapWithNodes() (REQUIRED for custom resolvers)│ │
190
- │ │ - Apply custom resolvers │ │
191
- │ │ - Returns { query, variables } (GraphQLPayload) │ │
192
- │ └─────────────────────────────────────────────────────┘ │
193
- │ ┌─────────────────────────────────────────────────────┐ │
194
- │ │ 3d. Execute GraphQL Mutation │ │
195
- │ │ - client.graphql(payload) - one simple step! │ │
196
- │ └─────────────────────────────────────────────────────┘ │
197
- └─────────────────────────────────────────────────────────────┘
198
- ```
199
-
200
- ### Response Timing
201
-
202
- | Stage | Timing | Blocking |
203
- |-------|--------|----------|
204
- | **Webhook Validation** | ~10-50ms | ✅ Yes (blocks response) |
205
- | **Background Processing** | ~1000-2000ms | ❌ No (fire-and-forget) |
206
- | **Total Response Time** | ~10-50ms | ✅ Fast response |
207
-
208
- **Key Benefit**: Webhook caller receives immediate acknowledgment (~50ms) while order creation happens in background (~1-2s).
209
-
210
- ---
211
-
212
- ## STEP 4: Production Modular Structure
213
-
214
- > **✅ This section shows the COMPLETE production-ready modular structure.**
215
- > All files are shown with proper imports/exports and folder organization.
216
-
217
- ### Complete Project Structure
218
-
219
- ```
220
- sfcc-xml-order-ingestion/
221
- ├── package.json # Dependencies and Versori config
222
- ├── index.ts # Entry point - exports all workflows
223
- └── src/
224
- ├── workflows/
225
- │ └── webhook/
226
- │ └── order-ingestion.ts # Webhook: Receive SFCC XML orders
227
-
228
- ├── services/
229
- │ └── order-processing.service.ts # Shared orchestration logic (reusable)
230
-
231
- ├── resolvers/
232
- │ ├── index.ts # Export all resolvers
233
- │ ├── types.ts # TypeScript interfaces
234
- │ ├── order-resolvers.ts # Order-level resolvers
235
- │ ├── fulfillment-resolvers.ts # Shipment/fulfillment resolvers
236
- │ ├── item-resolvers.ts # Product line item resolvers
237
- │ └── payment-resolvers.ts # Payment resolvers
238
-
239
- ├── config/
240
- │ └── sfcc-to-fluent-order-mapping.json # Mapping configuration (external JSON)
241
-
242
- └── types/
243
- └── order.types.ts # TypeScript interfaces
244
- ```
245
-
246
- **Why This Structure?**
247
-
248
- - ✅ **Clear separation**: Webhook handlers vs business logic
249
- - ✅ **Reusable services**: Order processing logic can be reused
250
- - ✅ **External config**: Mapping changes don't require code changes
251
- - ✅ **Custom resolvers**: Separate file for complex transformations
252
- - ✅ **Type safety**: TypeScript interfaces for better IDE support
253
- - ✅ **Scalable**: Easy to add new webhooks or services
254
-
255
- ---
256
-
257
- ---
258
-
259
- ## CRITICAL: XML Response Architecture (Production Pattern)
260
-
261
- ### Overview
262
-
263
- This template uses **XML responses** as the PRIMARY pattern for SFCC integration. This is the production-proven approach used in real SFCC → Fluent integrations.
264
-
265
- ### How XML Response Handling Works
266
-
267
- ```
268
- ┌─────────────────────────────────────────────────────────────────┐
269
- │ ARCHITECTURE FLOW │
270
- ├─────────────────────────────────────────────────────────────────┤
271
- │ │
272
- │ 1. Workflow Steps Return XML Strings │
273
- │ ↓ ctx.data = "<OrderProcessingResponse>...</>" │
274
- │ │
275
- │ 2. onSuccess Handler Receives Context │
276
- │ ↓ ctx = { data: xmlString, executionId: "..." } │
277
- │ │
278
- │ 3. Handler Wraps in Response Object │
279
- │ ↓ new Response(ctx.data, { headers: {...} }) │
280
- │ │
281
- │ 4. Framework Streams Response Directly │
282
- │ ↓ No JSON encoding - pure XML stream │
283
- │ │
284
- └─────────────────────────────────────────────────────────────────┘
285
- ```
286
-
287
- ### Response Format Examples
288
-
289
- **Success Response:**
290
- ```xml
291
- <?xml version="1.0" encoding="UTF-8"?>
292
- <OrderProcessingResponse>
293
- <Status>success</Status>
294
- <Message>Order successfully processed and created in Fluent Commerce</Message>
295
- <FluentOrderId>6370</FluentOrderId>
296
- <FluentOrderRef>0017326966182_G_postman_test_1760703173226</FluentOrderRef>
297
- <SFCCOrderRef>0017326966182_G_postman_test_1760703173226</SFCCOrderRef>
298
- <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
299
- </OrderProcessingResponse>
300
- ```
301
-
302
- **Error Response:**
303
- ```xml
304
- <?xml version="1.0" encoding="UTF-8"?>
305
- <OrderProcessingResponse>
306
- <Status>error</Status>
307
- <Message>Failed to process order</Message>
308
- <Error>Missing required field: original-order-no</Error>
309
- <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
310
- </OrderProcessingResponse>
311
- ```
312
-
313
- ### Key Benefits
314
-
315
- - ✅ **SFCC Native**: SFCC systems expect XML responses
316
- - ✅ **Type Safety**: Structured XML schema
317
- - ✅ **Execution Tracking**: X-Execution-Id header for troubleshooting
318
- - ✅ **Proper Content-Type**: application/xml; charset=utf-8
319
- - ✅ **Framework Streaming**: Direct XML stream (no JSON wrapper)
320
-
321
- ---
322
-
323
- ## XML Handling Patterns
324
-
325
- ### Incoming XML (Versori Auto-Parsing)
326
-
327
- **CRITICAL**: Versori automatically parses XML when `Content-Type: application/xml` is set.
328
-
329
- ```typescript
330
- // ✅ CORRECT - Versori auto-parses XML into object
331
- // When SFCC sends: POST /webhook with Content-Type: application/xml
332
- // Versori automatically converts XML string → parsed object
333
- // ctx.data is already a parsed object, NOT a string
334
-
335
- export const webhook = webhook('order-ingestion', { response: { mode: 'sync' } })
336
- .then(http('process', { connection: 'fluent_commerce' }, async (ctx) => {
337
- // ctx.data is already parsed! No manual parsing needed
338
- const sfccData = ctx.data; // Already an object: { orders: { order: {...} } }
339
-
340
- // Validate structure
341
- if (!sfccData.orders?.order) {
342
- throw new Error('Invalid XML structure: missing orders.order');
343
- }
344
-
345
- // Use mapWithNodes() because we have custom resolvers
346
- // ✅ Resolvers passed in constructor, so pass undefined here (or override if needed)
347
- const result = await mapper.mapWithNodes(sfccData, undefined, context);
348
- }));
349
-
350
- // ❌ WRONG - Don't try to parse manually
351
- const xmlString = ctx.data; // This is already parsed!
352
- const parsed = parseXML(xmlString); // Unnecessary!
353
- ```
354
-
355
- **Why This Matters:**
356
- - Versori handles XML parsing automatically
357
- - `ctx.data` is always a parsed object when Content-Type is `application/xml`
358
- - No need for `XMLParserService` or manual parsing in Versori workflows
359
- - Simply validate the structure and use directly
360
-
361
- ### Outgoing XML (Production-Ready Pattern)
362
-
363
- **RECOMMENDED FOR SFCC**: Use XML responses with custom handlers for production SFCC integrations.
364
-
365
- **XML Response Pattern (RECOMMENDED)**
366
- ```typescript
367
- import { XMLBuilder } from '@fluentcommerce/fc-connect-sdk';
368
-
369
- export const webhook = webhook('order-ingestion', {
370
- response: {
371
- mode: 'sync',
372
- /**
373
- * onSuccess Handler for XML Response
374
- *
375
- * This handler wraps the XML response string with proper headers.
376
- * The workflow steps return XML strings via ctx.data, and this
377
- * handler wraps them in Response objects for proper streaming.
378
- *
379
- * @param ctx - Context containing:
380
- * - ctx.data: The XML response string from the final workflow step
381
- * - ctx.executionId: Unique identifier for this execution
382
- * @returns Response object with XML body and proper headers
383
- */
384
- onSuccess: (ctx) => new Response(ctx.data, {
385
- status: 200,
386
- headers: {
387
- 'Content-Type': 'application/xml; charset=utf-8',
388
- 'X-Execution-Id': ctx.executionId
389
- }
390
- }),
391
- /**
392
- * onError Handler for XML Error Response
393
- *
394
- * This handler wraps error responses with proper status and headers.
395
- * Returns XML format for consistent error responses.
396
- *
397
- * @param ctx - Context containing:
398
- * - ctx.data: The error XML string from the .catch() step
399
- * - ctx.executionId: Unique identifier for tracking failed executions
400
- * @returns Response object with error XML and 500 status
401
- */
402
- onError: (ctx) => new Response(ctx.data, {
403
- status: 500,
404
- headers: {
405
- 'Content-Type': 'application/xml; charset=utf-8',
406
- 'X-Execution-Id': ctx.executionId
407
- }
408
- })
409
- }
410
- })
411
- .then(http('process', { connection: 'fluent_commerce' }, async (ctx) => {
412
- // Process order...
413
- const orderId = '12345';
414
- const orderRef = 'ORD-001';
415
-
416
- // Build XML response string
417
- const builder = new XMLBuilder({
418
- xmlDeclaration: true, // Adds <?xml version="1.0" encoding="UTF-8"?>
419
- prettyPrint: true, // Format with indentation
420
- encoding: 'UTF-8'
421
- });
422
-
423
- const responseData = {
424
- Status: 'success',
425
- FluentOrderId: orderId,
426
- FluentOrderRef: orderRef,
427
- Timestamp: new Date().toISOString()
428
- };
429
-
430
- // Return XML string (onSuccess handler wraps it in Response)
431
- return builder.build(responseData, 'OrderProcessingResponse');
432
- }))
433
- .catch(({ data, log }) => {
434
- // Return error XML string (onError handler wraps it)
435
- return `<?xml version="1.0" encoding="UTF-8"?>
436
- <OrderProcessingResponse>
437
- <Status>error</Status>
438
- <Error>${data instanceof Error ? data.message : String(data)}</Error>
439
- </OrderProcessingResponse>`;
440
- });
441
- // Response: <?xml version="1.0"?><OrderProcessingResponse>...
442
- // Content-Type: application/xml; charset=utf-8
443
- ```
444
-
445
- **Alternative: JSON Response (Simple Testing)**
446
-
447
- For testing or non-SFCC systems, you can use JSON responses:
448
-
449
- ```typescript
450
- export const webhook = webhook('order-ingestion', {
451
- response: { mode: 'sync' }
452
- })
453
- .then(http('process', { connection: 'fluent_commerce' }, async (ctx) => {
454
- // Returns JSON object - Versori auto-encodes
455
- return {
456
- success: true,
457
- orderId: '12345',
458
- orderRef: 'ORD-001'
459
- };
460
- }));
461
- // Response: {"success":true,"orderId":"12345","orderRef":"ORD-001"}
462
- // Content-Type: application/json
463
- ```
464
-
465
- **Key Decision Points:**
466
-
467
- | Pattern | Use When | Response Type |
468
- |---------|----------|---------------|
469
- | **XML Response** | Production SFCC integration | XML with proper headers |
470
- | **JSON Response** | Testing, non-SFCC systems | JSON auto-encoded |
471
-
472
- **Key Points:**
473
- - ✅ **XML (Recommended)**: Requires custom `onSuccess`/`onError` handlers with `Response` objects
474
- - ✅ **JSON (Testing)**: Default behavior, no custom handler needed
475
- - ✅ Workflow steps return raw strings (XML) or objects (JSON)
476
- - ✅ Response handlers wrap in `Response` objects with proper Content-Type
477
-
478
- ---
479
-
480
- ## SDK Methods Used
481
-
482
- ```typescript
483
- // Core SDK imports
484
- // FC Connect SDK v0.1.41+
485
- // Install: npm install @fluentcommerce/fc-connect-sdk@^0.1.41
486
- // Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk/
487
- // GitHub: https://github.com/fluentcommerce/fc-connect-sdk
488
-
489
- import { createClient, GraphQLMutationMapper, XMLBuilder } from '@fluentcommerce/fc-connect-sdk';
490
- import type { Logger } from '@fluentcommerce/fc-connect-sdk';
491
-
492
- // ═══════════════════════════════════════════════════════════════
493
- // LOGGER ADAPTER PATTERN (Production-Proven)
494
- // ═══════════════════════════════════════════════════════════════
495
- // Create typed Logger adapter from Versori log
496
- // Benefits:
497
- // - Type safety with SDK Logger interface
498
- // - Centralized logging (easy to modify all calls)
499
- // - Clear separation between Versori log and SDK Logger
500
- const logger: Logger = {
501
- info: (msg: string, meta?: any) => log.info(msg, meta),
502
- error: (msg: string, error?: Error, meta?: any) => log.error(msg, meta),
503
- warn: (msg: string, meta?: any) => log.info(msg, meta),
504
- debug: (msg: string, meta?: any) => log.info(msg, meta)
505
- };
506
-
507
- // Key methods
508
- createClient(ctx); // Auto-detects Versori context
509
- // ✅ RECOMMENDED: Pass resolvers in constructor
510
- new GraphQLMutationMapper(config, logger, { customResolvers, fluentClient: client });
511
- // ✅ Alternative: Pass resolvers to mapWithNodes (constructor resolvers merged)
512
- mapper.mapWithNodes(data, resolvers, context);
513
- // ✅ v0.1.41+: mapWithNodes() auto-returns query and variables
514
- const result = await mapper.mapWithNodes(data, undefined, context); // Use constructor resolvers
515
- // result.query - Auto-generated GraphQL mutation
516
- // result.variables - Auto-wrapped variables (input object)
517
- client.graphql({ query: result.query, variables: result.variables }); // Execute
518
- ```
519
-
520
- ### Why GraphQLMutationMapper (Not UniversalMapper)?
521
-
522
- **GraphQLMutationMapper** is the right choice for this template because:
523
-
524
- ✅ **Automatic Mutation Building**: Automatically generates GraphQL mutation query from mapping config
525
- ✅ **Input Wrapping**: Automatically wraps mapped data in `input` object (Fluent API requirement)
526
- ✅ **GraphQL-Specific**: Designed specifically for GraphQL mutations with schema awareness
527
-
528
- **UniversalMapper** would require:
529
- - ❌ Manual GraphQL mutation query building
530
- - ❌ Manual `input` wrapping
531
- - ❌ More boilerplate code
532
-
533
- **Use UniversalMapper when**: You need general-purpose data transformation (CSV → JSON, GraphQL → Parquet, etc.)
534
- **Use GraphQLMutationMapper when**: You need XML/JSON → GraphQL mutations (this template's use case)
535
-
536
- ---
537
-
538
- ## Complete Working Code
539
-
540
- ### 1. Entry Point (index.ts)
541
-
542
- ```typescript
543
- /**
544
- * Entry point - Export all workflows for Versori platform
545
- */
546
-
547
- // Webhook workflows
548
- export { sfccOrderCreate } from './workflows/webhook/sfcc-order-create';
549
- ```
550
-
551
- ### 2. Workflow Entry Point (workflows/webhook/sfcc-order-create.ts)
552
-
553
- ```typescript
554
- /**
555
- * ═══════════════════════════════════════════════════════════════════════════════
556
- * SFCC → FLUENT ORDER CREATE WEBHOOK (Production Pattern)
557
- * ═══════════════════════════════════════════════════════════════════════════════
558
- *
559
- * PURPOSE:
560
- * This webhook receives order data from Salesforce Commerce Cloud (SFCC) as XML
561
- * in the request body. It validates, transforms, and creates orders in Fluent
562
- * Commerce via GraphQL mutations.
563
- *
564
- * WEBHOOK CONFIGURATION:
565
- * - Response mode: synchronous (caller waits for response)
566
- * - Response format: XML (OrderProcessingResponse)
567
- * - CORS: enabled (allows cross-origin requests)
568
- * - Expected payload: XML in request body (Content-Type: application/xml)
569
- *
570
- * ARCHITECTURE:
571
- * Uses production-proven patterns from real SFCC integrations:
572
- * - XML response handling with proper Content-Type headers
573
- * - Logger adapter for type-safe logging
574
- * - Fire-and-forget background processing
575
- * - Fail-fast validation of required configuration
576
- * - Visual step documentation blocks
577
- *
578
- * WORKFLOW STEPS:
579
- * 1. Validate XML structure and required fields
580
- * 2. Apply mapping with custom resolvers
581
- * 3. Execute GraphQL mutation to create order
582
- * 4. Build XML response with order creation results
583
- * ═══════════════════════════════════════════════════════════════════════════════
584
- */
585
-
586
- import { webhook, http, fn } from '@versori/run';
587
- import type { Context } from '@versori/run';
588
- import { createClient, GraphQLMutationMapper } from '@fluentcommerce/fc-connect-sdk';
589
- import type { Logger } from '@fluentcommerce/fc-connect-sdk';
590
- import { allResolvers } from '../../resolvers';
591
- import mappingConfig from '../../config/sfcc-to-fluent-order-mapping.json' with { type: 'json' };
592
- import { XMLBuilder } from '@fluentcommerce/fc-connect-sdk';
593
-
594
- /**
595
- * ═══════════════════════════════════════════════════════════════════════════════
596
- * XML RESPONSE ARCHITECTURE (Production Pattern)
597
- * ═══════════════════════════════════════════════════════════════════════════════
598
- *
599
- * This webhook uses XML responses as the PRIMARY pattern for SFCC integration.
600
- * The onSuccess and onError handlers wrap XML strings in Response objects with
601
- * proper Content-Type headers.
602
- *
603
- * RESPONSE FORMAT:
604
- * Success:
605
- * <?xml version="1.0" encoding="UTF-8"?>
606
- * <OrderProcessingResponse>
607
- * <Status>success</Status>
608
- * <Message>Order successfully processed and created in Fluent Commerce</Message>
609
- * <FluentOrderId>6370</FluentOrderId>
610
- * <FluentOrderRef>0017326966182_G_postman_test_1760703173226</FluentOrderRef>
611
- * <SFCCOrderRef>0017326966182_G_postman_test_1760703173226</SFCCOrderRef>
612
- * <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
613
- * </OrderProcessingResponse>
614
- *
615
- * Error:
616
- * <?xml version="1.0" encoding="UTF-8"?>
617
- * <OrderProcessingResponse>
618
- * <Status>error</Status>
619
- * <Message>Failed to process order</Message>
620
- * <Error>Error details here</Error>
621
- * <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
622
- * </OrderProcessingResponse>
623
- *
624
- * HOW IT WORKS:
625
- * 1. Workflow steps return XML strings via ctx.data
626
- * 2. onSuccess handler receives ctx with the XML string in ctx.data
627
- * 3. Handler creates Response with Content-Type: application/xml
628
- * 4. Framework streams the Response directly (no JSON encoding)
629
- * ═══════════════════════════════════════════════════════════════════════════════
630
- */
631
- export const sfccOrderCreate = webhook('sfcc-order-create', {
632
- response: {
633
- mode: 'sync',
634
- /**
635
- * onSuccess Handler for XML Response
636
- *
637
- * This handler wraps the XML response string with proper headers.
638
- * Similar to production SFCC workflows, this ensures the XML is
639
- * streamed directly without JSON encoding.
640
- *
641
- * @param ctx - Context containing:
642
- * - ctx.data: The XML response string from the final workflow step
643
- * - ctx.executionId: Unique identifier for this execution
644
- * @returns Response object with XML body and proper headers
645
- */
646
- onSuccess: (ctx) => new Response(ctx.data, {
647
- status: 200,
648
- headers: {
649
- 'Content-Type': 'application/xml; charset=utf-8',
650
- 'X-Execution-Id': ctx.executionId
651
- }
652
- }),
653
- /**
654
- * onError Handler for XML Error Response
655
- *
656
- * This handler wraps error responses with proper status and headers.
657
- * Returns XML format for consistent error responses.
658
- *
659
- * @param ctx - Context containing:
660
- * - ctx.data: The error XML string from the .catch() step
661
- * - ctx.executionId: Unique identifier for tracking failed executions
662
- * @returns Response object with error XML and 500 status
663
- */
664
- onError: (ctx) => new Response(ctx.data, {
665
- status: 500,
666
- headers: {
667
- 'Content-Type': 'application/xml; charset=utf-8',
668
- 'X-Execution-Id': ctx.executionId
669
- }
670
- })
671
- },
672
- cors: true
673
- }).then(
674
- /**
675
- * ═══════════════════════════════════════════════════════════════════════════════
676
- * STEP 1: VALIDATE XML STRUCTURE
677
- * ═══════════════════════════════════════════════════════════════════════════════
678
- *
679
- * This step validates that the incoming data is properly parsed XML from Versori.
680
- * Uses fail-fast validation pattern to catch configuration issues early.
681
- * ═══════════════════════════════════════════════════════════════════════════════
682
- */
683
- fn('validate-xml-structure', async ({ data, log, activation }) => {
684
- // ─────────────────────────────────────────────────────────────────────────
685
- // LOGGER ADAPTER PATTERN (Production-Proven)
686
- // ─────────────────────────────────────────────────────────────────────────
687
- // Create Logger Adapter for type-safe SDK logging
688
- // Benefits:
689
- // - Explicit interface contract (TypeScript Logger type)
690
- // - Centralized logging adapter (easy to modify all log calls)
691
- // - Type safety enforced
692
- // - Clear separation between Versori log and SDK Logger
693
- const logger: Logger = {
694
- info: (msg: string, meta?: any) => log.info(msg, meta),
695
- error: (msg: string, error?: Error, meta?: any) => log.error(msg, meta),
696
- warn: (msg: string, meta?: any) => log.info(msg, meta),
697
- debug: (msg: string, meta?: any) => log.info(msg, meta)
698
- };
699
-
700
- logger.info('Starting SFCC order webhook processing');
701
-
702
- // ─────────────────────────────────────────────────────────────────────────
703
- // INITIAL DEPLOYMENT MONITORING PATTERN
704
- // ─────────────────────────────────────────────────────────────────────────
705
- // TEMPORARY: Complete XML log for short-term monitoring (first 2-4 weeks)
706
- // TODO: Remove this log once integration is stable and monitoring is complete
707
- // Purpose: Helps catch XML structure issues and field mapping problems early
708
- // Impact: Increases log volume - remove after stabilization
709
- // ─────────────────────────────────────────────────────────────────────────
710
- logger.info('Complete incoming XML data', { completeData: data });
711
-
712
- // ─────────────────────────────────────────────────────────────────────────
713
- // Validate Pre-Parsed XML Object from Versori
714
- // ─────────────────────────────────────────────────────────────────────────
715
- if (typeof data !== 'object' || data === null || !('orders' in data)) {
716
- throw new Error(
717
- `Invalid data format. Expected XML (as pre-parsed object with 'orders' root). ` +
718
- `Received: ${typeof data}. ` +
719
- `Please ensure you are sending XML with Content-Type: application/xml.`
720
- );
721
- }
722
-
723
- logger.info('XML structure validated successfully');
724
-
725
- return {
726
- parsedXml: data,
727
- logger
728
- };
729
- })
730
- )
731
-
732
- /**
733
- * ═══════════════════════════════════════════════════════════════════════════════
734
- * STEP 2: MAP WITH RESOLVERS USING SDK NODES PATTERN
735
- * ═══════════════════════════════════════════════════════════════════════════════
736
- *
737
- * This step applies field mappings and executes custom resolvers using the SDK's
738
- * mapWithNodes() method. This method is REQUIRED when using custom resolvers.
739
- *
740
- * v0.1.41+ Improvement: mapWithNodes() auto-returns query and variables
741
- * ═══════════════════════════════════════════════════════════════════════════════
742
- */
743
- .then(
744
- http('map-with-resolvers', {
745
- connection: 'fluent_commerce'
746
- }, async (ctx) => {
747
- const { data, log, activation } = ctx;
748
- const { parsedXml, logger } = data;
749
-
750
- logger.info('Mapping SFCC order to Fluent Commerce with custom resolvers');
751
-
752
- try {
753
- const fluentClient = await createClient(ctx);
754
-
755
- // ═════════════════════════════════════════════════════════════════════
756
- // INTEGRATION VARIABLES VALIDATION (Fail-Fast Pattern)
757
- // ═════════════════════════════════════════════════════════════════════
758
- // Production pattern: Validate required configuration early
759
- // Benefits:
760
- // - Fails immediately with explicit error message
761
- // - Tells user EXACTLY what's wrong and where to fix it
762
- // - Better than silent fallback to default (hides config issues)
763
- // - Provides audit trail of successful retrieval
764
- const fluentRetailerId = activation.getVariable('fluentRetailerId') as string;
765
-
766
- if (!fluentRetailerId) {
767
- throw new Error(
768
- 'fluentRetailerId integration variable is required but not set. ' +
769
- 'Please set this variable in Versori Activations > Variables section.'
770
- );
771
- }
772
-
773
- logger.info('Using retailer ID from integration variables', { retailerId: fluentRetailerId });
774
-
775
- // ═════════════════════════════════════════════════════════════════════
776
- // INITIALIZE MAPPER WITH RESOLVERS IN CONSTRUCTOR (Recommended Pattern)
777
- // ═════════════════════════════════════════════════════════════════════
778
- // ✅ CORRECT: Pass resolvers in constructor options
779
- // This is consistent with UniversalMapper and allows resolvers to be reused
780
- const mapper = new GraphQLMutationMapper(
781
- mappingConfig as any,
782
- logger,
783
- {
784
- customResolvers: allResolvers, // ✅ Resolvers in constructor
785
- fluentClient: fluentClient as any,
786
- }
787
- );
788
-
789
- // ═════════════════════════════════════════════════════════════════════
790
- // CREATE RESOLVER CONTEXT
791
- // ═════════════════════════════════════════════════════════════════════
792
- // Pass configuration to custom resolvers
793
- // SDK automatically provides helpers (get, ensureArray, logger, etc.)
794
- // You only need to pass custom config and fluentClient
795
- const resolverContext = {
796
- fluentClient: fluentClient as any,
797
- config: {
798
- retailerId: fluentRetailerId,
799
- defaultCountry: 'US',
800
- companyName: 'YourCompany' // Customize per deployment
801
- },
802
- // ✅ SDK automatically provides helpers.get, helpers.ensureArray, helpers.logger
803
- // No need to manually create them - SDK handles XML attributes via path resolver
804
- // ✅ Resolvers can access config via helpers.context.config.retailerId
805
- // OR via the config parameter (3rd param): config.retailerId
806
- };
807
-
808
- // Execute mapping WITH custom resolvers
809
- // ✅ v0.1.41+: mapWithNodes() auto-generates query and variables
810
- // ✅ Resolvers from constructor are automatically used (can override with 2nd param)
811
- const mappingResult = await mapper.mapWithNodes(
812
- parsedXml,
813
- undefined, // Use constructor resolvers (or pass override resolvers here)
814
- resolverContext
815
- );
816
-
817
- if (!mappingResult.success) {
818
- throw new Error(`Mapping failed: ${mappingResult.errors?.join(', ')}`);
819
- }
820
-
821
- const orderRef = mappingResult.data?.input?.ref || 'unknown';
822
-
823
- logger.info('Successfully mapped order data with custom resolvers', { orderRef });
824
-
825
- return {
826
- mappedData: mappingResult.data,
827
- mappingResult, // ✅ Contains auto-generated query and variables
828
- orderRef,
829
- fluentClient,
830
- logger
831
- };
832
- } catch (error) {
833
- log.error('Error during mapping with resolvers', {
834
- error: error instanceof Error ? error.message : String(error),
835
- stack: error instanceof Error ? error.stack : undefined
836
- });
837
- throw error;
838
- }
839
- })
840
- )
841
-
842
- /**
843
- * ═══════════════════════════════════════════════════════════════════════════════
844
- * STEP 3: EXECUTE GRAPHQL MUTATION
845
- * ═══════════════════════════════════════════════════════════════════════════════
846
- *
847
- * Execute the GraphQL mutation using auto-generated query from mapWithNodes().
848
- * v0.1.41+ automatically wraps variables and generates query.
849
- * ═══════════════════════════════════════════════════════════════════════════════
850
- */
851
- .then(
852
- http('execute-graphql-mutation', {
853
- connection: 'fluent_commerce'
854
- }, async (ctx) => {
855
- const { data, log } = ctx;
856
- const { mappingResult, orderRef, fluentClient, logger } = data;
857
-
858
- logger.info('Executing createOrder GraphQL mutation', { orderRef });
859
-
860
- try {
861
- // ✅ v0.1.41+: Use auto-generated query and variables from mapWithNodes()
862
- // No need to call buildMutation() - query is already generated
863
- const mutationResult = await (fluentClient as any).graphql({
864
- query: mappingResult.query, // ✅ Auto-generated mutation query
865
- variables: mappingResult.variables // ✅ Auto-wrapped variables
866
- });
867
-
868
- if (mutationResult.errors && mutationResult.errors.length > 0) {
869
- logger.error('GraphQL mutation returned errors', {
870
- errors: mutationResult.errors,
871
- orderRef
872
- });
873
- throw new Error(`GraphQL errors: ${JSON.stringify(mutationResult.errors)}`);
874
- }
875
-
876
- const createdOrder = (mutationResult.data as any)?.createOrder;
877
-
878
- if (!createdOrder) {
879
- throw new Error('No order data returned from createOrder mutation');
880
- }
881
-
882
- // Access fields specified in returnFields (id, ref, status, totalPrice)
883
- logger.info('Successfully created order in Fluent Commerce', {
884
- fluentOrderId: createdOrder.id, // From returnFields
885
- fluentOrderRef: createdOrder.ref, // From returnFields
886
- status: createdOrder.status, // From returnFields
887
- totalPrice: createdOrder.totalPrice, // From returnFields
888
- sfccOrderRef: orderRef
889
- });
890
-
891
- return {
892
- success: true,
893
- fluentOrderId: createdOrder.id,
894
- fluentOrderRef: createdOrder.ref,
895
- sfccOrderRef: orderRef,
896
- orderData: createdOrder
897
- };
898
- } catch (error) {
899
- log.error('Error executing GraphQL mutation', {
900
- error: error instanceof Error ? error.message : String(error),
901
- orderRef
902
- });
903
- throw error;
904
- }
905
- })
906
- )
907
-
908
- /**
909
- * ═══════════════════════════════════════════════════════════════════════════════
910
- * STEP 4: BUILD XML RESPONSE
911
- * ═══════════════════════════════════════════════════════════════════════════════
912
- *
913
- * IMPORTANT: Return the XML string here.
914
- * The onSuccess handler will wrap it in a Response with:
915
- * - Content-Type: application/xml
916
- * - Status: 200
917
- * - X-Execution-Id header
918
- *
919
- * This keeps the workflow logic focused on data transformation while
920
- * the framework handles HTTP response formatting.
921
- * ═══════════════════════════════════════════════════════════════════════════════
922
- */
923
- .then(
924
- fn('build-xml-response', ({ data, log }) => {
925
- log.info('Building XML response for SFCC order processing', {
926
- fluentOrderId: data.fluentOrderId,
927
- fluentOrderRef: data.fluentOrderRef,
928
- sfccOrderRef: data.sfccOrderRef
929
- });
930
-
931
- // Initialize XML Builder (SDK-provided)
932
- const builder = new XMLBuilder({
933
- xmlDeclaration: true, // Adds <?xml version="1.0" encoding="UTF-8"?>
934
- prettyPrint: true, // Format with indentation (default: true)
935
- indent: ' ', // Two-space indentation (default)
936
- encoding: 'UTF-8' // XML encoding (default)
937
- });
938
-
939
- // Build XML response data
940
- const responseData = {
941
- Status: 'success',
942
- Message: 'Order successfully processed and created in Fluent Commerce',
943
- FluentOrderId: data.fluentOrderId,
944
- FluentOrderRef: data.fluentOrderRef,
945
- SFCCOrderRef: data.sfccOrderRef,
946
- Timestamp: new Date().toISOString()
947
- };
948
-
949
- // Build XML with root element
950
- const xmlString = builder.build(responseData, 'OrderProcessingResponse');
951
-
952
- log.info('Successfully built XML response', {
953
- orderRef: data.sfccOrderRef,
954
- xmlLength: xmlString.length
955
- });
956
-
957
- /**
958
- * Return just the XML string here.
959
- * The onSuccess handler will wrap it in a Response object with:
960
- * - Content-Type: application/xml
961
- * - Status: 200
962
- * - X-Execution-Id header
963
- */
964
- return xmlString;
965
- })
966
- )
967
-
968
- /**
969
- * ═══════════════════════════════════════════════════════════════════════════════
970
- * ERROR HANDLING
971
- * ═══════════════════════════════════════════════════════════════════════════════
972
- *
973
- * Return error XML string that will be wrapped by onError handler with status 500.
974
- * ═══════════════════════════════════════════════════════════════════════════════
975
- */
976
- .catch(({ data, log }) => {
977
- log.error('Order processing failed', {
978
- error: data instanceof Error ? data.message : String(data)
979
- });
980
-
981
- /**
982
- * Build error XML that will be wrapped by onError handler.
983
- * The onError handler will receive this string in ctx.data and wrap it
984
- * in a Response object with proper status and headers.
985
- */
986
- const errorXml = `<?xml version="1.0" encoding="UTF-8"?>
987
- <OrderProcessingResponse>
988
- <Status>error</Status>
989
- <Message>Failed to process order</Message>
990
- <Error>${data instanceof Error ? data.message : String(data)}</Error>
991
- <Timestamp>${new Date().toISOString()}</Timestamp>
992
- </OrderProcessingResponse>`;
993
-
994
- /**
995
- * Return the error XML string.
996
- * The onError handler will wrap it in a Response with status 500.
997
- */
998
- return errorXml;
999
- });
1000
- ```
1001
-
1002
- ### 3. Service Implementation (services/order-ingestion.service.ts)
1003
-
1004
- ```typescript
1005
- /**
1006
- * SFCC Order Ingestion Service
1007
- *
1008
- * Orchestrates the complete order ingestion process:
1009
- * 1. Validate webhook payload
1010
- * 2. Validate SFCC structure
1011
- * 3. Apply field mapping with custom resolvers
1012
- * 4. Create order via GraphQL API
1013
- * 5. Audit trail (save input/output files)
1014
- */
1015
-
1016
- import { createClient, GraphQLMutationMapper } from '@fluentcommerce/fc-connect-sdk';
1017
- import { allResolvers } from '../resolvers';
1018
- import mappingConfig from '../config/sfcc-to-fluent-order-mapping.json' with { type: 'json' };
1019
-
1020
- /**
1021
- * Process SFCC order ingestion
1022
- *
1023
- * @param ctx - Versori context object containing fetch, connections, log, activation, data
1024
- * @param executionStartTime - Workflow start time for duration tracking
1025
- */
1026
- export async function processOrderIngestion(ctx: any, executionStartTime: number) {
1027
- const { log, data, activation } = ctx;
1028
-
1029
- log.info('Starting SFCC order processing');
1030
-
1031
- try {
1032
- const sfccData = data;
1033
-
1034
- if (!sfccData) {
1035
- log.error('No order data received');
1036
- return {
1037
- success: false,
1038
- error: 'No order data received',
1039
- timestamp: new Date().toISOString(),
1040
- duration: Date.now() - executionStartTime,
1041
- };
1042
- }
1043
-
1044
- if (!sfccData.orders?.order) {
1045
- log.error('Invalid order structure: missing orders.order');
1046
- return {
1047
- success: false,
1048
- error: 'Invalid order structure: missing orders.order',
1049
- timestamp: new Date().toISOString(),
1050
- duration: Date.now() - executionStartTime,
1051
- };
1052
- }
1053
-
1054
- const order = sfccData.orders.order;
1055
- if (!order['original-order-no']) {
1056
- log.error('Missing required field: original-order-no');
1057
- return {
1058
- success: false,
1059
- error: 'Missing required field: original-order-no',
1060
- timestamp: new Date().toISOString(),
1061
- duration: Date.now() - executionStartTime,
1062
- };
1063
- }
1064
-
1065
- if (!order.customer?.['customer-no']) {
1066
- log.error('Missing required field: customer.customer-no');
1067
- return {
1068
- success: false,
1069
- error: 'Missing required field: customer.customer-no',
1070
- timestamp: new Date().toISOString(),
1071
- duration: Date.now() - executionStartTime,
1072
- };
1073
- }
1074
-
1075
- const fluentClient = await createClient(ctx);
1076
- log.info('Fluent client initialized');
1077
-
1078
- log.info('Applying mapping');
1079
-
1080
- const retailerId = activation?.getVariable('fluentRetailerId') as string;
1081
-
1082
- if (!retailerId) {
1083
- log.error('Missing required variable: fluentRetailerId');
1084
- return {
1085
- success: false,
1086
- error: 'Missing required configuration: fluentRetailerId',
1087
- timestamp: new Date().toISOString(),
1088
- duration: Date.now() - executionStartTime,
1089
- };
1090
- }
1091
-
1092
- const productCatalogueRef = activation?.getVariable('productCatalogueRef') as string || 'PC:MASTER:2';
1093
-
1094
- const mapper = new GraphQLMutationMapper(
1095
- mappingConfig as any,
1096
- log,
1097
- {
1098
- customResolvers: allResolvers,
1099
- fluentClient: fluentClient as any,
1100
- }
1101
- );
1102
-
1103
- const resolverContext = {
1104
- fluentClient: fluentClient as any,
1105
- config: {
1106
- retailerId: retailerId,
1107
- defaultOrderType: 'HD',
1108
- defaultCountry: 'US',
1109
- productCatalogueRef: productCatalogueRef,
1110
- },
1111
- };
1112
-
1113
- const result = await mapper.mapWithNodes(sfccData, undefined, resolverContext);
1114
-
1115
- if (!result.success) {
1116
- log.error('Mapping failed', { errors: result.errors });
1117
- return {
1118
- success: false,
1119
- error: `Mapping failed: ${result.errors?.join(', ')}`,
1120
- timestamp: new Date().toISOString(),
1121
- duration: Date.now() - executionStartTime,
1122
- };
1123
- }
1124
-
1125
- const orderRef = sfccData.orders?.order?.['original-order-no'] || 'unknown';
1126
-
1127
- log.info('Mapping completed', { orderRef });
1128
-
1129
- // Save audit trail to KV storage (Versori-compatible - file system is read-only)
1130
- try {
1131
- const kv = openKv(':project:');
1132
- const timestamp = new Date().toISOString();
1133
- const auditKey = ['orders', 'audit', orderRef, timestamp];
1134
-
1135
- await kv.set([...auditKey, 'sfcc-input'], sfccData);
1136
- await kv.set([...auditKey, 'fluent-mapped'], result.data);
1137
-
1138
- log.info('Audit trail saved to KV storage', { orderRef, timestamp });
1139
- } catch (kvError: any) {
1140
- log.warn('Failed to save audit trail to KV', { error: kvError.message });
1141
- }
1142
-
1143
- log.info('Creating order in Fluent Commerce', { orderRef });
1144
-
1145
- const mutationRes = await (fluentClient as any).graphql({
1146
- query: result.query,
1147
- variables: result.variables
1148
- });
1149
-
1150
- if (mutationRes.errors && mutationRes.errors.length > 0) {
1151
- log.error('GraphQL mutation failed', { errors: mutationRes.errors });
1152
-
1153
- // Save error to KV storage
1154
- try {
1155
- const kv = openKv(':project:');
1156
- const timestamp = new Date().toISOString();
1157
- const auditKey = ['orders', 'audit', orderRef, timestamp, 'fluent-error'];
1158
- await kv.set(auditKey, mutationRes);
1159
- } catch (kvError) {
1160
- // Ignore KV save errors
1161
- }
1162
-
1163
- return {
1164
- success: false,
1165
- error: `GraphQL mutation failed: ${mutationRes.errors.map((e: any) => e.message).join(', ')}`,
1166
- timestamp: new Date().toISOString(),
1167
- duration: Date.now() - executionStartTime,
1168
- };
1169
- }
1170
-
1171
- const createOrderData = (mutationRes as any)?.data?.createOrder;
1172
-
1173
- if (!createOrderData) {
1174
- log.error('No order data returned from API');
1175
- return {
1176
- success: false,
1177
- error: 'No order data returned from GraphQL mutation',
1178
- timestamp: new Date().toISOString(),
1179
- duration: Date.now() - executionStartTime,
1180
- };
1181
- }
1182
-
1183
- // Save successful response to KV storage
1184
- try {
1185
- const kv = openKv(':project:');
1186
- const timestamp = new Date().toISOString();
1187
- const auditKey = ['orders', 'audit', orderRef, timestamp, 'fluent-response'];
1188
- await kv.set(auditKey, mutationRes);
1189
- } catch (kvError) {
1190
- // Ignore KV save errors
1191
- }
1192
-
1193
- log.info('Order created in Fluent Commerce', {
1194
- orderId: createOrderData?.id,
1195
- orderRef: createOrderData?.ref,
1196
- });
1197
-
1198
- return {
1199
- success: true,
1200
- data: {
1201
- sfccOrderRef: orderRef,
1202
- fluentOrderId: createOrderData?.id,
1203
- fluentOrderRef: createOrderData?.ref,
1204
- timestamp: new Date().toISOString(),
1205
- },
1206
- duration: Date.now() - executionStartTime,
1207
- };
1208
- } catch (error: any) {
1209
- log.error('Fatal error', {
1210
- error: error instanceof Error ? error.message : String(error),
1211
- stack: error instanceof Error ? error.stack : undefined,
1212
- });
1213
-
1214
- return {
1215
- success: false,
1216
- error: error instanceof Error ? error.message : String(error),
1217
- timestamp: new Date().toISOString(),
1218
- duration: Date.now() - executionStartTime,
1219
- };
1220
- }
1221
- }
1222
- ```
1223
-
1224
- ### 4. Mapping Configuration (config/sfcc-to-fluent-order-mapping.json)
1225
-
1226
- ```json
1227
- {
1228
- "direction": "ingest",
1229
- "sourceFormat": "xml",
1230
- "mutation": "createOrder",
1231
- "returnFields": ["id", "ref", "status", "totalPrice"],
1232
- "fields": {
1233
- "ref": {
1234
- "source": "orders.order.original-order-no",
1235
- "resolver": "sdk.toString",
1236
- "comment": "Order reference from SFCC native field (REQUIRED)"
1237
- },
1238
- "type": {
1239
- "resolver": "custom.deriveOrderType",
1240
- "comment": "Derive from shipment count: 1 shipment = HD, multiple = MULTI (REQUIRED)"
1241
- },
1242
- "retailer.id": {
1243
- "value": "${RETAILER_ID}",
1244
- "comment": "From environment variable or configuration (REQUIRED)"
1245
- },
1246
- "totalPrice": {
1247
- "source": "orders.order.totals.order-total.net-price",
1248
- "resolver": "sdk.parseFloat",
1249
- "comment": "Order total net price from SFCC totals (REQUIRED)"
1250
- },
1251
- "totalTaxPrice": {
1252
- "source": "orders.order.totals.order-total.tax",
1253
- "resolver": "sdk.parseFloat",
1254
- "comment": "Order total tax from SFCC totals (REQUIRED)"
1255
- },
1256
- "attributes": {
1257
- "resolver": "custom.buildOrderAttributes",
1258
- "comment": "Order-level attributes: order-date, created-by, invoice-no, totals JSON"
1259
- },
1260
- "customer": {
1261
- "fields": {
1262
- "ref": {
1263
- "source": "orders.order.customer.customer-no",
1264
- "resolver": "sdk.toString",
1265
- "comment": "Customer reference from SFCC (REQUIRED)"
1266
- },
1267
- "firstName": {
1268
- "source": "orders.order.customer.customer-name",
1269
- "resolver": "custom.extractFirstName",
1270
- "comment": "Extract first name from customer-name"
1271
- },
1272
- "lastName": {
1273
- "source": "orders.order.customer.customer-name",
1274
- "resolver": "custom.extractLastName",
1275
- "comment": "Extract last name from customer-name"
1276
- },
1277
- "primaryEmail": {
1278
- "source": "orders.order.customer.customer-email",
1279
- "resolver": "sdk.toString",
1280
- "comment": "Customer email from SFCC (REQUIRED)"
1281
- }
1282
- }
1283
- },
1284
- "billingAddress": {
1285
- "fields": {
1286
- "name": {
1287
- "source": "orders.order.customer.billing-address.first-name",
1288
- "resolver": "custom.combineBillingName",
1289
- "comment": "Combine first-name + last-name"
1290
- },
1291
- "street": {
1292
- "source": "orders.order.customer.billing-address.address1",
1293
- "resolver": "sdk.toString",
1294
- "comment": "Billing address line 1 (REQUIRED)"
1295
- },
1296
- "street2": {
1297
- "source": "orders.order.customer.billing-address.address2",
1298
- "resolver": "sdk.toString",
1299
- "comment": "Billing address line 2 (optional)"
1300
- },
1301
- "city": {
1302
- "source": "orders.order.customer.billing-address.city",
1303
- "resolver": "sdk.toString",
1304
- "comment": "Billing city (REQUIRED)"
1305
- },
1306
- "state": {
1307
- "source": "orders.order.customer.billing-address.state-code",
1308
- "resolver": "sdk.toString",
1309
- "comment": "Billing state code (REQUIRED)"
1310
- },
1311
- "postcode": {
1312
- "source": "orders.order.customer.billing-address.postal-code",
1313
- "resolver": "sdk.toString",
1314
- "comment": "Billing postal code (REQUIRED)"
1315
- },
1316
- "country": {
1317
- "source": "orders.order.customer.billing-address.country-code",
1318
- "resolver": "custom.normalizeCountryCode",
1319
- "comment": "Normalize country code (us → US)"
1320
- }
1321
- },
1322
- "attributes": {
1323
- "resolver": "custom.buildBillingAddressAttributes",
1324
- "comment": "Billing address phone and custom attributes"
1325
- }
1326
- },
1327
- "items": {
1328
- "source": "orders.order.product-lineitems.product-lineitem",
1329
- "isArray": true,
1330
- "fields": {
1331
- "ref": {
1332
- "source": "$.position",
1333
- "resolver": "custom.createItemRef",
1334
- "comment": "Item reference from position (REQUIRED)"
1335
- },
1336
- "productRef": {
1337
- "source": "$.product-id",
1338
- "resolver": "sdk.toString",
1339
- "comment": "Product reference from SFCC product-id (REQUIRED)"
1340
- },
1341
- "productCatalogueRef": {
1342
- "resolver": "custom.getProductCatalogueRef",
1343
- "comment": "Default product catalogue"
1344
- },
1345
- "quantity": {
1346
- "source": "$.quantity",
1347
- "resolver": "sdk.parseFloat",
1348
- "comment": "Quantity from SFCC (REQUIRED)"
1349
- },
1350
- "price": {
1351
- "source": "$.base-price",
1352
- "resolver": "sdk.parseFloat",
1353
- "comment": "Base price per item from SFCC (REQUIRED)"
1354
- },
1355
- "totalPrice": {
1356
- "source": "$.net-price",
1357
- "resolver": "sdk.parseFloat",
1358
- "comment": "Total price (net-price) from SFCC (REQUIRED)"
1359
- },
1360
- "taxPrice": {
1361
- "source": "$.tax",
1362
- "resolver": "sdk.parseFloat",
1363
- "comment": "Tax amount from SFCC (REQUIRED)"
1364
- },
1365
- "currency": {
1366
- "source": "orders.order.currency",
1367
- "resolver": "sdk.toString",
1368
- "comment": "Currency from order level (REQUIRED)"
1369
- },
1370
- "attributes": {
1371
- "resolver": "custom.buildItemAttributes",
1372
- "comment": "Item attributes: position, tax-basis, tax-rate, shipment-id, gift, custom-attributes"
1373
- }
1374
- }
1375
- },
1376
- "fulfilmentChoices": {
1377
- "source": "orders.order.shipments.shipment",
1378
- "isArray": true,
1379
- "fields": {
1380
- "type": {
1381
- "source": "$.shipping-method",
1382
- "resolver": "custom.mapShippingMethodToFulfilmentType",
1383
- "comment": "Map ISPU → BOPIS, STANDARD_SHIPPING → SHIP_TO_HOME (REQUIRED)"
1384
- },
1385
- "pickupLocationRef": {
1386
- "source": "$.custom-attributes.custom-attribute[attribute-id=fromStoreId]",
1387
- "resolver": "sdk.toString",
1388
- "comment": "Store ID for BOPIS orders"
1389
- },
1390
- "deliveryType": {
1391
- "source": "orders.order.shipping-lineitems.shipping-lineitem.item-id",
1392
- "resolver": "sdk.toString",
1393
- "comment": "Delivery type (STANDARD_SHIPPING, etc.)"
1394
- },
1395
- "deliveryAddress": {
1396
- "fields": {
1397
- "name": {
1398
- "source": "$.shipping-address.first-name",
1399
- "resolver": "custom.combineShippingName",
1400
- "comment": "Combine first-name + last-name"
1401
- },
1402
- "street": {
1403
- "source": "$.shipping-address.address1",
1404
- "resolver": "sdk.toString",
1405
- "comment": "Shipping address line 1 (REQUIRED)"
1406
- },
1407
- "street2": {
1408
- "source": "$.shipping-address.address2",
1409
- "resolver": "sdk.toString",
1410
- "comment": "Shipping address line 2 (optional)"
1411
- },
1412
- "city": {
1413
- "source": "$.shipping-address.city",
1414
- "resolver": "sdk.toString",
1415
- "comment": "Shipping city (REQUIRED)"
1416
- },
1417
- "state": {
1418
- "source": "$.shipping-address.state-code",
1419
- "resolver": "sdk.toString",
1420
- "comment": "Shipping state code (REQUIRED)"
1421
- },
1422
- "postcode": {
1423
- "source": "$.shipping-address.postal-code",
1424
- "resolver": "sdk.toString",
1425
- "comment": "Shipping postal code (REQUIRED)"
1426
- },
1427
- "country": {
1428
- "source": "$.shipping-address.country-code",
1429
- "resolver": "custom.normalizeCountryCode",
1430
- "comment": "Normalize country code (US → US)"
1431
- }
1432
- },
1433
- "attributes": {
1434
- "resolver": "custom.buildFulfilmentAddressAttributes",
1435
- "comment": "Shipping address phone and custom attributes"
1436
- }
1437
- },
1438
- "attributes": {
1439
- "resolver": "custom.buildFulfilmentChoicesAttributes",
1440
- "comment": "Fulfilment attributes: gift, totals JSON (merchandize-total, shipping-total, shipment-total), custom-attributes"
1441
- }
1442
- }
1443
- },
1444
- "financialTransactions": {
1445
- "source": "orders.order.payments.payment",
1446
- "isArray": true,
1447
- "fields": {
1448
- "ref": {
1449
- "source": "$.transaction-id",
1450
- "resolver": "sdk.toString",
1451
- "comment": "Transaction ID from SFCC (REQUIRED)"
1452
- },
1453
- "type": {
1454
- "value": "AUTHORIZATION",
1455
- "comment": "Payment type (REQUIRED)"
1456
- },
1457
- "amount": {
1458
- "source": "$.amount",
1459
- "resolver": "sdk.parseFloat",
1460
- "comment": "Payment amount from SFCC (REQUIRED)"
1461
- },
1462
- "currency": {
1463
- "source": "orders.order.currency",
1464
- "resolver": "sdk.toString",
1465
- "comment": "Currency from order level (REQUIRED)"
1466
- },
1467
- "paymentMethod": {
1468
- "source": "$.custom-method.method-name",
1469
- "resolver": "custom.mapPaymentMethod",
1470
- "comment": "Payment method (KLARNA, CreditCard, etc.) (REQUIRED)"
1471
- },
1472
- "externalTransactionCode": {
1473
- "source": "$.custom-attributes.custom-attribute[attribute-id=klarnaAuthorizationCode]",
1474
- "resolver": "sdk.toString",
1475
- "comment": "Authorization code (Klarna, etc.)"
1476
- },
1477
- "externalTransactionId": {
1478
- "source": "$.custom-method.custom-attributes.custom-attribute[attribute-id=klarnaClientToken]",
1479
- "resolver": "sdk.toString",
1480
- "comment": "External transaction ID (Klarna client token, etc.)"
1481
- },
1482
- "attributes": {
1483
- "resolver": "custom.buildPaymentAttributes",
1484
- "comment": "Payment attributes: processor-id, custom-attributes"
1485
- }
1486
- }
1487
- }
1488
- },
1489
- "returnFields": ["id", "ref", "status", "totalPrice"]
1490
- }
1491
- ```
1492
-
1493
- **Note:** The `returnFields` array specifies which fields are returned in the GraphQL mutation response. These fields are available in `mutationRes.data.createOrder` after executing the mutation. If omitted, defaults to `["id", "ref"]`.
1494
-
1495
- ### 5. Custom Resolvers (src/resolvers/order-resolvers.ts)
1496
-
1497
- ```typescript
1498
- /**
1499
- * Order Resolvers for SFCC Native → Fluent
1500
- *
1501
- * Maps from SFCC native structure to Fluent order fields
1502
- */
1503
-
1504
- import type { ResolverMap } from './types';
1505
-
1506
- export const orderResolvers: ResolverMap = {
1507
- /**
1508
- * Derive order type from SFCC order structure
1509
- * Example: 1 shipment → "HD", multiple shipments → "MULTI"
1510
- */
1511
- 'custom.deriveOrderType': (value: any, data: any, config: any, helpers: any): string => {
1512
- const sfccData = data.root || data || {};
1513
- const shipments = helpers.ensureArray(helpers.get(sfccData, 'orders.order.shipments.shipment'));
1514
-
1515
- if (shipments.length > 1) {
1516
- return 'MULTI';
1517
- }
1518
-
1519
- return config.defaultOrderType || 'HD';
1520
- },
1521
-
1522
- /**
1523
- * Get product catalogue reference
1524
- * Returns default catalogue ref for all items
1525
- */
1526
- 'custom.getProductCatalogueRef': (value: any, data: any, config: any, helpers: any): string => {
1527
- return process.env.PRODUCT_CATALOGUE_REF || 'PC:MASTER:2';
1528
- },
1529
-
1530
- /**
1531
- * Build order-level attributes from SFCC fields
1532
- * Extracts order metadata (order-date, created-by, invoice-no, totals)
1533
- */
1534
- 'custom.buildOrderAttributes': (
1535
- value: any,
1536
- data: any,
1537
- config: any,
1538
- helpers: any
1539
- ): Array<{ name: string; type: string; value: any }> => {
1540
- const sfccData = data.root || data || {};
1541
- const order = helpers.get(sfccData, 'orders.order');
1542
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1543
-
1544
- // Order date
1545
- const orderDate = helpers.get(order, 'order-date');
1546
- if (orderDate) {
1547
- attributes.push({
1548
- name: 'order-date',
1549
- type: 'String',
1550
- value: orderDate,
1551
- });
1552
- }
1553
-
1554
- // Created by
1555
- const createdBy = helpers.get(order, 'created-by');
1556
- if (createdBy) {
1557
- attributes.push({
1558
- name: 'created-by',
1559
- type: 'String',
1560
- value: createdBy,
1561
- });
1562
- }
1563
-
1564
- // Invoice number
1565
- const invoiceNo = helpers.get(order, 'invoice-no');
1566
- if (invoiceNo) {
1567
- attributes.push({
1568
- name: 'invoice-no',
1569
- type: 'String',
1570
- value: invoiceNo,
1571
- });
1572
- }
1573
-
1574
- // Order totals as JSON
1575
- const totals = helpers.get(order, 'totals');
1576
- if (totals) {
1577
- attributes.push({
1578
- name: 'totals',
1579
- type: 'JSON',
1580
- value: JSON.stringify(totals),
1581
- });
1582
- }
1583
-
1584
- return attributes;
1585
- },
1586
- };
1587
- ```
1588
-
1589
- ### 6. Additional Resolvers (src/resolvers/fulfillment-resolvers.ts, item-resolvers.ts, payment-resolvers.ts)
1590
-
1591
- ```typescript
1592
- /**
1593
- * Fulfillment Resolvers
1594
- * Handle SFCC shipment data → Fluent fulfilmentChoices
1595
- */
1596
-
1597
- export const fulfillmentResolvers: ResolverMap = {
1598
- /**
1599
- * Extract first name from SFCC customer-name field
1600
- * Example: "John Smith" → "John"
1601
- */
1602
- 'custom.extractFirstName': (value: any, data: any, config: any, helpers: any): string => {
1603
- if (!value) return '';
1604
- const parts = String(value).trim().split(/\s+/);
1605
- return parts[0] || '';
1606
- },
1607
-
1608
- /**
1609
- * Extract last name from SFCC customer-name field
1610
- * Example: "John Smith" → "Smith"
1611
- */
1612
- 'custom.extractLastName': (value: any, data: any, config: any, helpers: any): string => {
1613
- if (!value) return '';
1614
- const parts = String(value).trim().split(/\s+/);
1615
- return parts.length > 1 ? parts.slice(1).join(' ') : '';
1616
- },
1617
-
1618
- /**
1619
- * Combine billing address first-name and last-name
1620
- * Example: "John" + "Smith" → "John Smith"
1621
- */
1622
- 'custom.combineBillingName': (value: any, data: any, config: any, helpers: any): string => {
1623
- const sfccData = data.root || data || {};
1624
- const firstName = value || helpers.get(sfccData, 'orders.order.customer.billing-address.first-name') || '';
1625
- const lastName = helpers.get(sfccData, 'orders.order.customer.billing-address.last-name') || '';
1626
- return `${firstName} ${lastName}`.trim();
1627
- },
1628
-
1629
- /**
1630
- * Combine shipping address first-name and last-name
1631
- * Example: "Acme" + "Store" → "Acme Store"
1632
- */
1633
- 'custom.combineShippingName': (value: any, data: any, config: any, helpers: any): string => {
1634
- const sfccData = data.root || data || {};
1635
- const firstName = value || helpers.get(sfccData, 'orders.order.shipments.shipment.shipping-address.first-name') || '';
1636
- const lastName = helpers.get(sfccData, 'orders.order.shipments.shipment.shipping-address.last-name') || '';
1637
- return `${firstName} ${lastName}`.trim();
1638
- },
1639
-
1640
- /**
1641
- * Normalize country code to uppercase
1642
- * Example: "us" → "US", "US" → "US"
1643
- */
1644
- 'custom.normalizeCountryCode': (value: any, data: any, config: any, helpers: any): string => {
1645
- if (!value) return config.defaultCountry || 'US';
1646
- return String(value).toUpperCase();
1647
- },
1648
-
1649
- /**
1650
- * Map SFCC shipping-method to Fluent fulfilment type
1651
- * Example: "ISPU" → "BOPIS", "STANDARD_SHIPPING" → "SHIP_TO_HOME"
1652
- */
1653
- 'custom.mapShippingMethodToFulfilmentType': (value: any, data: any, config: any, helpers: any): string => {
1654
- const shippingMethod = String(value || '').toUpperCase();
1655
- const mapping: Record<string, string> = {
1656
- ISPU: 'BOPIS',
1657
- STANDARD_SHIPPING: 'SHIP_TO_HOME',
1658
- EXPRESS_SHIPPING: 'SHIP_TO_HOME',
1659
- GROUND_SHIPPING: 'SHIP_TO_HOME',
1660
- };
1661
- return mapping[shippingMethod] || 'SHIP_TO_HOME';
1662
- },
1663
-
1664
- /**
1665
- * Build billing address attributes (phone)
1666
- */
1667
- 'custom.buildBillingAddressAttributes': (
1668
- value: any,
1669
- data: any,
1670
- config: any,
1671
- helpers: any
1672
- ): Array<{ name: string; type: string; value: any }> => {
1673
- const sfccData = data.root || data || {};
1674
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1675
-
1676
- const phone = helpers.get(sfccData, 'orders.order.customer.billing-address.phone');
1677
- if (phone) {
1678
- attributes.push({ name: 'phone', type: 'String', value: String(phone) });
1679
- }
1680
-
1681
- return attributes;
1682
- },
1683
-
1684
- /**
1685
- * Build fulfilment address attributes (phone)
1686
- */
1687
- 'custom.buildFulfilmentAddressAttributes': (
1688
- value: any,
1689
- data: any,
1690
- config: any,
1691
- helpers: any
1692
- ): Array<{ name: string; type: string; value: any }> => {
1693
- const sfccData = data.root || data || {};
1694
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1695
-
1696
- const phone = helpers.get(sfccData, 'orders.order.shipments.shipment.shipping-address.phone');
1697
- if (phone) {
1698
- attributes.push({ name: 'phone', type: 'String', value: String(phone) });
1699
- }
1700
-
1701
- return attributes;
1702
- },
1703
-
1704
- /**
1705
- * Build fulfilment choices attributes
1706
- * Includes gift flag, totals JSON, and custom attributes
1707
- */
1708
- 'custom.buildFulfilmentChoicesAttributes': (
1709
- value: any,
1710
- data: any,
1711
- config: any,
1712
- helpers: any
1713
- ): Array<{ name: string; type: string; value: any }> => {
1714
- const sfccData = data.root || data || {};
1715
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1716
-
1717
- // Gift flag
1718
- const gift = helpers.get(sfccData, 'orders.order.shipments.shipment.gift');
1719
- if (gift !== undefined) {
1720
- attributes.push({ name: 'gift', type: 'Boolean', value: gift === 'true' || gift === true });
1721
- }
1722
-
1723
- // Shipment totals as JSON
1724
- const totals = helpers.get(sfccData, 'orders.order.shipments.shipment.totals');
1725
- if (totals) {
1726
- attributes.push({
1727
- name: 'totals',
1728
- type: 'JSON',
1729
- value: JSON.stringify(totals),
1730
- });
1731
- }
1732
-
1733
- // Custom attributes (fromStoreId, shipmentType, etc.)
1734
- const customAttrs = helpers.ensureArray(
1735
- helpers.get(sfccData, 'orders.order.shipments.shipment.custom-attributes.custom-attribute')
1736
- );
1737
- for (const attr of customAttrs) {
1738
- const attrId = attr['@attribute-id'];
1739
- const attrValue = attr._ || attr['#text'] || attr;
1740
- if (attrId && attrValue !== undefined) {
1741
- attributes.push({
1742
- name: attrId,
1743
- type: 'String',
1744
- value: String(attrValue),
1745
- });
1746
- }
1747
- }
1748
-
1749
- return attributes;
1750
- },
1751
- };
1752
-
1753
- /**
1754
- * Item Resolvers
1755
- * Handle SFCC product-lineitem data → Fluent order items
1756
- */
1757
-
1758
- export const itemResolvers: ResolverMap = {
1759
- /**
1760
- * Create item reference from position
1761
- * Example: position "1" → "item_1"
1762
- */
1763
- 'custom.createItemRef': (value: any, data: any, config: any, helpers: any): string => {
1764
- const position = String(value || '1');
1765
- return `item_${position}`;
1766
- },
1767
-
1768
- /**
1769
- * Build item attributes
1770
- * Includes position, tax-basis, tax-rate, shipment-id, gift, custom-attributes
1771
- */
1772
- 'custom.buildItemAttributes': (
1773
- value: any,
1774
- data: any,
1775
- config: any,
1776
- helpers: any
1777
- ): Array<{ name: string; type: string; value: any }> => {
1778
- const itemData = data || {};
1779
- const sfccData = data.root || data || {};
1780
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1781
-
1782
- // Position
1783
- const position = itemData.position || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.position');
1784
- if (position) {
1785
- attributes.push({ name: 'position', type: 'String', value: String(position) });
1786
- }
1787
-
1788
- // Tax basis
1789
- const taxBasis = itemData['tax-basis'] || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.tax-basis');
1790
- if (taxBasis) {
1791
- attributes.push({ name: 'tax-basis', type: 'String', value: String(taxBasis) });
1792
- }
1793
-
1794
- // Tax rate
1795
- const taxRate = itemData['tax-rate'] || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.tax-rate');
1796
- if (taxRate) {
1797
- attributes.push({ name: 'tax-rate', type: 'String', value: String(taxRate) });
1798
- }
1799
-
1800
- // Shipment ID
1801
- const shipmentId = itemData['shipment-id'] || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.shipment-id');
1802
- if (shipmentId) {
1803
- attributes.push({ name: 'shipment-id', type: 'String', value: String(shipmentId) });
1804
- }
1805
-
1806
- // Gift flag
1807
- const gift = itemData.gift || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.gift');
1808
- if (gift !== undefined) {
1809
- attributes.push({ name: 'gift', type: 'Boolean', value: gift === 'true' || gift === true });
1810
- }
1811
-
1812
- // Custom attributes (fromStoreId, storeQty, etc.)
1813
- const customAttrs = helpers.ensureArray(
1814
- itemData['custom-attributes']?.['custom-attribute'] ||
1815
- helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.custom-attributes.custom-attribute')
1816
- );
1817
- for (const attr of customAttrs) {
1818
- const attrId = attr['@attribute-id'];
1819
- const attrValue = attr._ || attr['#text'] || attr;
1820
- if (attrId && attrValue !== undefined) {
1821
- attributes.push({
1822
- name: attrId,
1823
- type: 'String',
1824
- value: String(attrValue),
1825
- });
1826
- }
1827
- }
1828
-
1829
- return attributes;
1830
- },
1831
- };
1832
-
1833
- /**
1834
- * Payment Resolvers
1835
- * Handle SFCC payment data → Fluent financialTransactions
1836
- */
1837
-
1838
- export const paymentResolvers: ResolverMap = {
1839
- /**
1840
- * Map SFCC payment method to Fluent payment method
1841
- * Example: "KLARNA" → "KLARNA", "CreditCard" → "CreditCard"
1842
- */
1843
- 'custom.mapPaymentMethod': (value: any, data: any, config: any, helpers: any): string => {
1844
- const methodName = String(value || '').toUpperCase();
1845
- const mapping: Record<string, string> = {
1846
- KLARNA: 'KLARNA',
1847
- CREDITCARD: 'CreditCard',
1848
- PAYPAL: 'PayPal',
1849
- APPLEPAY: 'ApplePay',
1850
- GOOGLEPAY: 'GooglePay',
1851
- };
1852
- return mapping[methodName] || methodName;
1853
- },
1854
-
1855
- /**
1856
- * Build payment attributes
1857
- * Includes processor-id, custom-attributes (klarnaAuthorizationCode, etc.)
1858
- */
1859
- 'custom.buildPaymentAttributes': (
1860
- value: any,
1861
- data: any,
1862
- config: any,
1863
- helpers: any
1864
- ): Array<{ name: string; type: string; value: any }> => {
1865
- const paymentData = data || {};
1866
- const sfccData = data.root || data || {};
1867
- const attributes: Array<{ name: string; type: string; value: any }> = [];
1868
-
1869
- // Processor ID
1870
- const processorId = paymentData['processor-id'] || helpers.get(sfccData, 'orders.order.payments.payment.processor-id');
1871
- if (processorId) {
1872
- attributes.push({ name: 'processor-id', type: 'String', value: String(processorId) });
1873
- }
1874
-
1875
- // Custom attributes
1876
- const customAttrs = helpers.ensureArray(
1877
- paymentData['custom-attributes']?.['custom-attribute'] ||
1878
- helpers.get(sfccData, 'orders.order.payments.payment.custom-attributes.custom-attribute')
1879
- );
1880
- for (const attr of customAttrs) {
1881
- const attrId = attr['@attribute-id'];
1882
- const attrValue = attr._ || attr['#text'] || attr;
1883
- if (attrId && attrValue !== undefined) {
1884
- attributes.push({
1885
- name: attrId,
1886
- type: 'String',
1887
- value: String(attrValue),
1888
- });
1889
- }
1890
- }
1891
-
1892
- return attributes;
1893
- },
1894
- };
1895
- ```
1896
-
1897
- ### 7. Resolver Types (src/resolvers/types.ts)
1898
-
1899
- ```typescript
1900
- /**
1901
- * Type definitions for custom resolvers
1902
- */
1903
-
1904
- export interface ResolverHelpers {
1905
- logger?: any;
1906
- fluentClient?: any;
1907
- get: (obj: any, path: string) => any;
1908
- ensureArray: (val: any) => any[];
1909
- }
1910
-
1911
- export type ResolverFunction = (
1912
- value: any,
1913
- data: any,
1914
- config: any,
1915
- helpers: ResolverHelpers
1916
- ) => any | Promise<any>;
1917
-
1918
- export type ResolverMap = Record<string, ResolverFunction>;
1919
- ```
1920
-
1921
- ### 8. Export All Resolvers (src/resolvers/index.ts)
1922
-
1923
- ```typescript
1924
- /**
1925
- * All custom resolvers for SFCC native mapping
1926
- */
1927
-
1928
- import { orderResolvers } from './order-resolvers';
1929
- import { fulfillmentResolvers } from './fulfillment-resolvers';
1930
- import { itemResolvers } from './item-resolvers';
1931
- import { paymentResolvers } from './payment-resolvers';
1932
-
1933
- export const allResolvers = {
1934
- ...orderResolvers,
1935
- ...fulfillmentResolvers,
1936
- ...itemResolvers,
1937
- ...paymentResolvers,
1938
- };
1939
-
1940
- // Export individual resolver maps for testing
1941
- export { orderResolvers, fulfillmentResolvers, itemResolvers, paymentResolvers };
1942
- ```
1943
-
1944
- ### 9. Versori Deployment Files
1945
-
1946
- #### package.json
1947
-
1948
- ```json
1949
- {
1950
- "name": "sfcc-order-webhook",
1951
- "version": "1.0.0",
1952
- "type": "module",
1953
- "dependencies": {
1954
- "@fluentcommerce/fc-connect-sdk": "^0.1.41",
1955
- "@versori/run": "latest"
1956
- }
1957
- }
1958
- ```
1959
-
1960
- #### import_map.json
1961
-
1962
- ```json
1963
- {
1964
- "imports": {
1965
- "@fluentcommerce/fc-connect-sdk": "https://esm.sh/@fluentcommerce/fc-connect-sdk@0.1.41",
1966
- "@versori/run": "https://esm.sh/@versori/run@latest",
1967
- "node:buffer": "https://deno.land/std@0.224.0/node/buffer.ts",
1968
- "node:fs": "https://deno.land/std@0.224.0/node/fs.ts",
1969
- "node:path": "https://deno.land/std@0.224.0/node/path.ts"
1970
- }
1971
- }
1972
- ```
1973
-
1974
- ---
1975
-
1976
- ## Common Issues
1977
-
1978
- ### Issue 1: "Missing required field: orders.order"
1979
-
1980
- **Cause**: XML structure doesn't match expected SFCC format
1981
-
1982
- **Solution**: Validate XML structure
1983
-
1984
- ```typescript
1985
- // Check structure
1986
- if (!sfccData.orders?.order) {
1987
- throw new Error('Invalid XML structure: missing orders.order');
1988
- }
1989
- ```
1990
-
1991
- ### Issue 2: "Order type is undefined"
1992
-
1993
- **Cause**: Resolver not deriving order type correctly
1994
-
1995
- **Solution**: Check shipments array
1996
-
1997
- ```typescript
1998
- const shipments = helpers.ensureArray(helpers.get(sfccData, 'orders.order.shipments.shipment'));
1999
- return shipments.length > 1 ? 'MULTI' : 'HD';
2000
- ```
2001
-
2002
- ### Issue 3: "Expected array at path but got object"
2003
-
2004
- **Cause**: XML parser doesn't create arrays for single items
2005
-
2006
- **Solution**: Use `helpers.ensureArray()` in resolvers
2007
-
2008
- ```typescript
2009
- // ✅ CORRECT - wraps single items
2010
- const items = helpers.ensureArray(helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem'));
2011
- ```
2012
-
2013
- ---
2014
-
2015
- ## Production Checklist
2016
-
2017
- Before deploying to production:
2018
-
2019
- - [ ] Set proper environment variables in Versori (RETAILER_ID, PRODUCT_CATALOGUE_REF)
2020
- - [ ] Configure error alerting/monitoring
2021
- - [ ] Test with real SFCC webhook payload
2022
- - [ ] Test all resolver edge cases (missing data, null values)
2023
- - [ ] Set up log aggregation
2024
- - [ ] Configure connection retry logic for transient failures
2025
- - [ ] Document any customer-specific field mappings
2026
-
2027
- ---
2028
-
2029
- **Next Steps**: For more advanced SFCC integration patterns, explore the Radial XML version of this template for orders requiring Radial-specific fields.
1
+ ---
2
+ template_id: tpl-webhook-xml-order-ingestion-sfcc
3
+ canonical_filename: template-webhook-xml-order-ingestion.md
4
+ sdk_version: ^0.1.41
5
+ runtime: versori
6
+ direction: ingestion
7
+ source: webhook-xml-sfcc
8
+ destination: fluent-graphql
9
+ entity: order
10
+ format: xml
11
+ logging: versori
12
+ status: production-ready
13
+ last_updated: 2025-11-13
14
+ ---
15
+
16
+ # Template: Webhook - SFCC XML Order Ingestion
17
+
18
+ **FC Connect SDK Use Case Guide**
19
+
20
+ > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
21
+ > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
22
+
23
+ **Context**: Receive SFCC XML orders via Versori HTTP webhook and create orders in Fluent Commerce using GraphQL mutations.
24
+
25
+ **Complexity**: Medium
26
+
27
+ **Runtime**: Versori Platform
28
+
29
+ **Estimated Lines**: ~500 lines (modular structure)
30
+
31
+ ---
32
+
33
+ ## STEP 1: Understand This Template
34
+
35
+ **What This Template Does:**
36
+
37
+ - Versori HTTP webhook endpoint receiving XML order data (SFCC native format)
38
+ - XML parsing with automatic Versori XML-to-object conversion (incoming XML)
39
+ - GraphQL mutation mapping from SFCC native XML structure to Fluent schema
40
+ - Uses ONLY SFCC native fields (orders.order.*) - no Radial XML dependency
41
+ - Custom resolvers for data transformation, name parsing, and order type derivation
42
+ - Error handling and structured response formatting
43
+ - **Response Options**: JSON (default) or XML (with custom Response handler)
44
+ - **Sync + Fire-and-Forget Pattern**: Fast webhook response, background processing
45
+
46
+ **Key SDK Components:**
47
+
48
+ - `createClient()` - Universal client factory (auto-detects Versori context)
49
+ - `GraphQLMutationMapper` - XML/JSON → GraphQL mutation mapping
50
+ - Native Versori `log` - Use `log` from context
51
+ - No XML parsing needed (Versori handles automatically)
52
+
53
+ **Entity Type:**
54
+
55
+ - **Order** - Fluent entity for order creation
56
+ - **GraphQL Mutation** - Uses `createOrder` mutation (not Event API)
57
+
58
+ **Critical Patterns:**
59
+
60
+ - **XML Response Pattern**: Production-ready XML response handling with proper Content-Type headers
61
+ - **Logger Adapter**: Type-safe logging with SDK Logger interface
62
+ - **Sync + Fire-and-Forget**: Webhook validates quickly, returns immediately, processes in background
63
+ - **External JSON Config**: Mapping configuration in separate JSON file (`config/sfcc-to-fluent-order-mapping.json`)
64
+ - **Modular Architecture**: Separate services, workflows, config, types folders
65
+ - **Integration Variables Validation**: Fail-fast validation of required configuration
66
+ - **Initial Deployment Monitoring**: Temporary full payload logging for troubleshooting
67
+ - **SFCC Native Only**: No Radial XML dependency - simpler and more maintainable
68
+
69
+ **When to Use This Template:**
70
+
71
+ - ✅ SFCC native XML order ingestion (orders.order.* structure)
72
+ - ✅ Need fast webhook response (don't wait for order creation)
73
+ - ✅ Single order per webhook call
74
+ - ✅ BOPIS (Buy Online Pick Up In Store) and standard shipping orders
75
+ - ✅ Custom resolvers for complex transformations (name parsing, address normalization)
76
+ - ✅ Want simpler template without Radial XML complexity
77
+
78
+ **When NOT to Use:**
79
+
80
+ - ❌ Bulk order processing (use Batch API or scheduled workflows)
81
+ - ❌ Order updates (use GraphQL `updateOrder` mutation)
82
+ - ❌ CSV/JSON formats (use appropriate format template)
83
+ - ❌ Need synchronous order creation (wait for result before responding)
84
+ - ❌ Orders requiring Radial-specific fields (use Radial template instead)
85
+
86
+ ---
87
+
88
+ ## STEP 2: Implementation Prompt for Claude Code
89
+
90
+ **Copy this prompt and send to Claude Code to generate the complete implementation:**
91
+
92
+ ```
93
+ Create a Versori webhook workflow for SFCC XML order ingestion to Fluent Commerce GraphQL.
94
+
95
+ REQUIREMENTS:
96
+ 1. Runtime: Versori Platform (HTTP webhook)
97
+ 2. Source: SFCC XML order data via HTTP POST webhook
98
+ 3. Destination: Fluent Commerce GraphQL API (createOrder mutation)
99
+ 4. Format: XML (Versori auto-parses to object)
100
+ 5. Entity: Order (GraphQL mutation)
101
+
102
+ KEY FEATURES:
103
+ - Sync + fire-and-forget pattern (fast webhook response, background processing)
104
+ - External JSON mapping configuration (config/sfcc-to-fluent-order-mapping.json)
105
+ - Modular architecture (workflows/, services/, config/, types/)
106
+ - SFCC native fields ONLY (no Radial XML)
107
+ - GraphQLMutationMapper with standard map() method (no nodes)
108
+ - Custom resolvers for data transformation
109
+ - Audit trail (save input/output files)
110
+ - Comprehensive error handling with structured logging
111
+
112
+ CRITICAL REQUIREMENTS:
113
+ 1. Webhook Mode: response: { mode: 'sync' } with XML onSuccess/onError handlers
114
+ 2. Response Format: XML with proper Content-Type headers (production standard for SFCC)
115
+ 3. Logger Adapter: Create typed Logger adapter from Versori log
116
+ 4. Background Processing: Fire-and-forget pattern (no await on long operations)
117
+ 5. Mapping Config: External JSON file (config/sfcc-to-fluent-order-mapping.json)
118
+ 6. Custom Resolvers: USE mapWithNodes() when you have custom resolvers (REQUIRED)
119
+ 7. Integration Variables: Fail-fast validation of required config (fluentRetailerId)
120
+ 8. Modular Structure: Separate services/, config/, types/ folders
121
+ 9. Step Documentation: Use visual separator blocks for each workflow step
122
+ 10. Error Handling: Structured error responses with recommendations
123
+
124
+ SDK METHODS TO USE (v0.1.41+):
125
+ - createClient({ ...ctx, log }) - Pass full Versori context
126
+ - Logger adapter pattern - Type-safe logging wrapper
127
+ - new GraphQLMutationMapper(mappingConfig, logger, { customResolvers, fluentClient: client }) - Initialize mapper with resolvers (RECOMMENDED)
128
+ - mapper.mapWithNodes(xmlData, undefined, context) - Map with constructor resolvers (auto-returns query)
129
+ - result.query and result.variables - Auto-generated from mapWithNodes()
130
+ - client.graphql({ query: result.query, variables: result.variables }) - Execute mutation
131
+
132
+ FORBIDDEN PATTERNS:
133
+ - ❌ Inline mapping config (use external JSON)
134
+ - ❌ await on background processing (use fire-and-forget)
135
+ - ❌ Passing log directly to SDK (use Logger adapter)
136
+ - ❌ All code in one file (use modular structure)
137
+ - ❌ async mode webhook (use sync + fire-and-forget)
138
+ - ❌ Radial XML references (use SFCC native only)
139
+ - ❌ buildMutation() calls (mapWithNodes() auto-generates query in v0.1.41+)
140
+ - ❌ Silent config fallbacks (fail-fast on missing required variables)
141
+ ```
142
+
143
+ ---
144
+
145
+ ## STEP 3: Detailed Flow Documentation
146
+
147
+ ### Complete Processing Flow
148
+
149
+ ```
150
+ ┌─────────────────────────────────────────────────────────────┐
151
+ │ 1. WEBHOOK RECEIVED │
152
+ │ POST https://{workspace}.versori.run/sfcc-order-create │
153
+ │ Content-Type: application/xml │
154
+ │ Body: <orders xmlns="..."><order order-no="..."> │
155
+ │ <original-order-no>...</original-order-no> │
156
+ │ <customer>...</customer> │
157
+ │ <product-lineitems>...</product-lineitems> │
158
+ │ <shipments>...</shipments> │
159
+ │ <totals>...</totals> │
160
+ │ <payments>...</payments> │
161
+ │ </order></orders> │
162
+ └────────────────────┬────────────────────────────────────────┘
163
+
164
+
165
+ ┌─────────────────────────────────────────────────────────────┐
166
+ │ 2. QUICK VALIDATION (Synchronous, ~10-50ms) │
167
+ │ - Check fluent_commerce connection exists │
168
+ │ - Validate SFCC XML payload present │
169
+ │ - Validate required fields (original-order-no, customer) │
170
+ │ - Return HTTP 200 OK immediately │
171
+ └────────────────────┬────────────────────────────────────────┘
172
+
173
+
174
+ ┌─────────────────────────────────────────────────────────────┐
175
+ │ 3. BACKGROUND PROCESSING (Fire-and-Forget) │
176
+ │ ┌─────────────────────────────────────────────────────┐ │
177
+ │ │ 3a. Initialize Fluent Client │ │
178
+ │ │ - createClient({ ...ctx, log }) │ │
179
+ │ └─────────────────────────────────────────────────────┘ │
180
+ │ ┌─────────────────────────────────────────────────────┐ │
181
+ │ │ 3b. Validate SFCC Structure │ │
182
+ │ │ - Check required SFCC native fields │ │
183
+ │ │ - Validate order structure │ │
184
+ │ └─────────────────────────────────────────────────────┘ │
185
+ │ ┌─────────────────────────────────────────────────────┐ │
186
+ │ │ 3c. Map SFCC Native XML to GraphQL Variables │ │
187
+ │ │ - Load mapping config from JSON │ │
188
+ │ │ - Use SFCC native fields ONLY (orders.order.*) │ │
189
+ │ │ - GraphQLMutationMapper.mapWithNodes() (REQUIRED for custom resolvers)│ │
190
+ │ │ - Apply custom resolvers │ │
191
+ │ │ - Returns { query, variables } (GraphQLPayload) │ │
192
+ │ └─────────────────────────────────────────────────────┘ │
193
+ │ ┌─────────────────────────────────────────────────────┐ │
194
+ │ │ 3d. Execute GraphQL Mutation │ │
195
+ │ │ - client.graphql(payload) - one simple step! │ │
196
+ │ └─────────────────────────────────────────────────────┘ │
197
+ └─────────────────────────────────────────────────────────────┘
198
+ ```
199
+
200
+ ### Response Timing
201
+
202
+ | Stage | Timing | Blocking |
203
+ |-------|--------|----------|
204
+ | **Webhook Validation** | ~10-50ms | ✅ Yes (blocks response) |
205
+ | **Background Processing** | ~1000-2000ms | ❌ No (fire-and-forget) |
206
+ | **Total Response Time** | ~10-50ms | ✅ Fast response |
207
+
208
+ **Key Benefit**: Webhook caller receives immediate acknowledgment (~50ms) while order creation happens in background (~1-2s).
209
+
210
+ ---
211
+
212
+ ## STEP 4: Production Modular Structure
213
+
214
+ > **✅ This section shows the COMPLETE production-ready modular structure.**
215
+ > All files are shown with proper imports/exports and folder organization.
216
+
217
+ ### Complete Project Structure
218
+
219
+ ```
220
+ sfcc-xml-order-ingestion/
221
+ ├── package.json # Dependencies and Versori config
222
+ ├── index.ts # Entry point - exports all workflows
223
+ └── src/
224
+ ├── workflows/
225
+ │ └── webhook/
226
+ │ └── order-ingestion.ts # Webhook: Receive SFCC XML orders
227
+
228
+ ├── services/
229
+ │ └── order-processing.service.ts # Shared orchestration logic (reusable)
230
+
231
+ ├── resolvers/
232
+ │ ├── index.ts # Export all resolvers
233
+ │ ├── types.ts # TypeScript interfaces
234
+ │ ├── order-resolvers.ts # Order-level resolvers
235
+ │ ├── fulfillment-resolvers.ts # Shipment/fulfillment resolvers
236
+ │ ├── item-resolvers.ts # Product line item resolvers
237
+ │ └── payment-resolvers.ts # Payment resolvers
238
+
239
+ ├── config/
240
+ │ └── sfcc-to-fluent-order-mapping.json # Mapping configuration (external JSON)
241
+
242
+ └── types/
243
+ └── order.types.ts # TypeScript interfaces
244
+ ```
245
+
246
+ **Why This Structure?**
247
+
248
+ - ✅ **Clear separation**: Webhook handlers vs business logic
249
+ - ✅ **Reusable services**: Order processing logic can be reused
250
+ - ✅ **External config**: Mapping changes don't require code changes
251
+ - ✅ **Custom resolvers**: Separate file for complex transformations
252
+ - ✅ **Type safety**: TypeScript interfaces for better IDE support
253
+ - ✅ **Scalable**: Easy to add new webhooks or services
254
+
255
+ ---
256
+
257
+ ---
258
+
259
+ ## CRITICAL: XML Response Architecture (Production Pattern)
260
+
261
+ ### Overview
262
+
263
+ This template uses **XML responses** as the PRIMARY pattern for SFCC integration. This is the production-proven approach used in real SFCC → Fluent integrations.
264
+
265
+ ### How XML Response Handling Works
266
+
267
+ ```
268
+ ┌─────────────────────────────────────────────────────────────────┐
269
+ │ ARCHITECTURE FLOW │
270
+ ├─────────────────────────────────────────────────────────────────┤
271
+ │ │
272
+ │ 1. Workflow Steps Return XML Strings │
273
+ │ ↓ ctx.data = "<OrderProcessingResponse>...</>" │
274
+ │ │
275
+ │ 2. onSuccess Handler Receives Context │
276
+ │ ↓ ctx = { data: xmlString, executionId: "..." } │
277
+ │ │
278
+ │ 3. Handler Wraps in Response Object │
279
+ │ ↓ new Response(ctx.data, { headers: {...} }) │
280
+ │ │
281
+ │ 4. Framework Streams Response Directly │
282
+ │ ↓ No JSON encoding - pure XML stream │
283
+ │ │
284
+ └─────────────────────────────────────────────────────────────────┘
285
+ ```
286
+
287
+ ### Response Format Examples
288
+
289
+ **Success Response:**
290
+ ```xml
291
+ <?xml version="1.0" encoding="UTF-8"?>
292
+ <OrderProcessingResponse>
293
+ <Status>success</Status>
294
+ <Message>Order successfully processed and created in Fluent Commerce</Message>
295
+ <FluentOrderId>6370</FluentOrderId>
296
+ <FluentOrderRef>0017326966182_G_postman_test_1760703173226</FluentOrderRef>
297
+ <SFCCOrderRef>0017326966182_G_postman_test_1760703173226</SFCCOrderRef>
298
+ <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
299
+ </OrderProcessingResponse>
300
+ ```
301
+
302
+ **Error Response:**
303
+ ```xml
304
+ <?xml version="1.0" encoding="UTF-8"?>
305
+ <OrderProcessingResponse>
306
+ <Status>error</Status>
307
+ <Message>Failed to process order</Message>
308
+ <Error>Missing required field: original-order-no</Error>
309
+ <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
310
+ </OrderProcessingResponse>
311
+ ```
312
+
313
+ ### Key Benefits
314
+
315
+ - ✅ **SFCC Native**: SFCC systems expect XML responses
316
+ - ✅ **Type Safety**: Structured XML schema
317
+ - ✅ **Execution Tracking**: X-Execution-Id header for troubleshooting
318
+ - ✅ **Proper Content-Type**: application/xml; charset=utf-8
319
+ - ✅ **Framework Streaming**: Direct XML stream (no JSON wrapper)
320
+
321
+ ---
322
+
323
+ ## XML Handling Patterns
324
+
325
+ ### Incoming XML (Versori Auto-Parsing)
326
+
327
+ **CRITICAL**: Versori automatically parses XML when `Content-Type: application/xml` is set.
328
+
329
+ ```typescript
330
+ // ✅ CORRECT - Versori auto-parses XML into object
331
+ // When SFCC sends: POST /webhook with Content-Type: application/xml
332
+ // Versori automatically converts XML string → parsed object
333
+ // ctx.data is already a parsed object, NOT a string
334
+
335
+ export const webhook = webhook('order-ingestion', { response: { mode: 'sync' } })
336
+ .then(http('process', { connection: 'fluent_commerce' }, async (ctx) => {
337
+ // ctx.data is already parsed! No manual parsing needed
338
+ const sfccData = ctx.data; // Already an object: { orders: { order: {...} } }
339
+
340
+ // Validate structure
341
+ if (!sfccData.orders?.order) {
342
+ throw new Error('Invalid XML structure: missing orders.order');
343
+ }
344
+
345
+ // Use mapWithNodes() because we have custom resolvers
346
+ // ✅ Resolvers passed in constructor, so pass undefined here (or override if needed)
347
+ const result = await mapper.mapWithNodes(sfccData, undefined, context);
348
+ }));
349
+
350
+ // ❌ WRONG - Don't try to parse manually
351
+ const xmlString = ctx.data; // This is already parsed!
352
+ const parsed = parseXML(xmlString); // Unnecessary!
353
+ ```
354
+
355
+ **Why This Matters:**
356
+ - Versori handles XML parsing automatically
357
+ - `ctx.data` is always a parsed object when Content-Type is `application/xml`
358
+ - No need for `XMLParserService` or manual parsing in Versori workflows
359
+ - Simply validate the structure and use directly
360
+
361
+ ### Outgoing XML (Production-Ready Pattern)
362
+
363
+ **RECOMMENDED FOR SFCC**: Use XML responses with custom handlers for production SFCC integrations.
364
+
365
+ **XML Response Pattern (RECOMMENDED)**
366
+ ```typescript
367
+ import { XMLBuilder } from '@fluentcommerce/fc-connect-sdk';
368
+
369
+ export const webhook = webhook('order-ingestion', {
370
+ response: {
371
+ mode: 'sync',
372
+ /**
373
+ * onSuccess Handler for XML Response
374
+ *
375
+ * This handler wraps the XML response string with proper headers.
376
+ * The workflow steps return XML strings via ctx.data, and this
377
+ * handler wraps them in Response objects for proper streaming.
378
+ *
379
+ * @param ctx - Context containing:
380
+ * - ctx.data: The XML response string from the final workflow step
381
+ * - ctx.executionId: Unique identifier for this execution
382
+ * @returns Response object with XML body and proper headers
383
+ */
384
+ onSuccess: (ctx) => new Response(ctx.data, {
385
+ status: 200,
386
+ headers: {
387
+ 'Content-Type': 'application/xml; charset=utf-8',
388
+ 'X-Execution-Id': ctx.executionId
389
+ }
390
+ }),
391
+ /**
392
+ * onError Handler for XML Error Response
393
+ *
394
+ * This handler wraps error responses with proper status and headers.
395
+ * Returns XML format for consistent error responses.
396
+ *
397
+ * @param ctx - Context containing:
398
+ * - ctx.data: The error XML string from the .catch() step
399
+ * - ctx.executionId: Unique identifier for tracking failed executions
400
+ * @returns Response object with error XML and 500 status
401
+ */
402
+ onError: (ctx) => new Response(ctx.data, {
403
+ status: 500,
404
+ headers: {
405
+ 'Content-Type': 'application/xml; charset=utf-8',
406
+ 'X-Execution-Id': ctx.executionId
407
+ }
408
+ })
409
+ }
410
+ })
411
+ .then(http('process', { connection: 'fluent_commerce' }, async (ctx) => {
412
+ // Process order...
413
+ const orderId = '12345';
414
+ const orderRef = 'ORD-001';
415
+
416
+ // Build XML response string
417
+ const builder = new XMLBuilder({
418
+ xmlDeclaration: true, // Adds <?xml version="1.0" encoding="UTF-8"?>
419
+ prettyPrint: true, // Format with indentation
420
+ encoding: 'UTF-8'
421
+ });
422
+
423
+ const responseData = {
424
+ Status: 'success',
425
+ FluentOrderId: orderId,
426
+ FluentOrderRef: orderRef,
427
+ Timestamp: new Date().toISOString()
428
+ };
429
+
430
+ // Return XML string (onSuccess handler wraps it in Response)
431
+ return builder.build(responseData, 'OrderProcessingResponse');
432
+ }))
433
+ .catch(({ data, log }) => {
434
+ // Return error XML string (onError handler wraps it)
435
+ return `<?xml version="1.0" encoding="UTF-8"?>
436
+ <OrderProcessingResponse>
437
+ <Status>error</Status>
438
+ <Error>${data instanceof Error ? data.message : String(data)}</Error>
439
+ </OrderProcessingResponse>`;
440
+ });
441
+ // Response: <?xml version="1.0"?><OrderProcessingResponse>...
442
+ // Content-Type: application/xml; charset=utf-8
443
+ ```
444
+
445
+ **Alternative: JSON Response (Simple Testing)**
446
+
447
+ For testing or non-SFCC systems, you can use JSON responses:
448
+
449
+ ```typescript
450
+ export const webhook = webhook('order-ingestion', {
451
+ response: { mode: 'sync' }
452
+ })
453
+ .then(http('process', { connection: 'fluent_commerce' }, async (ctx) => {
454
+ // Returns JSON object - Versori auto-encodes
455
+ return {
456
+ success: true,
457
+ orderId: '12345',
458
+ orderRef: 'ORD-001'
459
+ };
460
+ }));
461
+ // Response: {"success":true,"orderId":"12345","orderRef":"ORD-001"}
462
+ // Content-Type: application/json
463
+ ```
464
+
465
+ **Key Decision Points:**
466
+
467
+ | Pattern | Use When | Response Type |
468
+ |---------|----------|---------------|
469
+ | **XML Response** | Production SFCC integration | XML with proper headers |
470
+ | **JSON Response** | Testing, non-SFCC systems | JSON auto-encoded |
471
+
472
+ **Key Points:**
473
+ - ✅ **XML (Recommended)**: Requires custom `onSuccess`/`onError` handlers with `Response` objects
474
+ - ✅ **JSON (Testing)**: Default behavior, no custom handler needed
475
+ - ✅ Workflow steps return raw strings (XML) or objects (JSON)
476
+ - ✅ Response handlers wrap in `Response` objects with proper Content-Type
477
+
478
+ ---
479
+
480
+ ## SDK Methods Used
481
+
482
+ ```typescript
483
+ // Core SDK imports
484
+ // FC Connect SDK v0.1.41+
485
+ // Install: npm install @fluentcommerce/fc-connect-sdk@^0.1.41
486
+ // Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk/
487
+ // GitHub: https://github.com/fluentcommerce/fc-connect-sdk
488
+
489
+ import { createClient, GraphQLMutationMapper, XMLBuilder } from '@fluentcommerce/fc-connect-sdk';
490
+ import type { Logger } from '@fluentcommerce/fc-connect-sdk';
491
+
492
+ // ═══════════════════════════════════════════════════════════════
493
+ // LOGGER ADAPTER PATTERN (Production-Proven)
494
+ // ═══════════════════════════════════════════════════════════════
495
+ // Create typed Logger adapter from Versori log
496
+ // Benefits:
497
+ // - Type safety with SDK Logger interface
498
+ // - Centralized logging (easy to modify all calls)
499
+ // - Clear separation between Versori log and SDK Logger
500
+ const logger: Logger = {
501
+ info: (msg: string, meta?: any) => log.info(msg, meta),
502
+ error: (msg: string, error?: Error, meta?: any) => log.error(msg, meta),
503
+ warn: (msg: string, meta?: any) => log.info(msg, meta),
504
+ debug: (msg: string, meta?: any) => log.info(msg, meta)
505
+ };
506
+
507
+ // Key methods
508
+ createClient(ctx); // Auto-detects Versori context
509
+ // ✅ RECOMMENDED: Pass resolvers in constructor
510
+ new GraphQLMutationMapper(config, logger, { customResolvers, fluentClient: client });
511
+ // ✅ Alternative: Pass resolvers to mapWithNodes (constructor resolvers merged)
512
+ mapper.mapWithNodes(data, resolvers, context);
513
+ // ✅ v0.1.41+: mapWithNodes() auto-returns query and variables
514
+ const result = await mapper.mapWithNodes(data, undefined, context); // Use constructor resolvers
515
+ // result.query - Auto-generated GraphQL mutation
516
+ // result.variables - Auto-wrapped variables (input object)
517
+ client.graphql({ query: result.query, variables: result.variables }); // Execute
518
+ ```
519
+
520
+ ### Why GraphQLMutationMapper (Not UniversalMapper)?
521
+
522
+ **GraphQLMutationMapper** is the right choice for this template because:
523
+
524
+ ✅ **Automatic Mutation Building**: Automatically generates GraphQL mutation query from mapping config
525
+ ✅ **Input Wrapping**: Automatically wraps mapped data in `input` object (Fluent API requirement)
526
+ ✅ **GraphQL-Specific**: Designed specifically for GraphQL mutations with schema awareness
527
+
528
+ **UniversalMapper** would require:
529
+ - ❌ Manual GraphQL mutation query building
530
+ - ❌ Manual `input` wrapping
531
+ - ❌ More boilerplate code
532
+
533
+ **Use UniversalMapper when**: You need general-purpose data transformation (CSV → JSON, GraphQL → Parquet, etc.)
534
+ **Use GraphQLMutationMapper when**: You need XML/JSON → GraphQL mutations (this template's use case)
535
+
536
+ ---
537
+
538
+ ## Complete Working Code
539
+
540
+ ### 1. Entry Point (index.ts)
541
+
542
+ ```typescript
543
+ /**
544
+ * Entry point - Export all workflows for Versori platform
545
+ */
546
+
547
+ // Webhook workflows
548
+ export { sfccOrderCreate } from './workflows/webhook/sfcc-order-create';
549
+ ```
550
+
551
+ ### 2. Workflow Entry Point (workflows/webhook/sfcc-order-create.ts)
552
+
553
+ ```typescript
554
+ /**
555
+ * ═══════════════════════════════════════════════════════════════════════════════
556
+ * SFCC → FLUENT ORDER CREATE WEBHOOK (Production Pattern)
557
+ * ═══════════════════════════════════════════════════════════════════════════════
558
+ *
559
+ * PURPOSE:
560
+ * This webhook receives order data from Salesforce Commerce Cloud (SFCC) as XML
561
+ * in the request body. It validates, transforms, and creates orders in Fluent
562
+ * Commerce via GraphQL mutations.
563
+ *
564
+ * WEBHOOK CONFIGURATION:
565
+ * - Response mode: synchronous (caller waits for response)
566
+ * - Response format: XML (OrderProcessingResponse)
567
+ * - CORS: enabled (allows cross-origin requests)
568
+ * - Expected payload: XML in request body (Content-Type: application/xml)
569
+ *
570
+ * ARCHITECTURE:
571
+ * Uses production-proven patterns from real SFCC integrations:
572
+ * - XML response handling with proper Content-Type headers
573
+ * - Logger adapter for type-safe logging
574
+ * - Fire-and-forget background processing
575
+ * - Fail-fast validation of required configuration
576
+ * - Visual step documentation blocks
577
+ *
578
+ * WORKFLOW STEPS:
579
+ * 1. Validate XML structure and required fields
580
+ * 2. Apply mapping with custom resolvers
581
+ * 3. Execute GraphQL mutation to create order
582
+ * 4. Build XML response with order creation results
583
+ * ═══════════════════════════════════════════════════════════════════════════════
584
+ */
585
+
586
+ import { webhook, http, fn } from '@versori/run';
587
+ import type { Context } from '@versori/run';
588
+ import { createClient, GraphQLMutationMapper } from '@fluentcommerce/fc-connect-sdk';
589
+ import type { Logger } from '@fluentcommerce/fc-connect-sdk';
590
+ import { allResolvers } from '../../resolvers';
591
+ import mappingConfig from '../../config/sfcc-to-fluent-order-mapping.json' with { type: 'json' };
592
+ import { XMLBuilder } from '@fluentcommerce/fc-connect-sdk';
593
+
594
+ /**
595
+ * ═══════════════════════════════════════════════════════════════════════════════
596
+ * XML RESPONSE ARCHITECTURE (Production Pattern)
597
+ * ═══════════════════════════════════════════════════════════════════════════════
598
+ *
599
+ * This webhook uses XML responses as the PRIMARY pattern for SFCC integration.
600
+ * The onSuccess and onError handlers wrap XML strings in Response objects with
601
+ * proper Content-Type headers.
602
+ *
603
+ * RESPONSE FORMAT:
604
+ * Success:
605
+ * <?xml version="1.0" encoding="UTF-8"?>
606
+ * <OrderProcessingResponse>
607
+ * <Status>success</Status>
608
+ * <Message>Order successfully processed and created in Fluent Commerce</Message>
609
+ * <FluentOrderId>6370</FluentOrderId>
610
+ * <FluentOrderRef>0017326966182_G_postman_test_1760703173226</FluentOrderRef>
611
+ * <SFCCOrderRef>0017326966182_G_postman_test_1760703173226</SFCCOrderRef>
612
+ * <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
613
+ * </OrderProcessingResponse>
614
+ *
615
+ * Error:
616
+ * <?xml version="1.0" encoding="UTF-8"?>
617
+ * <OrderProcessingResponse>
618
+ * <Status>error</Status>
619
+ * <Message>Failed to process order</Message>
620
+ * <Error>Error details here</Error>
621
+ * <Timestamp>2025-11-13T12:12:54.229Z</Timestamp>
622
+ * </OrderProcessingResponse>
623
+ *
624
+ * HOW IT WORKS:
625
+ * 1. Workflow steps return XML strings via ctx.data
626
+ * 2. onSuccess handler receives ctx with the XML string in ctx.data
627
+ * 3. Handler creates Response with Content-Type: application/xml
628
+ * 4. Framework streams the Response directly (no JSON encoding)
629
+ * ═══════════════════════════════════════════════════════════════════════════════
630
+ */
631
+ export const sfccOrderCreate = webhook('sfcc-order-create', {
632
+ response: {
633
+ mode: 'sync',
634
+ /**
635
+ * onSuccess Handler for XML Response
636
+ *
637
+ * This handler wraps the XML response string with proper headers.
638
+ * Similar to production SFCC workflows, this ensures the XML is
639
+ * streamed directly without JSON encoding.
640
+ *
641
+ * @param ctx - Context containing:
642
+ * - ctx.data: The XML response string from the final workflow step
643
+ * - ctx.executionId: Unique identifier for this execution
644
+ * @returns Response object with XML body and proper headers
645
+ */
646
+ onSuccess: (ctx) => new Response(ctx.data, {
647
+ status: 200,
648
+ headers: {
649
+ 'Content-Type': 'application/xml; charset=utf-8',
650
+ 'X-Execution-Id': ctx.executionId
651
+ }
652
+ }),
653
+ /**
654
+ * onError Handler for XML Error Response
655
+ *
656
+ * This handler wraps error responses with proper status and headers.
657
+ * Returns XML format for consistent error responses.
658
+ *
659
+ * @param ctx - Context containing:
660
+ * - ctx.data: The error XML string from the .catch() step
661
+ * - ctx.executionId: Unique identifier for tracking failed executions
662
+ * @returns Response object with error XML and 500 status
663
+ */
664
+ onError: (ctx) => new Response(ctx.data, {
665
+ status: 500,
666
+ headers: {
667
+ 'Content-Type': 'application/xml; charset=utf-8',
668
+ 'X-Execution-Id': ctx.executionId
669
+ }
670
+ })
671
+ },
672
+ cors: true
673
+ }).then(
674
+ /**
675
+ * ═══════════════════════════════════════════════════════════════════════════════
676
+ * STEP 1: VALIDATE XML STRUCTURE
677
+ * ═══════════════════════════════════════════════════════════════════════════════
678
+ *
679
+ * This step validates that the incoming data is properly parsed XML from Versori.
680
+ * Uses fail-fast validation pattern to catch configuration issues early.
681
+ * ═══════════════════════════════════════════════════════════════════════════════
682
+ */
683
+ fn('validate-xml-structure', async ({ data, log, activation }) => {
684
+ // ─────────────────────────────────────────────────────────────────────────
685
+ // LOGGER ADAPTER PATTERN (Production-Proven)
686
+ // ─────────────────────────────────────────────────────────────────────────
687
+ // Create Logger Adapter for type-safe SDK logging
688
+ // Benefits:
689
+ // - Explicit interface contract (TypeScript Logger type)
690
+ // - Centralized logging adapter (easy to modify all log calls)
691
+ // - Type safety enforced
692
+ // - Clear separation between Versori log and SDK Logger
693
+ const logger: Logger = {
694
+ info: (msg: string, meta?: any) => log.info(msg, meta),
695
+ error: (msg: string, error?: Error, meta?: any) => log.error(msg, meta),
696
+ warn: (msg: string, meta?: any) => log.info(msg, meta),
697
+ debug: (msg: string, meta?: any) => log.info(msg, meta)
698
+ };
699
+
700
+ logger.info('Starting SFCC order webhook processing');
701
+
702
+ // ─────────────────────────────────────────────────────────────────────────
703
+ // INITIAL DEPLOYMENT MONITORING PATTERN
704
+ // ─────────────────────────────────────────────────────────────────────────
705
+ // TEMPORARY: Complete XML log for short-term monitoring (first 2-4 weeks)
706
+ // TODO: Remove this log once integration is stable and monitoring is complete
707
+ // Purpose: Helps catch XML structure issues and field mapping problems early
708
+ // Impact: Increases log volume - remove after stabilization
709
+ // ─────────────────────────────────────────────────────────────────────────
710
+ logger.info('Complete incoming XML data', { completeData: data });
711
+
712
+ // ─────────────────────────────────────────────────────────────────────────
713
+ // Validate Pre-Parsed XML Object from Versori
714
+ // ─────────────────────────────────────────────────────────────────────────
715
+ if (typeof data !== 'object' || data === null || !('orders' in data)) {
716
+ throw new Error(
717
+ `Invalid data format. Expected XML (as pre-parsed object with 'orders' root). ` +
718
+ `Received: ${typeof data}. ` +
719
+ `Please ensure you are sending XML with Content-Type: application/xml.`
720
+ );
721
+ }
722
+
723
+ logger.info('XML structure validated successfully');
724
+
725
+ return {
726
+ parsedXml: data,
727
+ logger
728
+ };
729
+ })
730
+ )
731
+
732
+ /**
733
+ * ═══════════════════════════════════════════════════════════════════════════════
734
+ * STEP 2: MAP WITH RESOLVERS USING SDK NODES PATTERN
735
+ * ═══════════════════════════════════════════════════════════════════════════════
736
+ *
737
+ * This step applies field mappings and executes custom resolvers using the SDK's
738
+ * mapWithNodes() method. This method is REQUIRED when using custom resolvers.
739
+ *
740
+ * v0.1.41+ Improvement: mapWithNodes() auto-returns query and variables
741
+ * ═══════════════════════════════════════════════════════════════════════════════
742
+ */
743
+ .then(
744
+ http('map-with-resolvers', {
745
+ connection: 'fluent_commerce'
746
+ }, async (ctx) => {
747
+ const { data, log, activation } = ctx;
748
+ const { parsedXml, logger } = data;
749
+
750
+ logger.info('Mapping SFCC order to Fluent Commerce with custom resolvers');
751
+
752
+ try {
753
+ const fluentClient = await createClient(ctx);
754
+
755
+ // ═════════════════════════════════════════════════════════════════════
756
+ // INTEGRATION VARIABLES VALIDATION (Fail-Fast Pattern)
757
+ // ═════════════════════════════════════════════════════════════════════
758
+ // Production pattern: Validate required configuration early
759
+ // Benefits:
760
+ // - Fails immediately with explicit error message
761
+ // - Tells user EXACTLY what's wrong and where to fix it
762
+ // - Better than silent fallback to default (hides config issues)
763
+ // - Provides audit trail of successful retrieval
764
+ const fluentRetailerId = activation.getVariable('fluentRetailerId') as string;
765
+
766
+ if (!fluentRetailerId) {
767
+ throw new Error(
768
+ 'fluentRetailerId integration variable is required but not set. ' +
769
+ 'Please set this variable in Versori Activations > Variables section.'
770
+ );
771
+ }
772
+
773
+ logger.info('Using retailer ID from integration variables', { retailerId: fluentRetailerId });
774
+
775
+ // ═════════════════════════════════════════════════════════════════════
776
+ // INITIALIZE MAPPER WITH RESOLVERS IN CONSTRUCTOR (Recommended Pattern)
777
+ // ═════════════════════════════════════════════════════════════════════
778
+ // ✅ CORRECT: Pass resolvers in constructor options
779
+ // This is consistent with UniversalMapper and allows resolvers to be reused
780
+ const mapper = new GraphQLMutationMapper(
781
+ mappingConfig as any,
782
+ logger,
783
+ {
784
+ customResolvers: allResolvers, // ✅ Resolvers in constructor
785
+ fluentClient: fluentClient as any,
786
+ }
787
+ );
788
+
789
+ // ═════════════════════════════════════════════════════════════════════
790
+ // CREATE RESOLVER CONTEXT
791
+ // ═════════════════════════════════════════════════════════════════════
792
+ // Pass configuration to custom resolvers
793
+ // SDK automatically provides helpers (get, ensureArray, logger, etc.)
794
+ // You only need to pass custom config and fluentClient
795
+ const resolverContext = {
796
+ fluentClient: fluentClient as any,
797
+ config: {
798
+ retailerId: fluentRetailerId,
799
+ defaultCountry: 'US',
800
+ companyName: 'YourCompany' // Customize per deployment
801
+ },
802
+ // ✅ SDK automatically provides helpers.get, helpers.ensureArray, helpers.logger
803
+ // No need to manually create them - SDK handles XML attributes via path resolver
804
+ // ✅ Resolvers can access config via helpers.context.config.retailerId
805
+ // OR via the config parameter (3rd param): config.retailerId
806
+ };
807
+
808
+ // Execute mapping WITH custom resolvers
809
+ // ✅ v0.1.41+: mapWithNodes() auto-generates query and variables
810
+ // ✅ Resolvers from constructor are automatically used (can override with 2nd param)
811
+ const mappingResult = await mapper.mapWithNodes(
812
+ parsedXml,
813
+ undefined, // Use constructor resolvers (or pass override resolvers here)
814
+ resolverContext
815
+ );
816
+
817
+ if (!mappingResult.success) {
818
+ throw new Error(`Mapping failed: ${mappingResult.errors?.join(', ')}`);
819
+ }
820
+
821
+ const orderRef = mappingResult.data?.input?.ref || 'unknown';
822
+
823
+ logger.info('Successfully mapped order data with custom resolvers', { orderRef });
824
+
825
+ return {
826
+ mappedData: mappingResult.data,
827
+ mappingResult, // ✅ Contains auto-generated query and variables
828
+ orderRef,
829
+ fluentClient,
830
+ logger
831
+ };
832
+ } catch (error) {
833
+ log.error('Error during mapping with resolvers', {
834
+ error: error instanceof Error ? error.message : String(error),
835
+ stack: error instanceof Error ? error.stack : undefined
836
+ });
837
+ throw error;
838
+ }
839
+ })
840
+ )
841
+
842
+ /**
843
+ * ═══════════════════════════════════════════════════════════════════════════════
844
+ * STEP 3: EXECUTE GRAPHQL MUTATION
845
+ * ═══════════════════════════════════════════════════════════════════════════════
846
+ *
847
+ * Execute the GraphQL mutation using auto-generated query from mapWithNodes().
848
+ * v0.1.41+ automatically wraps variables and generates query.
849
+ * ═══════════════════════════════════════════════════════════════════════════════
850
+ */
851
+ .then(
852
+ http('execute-graphql-mutation', {
853
+ connection: 'fluent_commerce'
854
+ }, async (ctx) => {
855
+ const { data, log } = ctx;
856
+ const { mappingResult, orderRef, fluentClient, logger } = data;
857
+
858
+ logger.info('Executing createOrder GraphQL mutation', { orderRef });
859
+
860
+ try {
861
+ // ✅ v0.1.41+: Use auto-generated query and variables from mapWithNodes()
862
+ // No need to call buildMutation() - query is already generated
863
+ const mutationResult = await (fluentClient as any).graphql({
864
+ query: mappingResult.query, // ✅ Auto-generated mutation query
865
+ variables: mappingResult.variables // ✅ Auto-wrapped variables
866
+ });
867
+
868
+ if (mutationResult.errors && mutationResult.errors.length > 0) {
869
+ logger.error('GraphQL mutation returned errors', {
870
+ errors: mutationResult.errors,
871
+ orderRef
872
+ });
873
+ throw new Error(`GraphQL errors: ${JSON.stringify(mutationResult.errors)}`);
874
+ }
875
+
876
+ const createdOrder = (mutationResult.data as any)?.createOrder;
877
+
878
+ if (!createdOrder) {
879
+ throw new Error('No order data returned from createOrder mutation');
880
+ }
881
+
882
+ // Access fields specified in returnFields (id, ref, status, totalPrice)
883
+ logger.info('Successfully created order in Fluent Commerce', {
884
+ fluentOrderId: createdOrder.id, // From returnFields
885
+ fluentOrderRef: createdOrder.ref, // From returnFields
886
+ status: createdOrder.status, // From returnFields
887
+ totalPrice: createdOrder.totalPrice, // From returnFields
888
+ sfccOrderRef: orderRef
889
+ });
890
+
891
+ return {
892
+ success: true,
893
+ fluentOrderId: createdOrder.id,
894
+ fluentOrderRef: createdOrder.ref,
895
+ sfccOrderRef: orderRef,
896
+ orderData: createdOrder
897
+ };
898
+ } catch (error) {
899
+ log.error('Error executing GraphQL mutation', {
900
+ error: error instanceof Error ? error.message : String(error),
901
+ orderRef
902
+ });
903
+ throw error;
904
+ }
905
+ })
906
+ )
907
+
908
+ /**
909
+ * ═══════════════════════════════════════════════════════════════════════════════
910
+ * STEP 4: BUILD XML RESPONSE
911
+ * ═══════════════════════════════════════════════════════════════════════════════
912
+ *
913
+ * IMPORTANT: Return the XML string here.
914
+ * The onSuccess handler will wrap it in a Response with:
915
+ * - Content-Type: application/xml
916
+ * - Status: 200
917
+ * - X-Execution-Id header
918
+ *
919
+ * This keeps the workflow logic focused on data transformation while
920
+ * the framework handles HTTP response formatting.
921
+ * ═══════════════════════════════════════════════════════════════════════════════
922
+ */
923
+ .then(
924
+ fn('build-xml-response', ({ data, log }) => {
925
+ log.info('Building XML response for SFCC order processing', {
926
+ fluentOrderId: data.fluentOrderId,
927
+ fluentOrderRef: data.fluentOrderRef,
928
+ sfccOrderRef: data.sfccOrderRef
929
+ });
930
+
931
+ // Initialize XML Builder (SDK-provided)
932
+ const builder = new XMLBuilder({
933
+ xmlDeclaration: true, // Adds <?xml version="1.0" encoding="UTF-8"?>
934
+ prettyPrint: true, // Format with indentation (default: true)
935
+ indent: ' ', // Two-space indentation (default)
936
+ encoding: 'UTF-8' // XML encoding (default)
937
+ });
938
+
939
+ // Build XML response data
940
+ const responseData = {
941
+ Status: 'success',
942
+ Message: 'Order successfully processed and created in Fluent Commerce',
943
+ FluentOrderId: data.fluentOrderId,
944
+ FluentOrderRef: data.fluentOrderRef,
945
+ SFCCOrderRef: data.sfccOrderRef,
946
+ Timestamp: new Date().toISOString()
947
+ };
948
+
949
+ // Build XML with root element
950
+ const xmlString = builder.build(responseData, 'OrderProcessingResponse');
951
+
952
+ log.info('Successfully built XML response', {
953
+ orderRef: data.sfccOrderRef,
954
+ xmlLength: xmlString.length
955
+ });
956
+
957
+ /**
958
+ * Return just the XML string here.
959
+ * The onSuccess handler will wrap it in a Response object with:
960
+ * - Content-Type: application/xml
961
+ * - Status: 200
962
+ * - X-Execution-Id header
963
+ */
964
+ return xmlString;
965
+ })
966
+ )
967
+
968
+ /**
969
+ * ═══════════════════════════════════════════════════════════════════════════════
970
+ * ERROR HANDLING
971
+ * ═══════════════════════════════════════════════════════════════════════════════
972
+ *
973
+ * Return error XML string that will be wrapped by onError handler with status 500.
974
+ * ═══════════════════════════════════════════════════════════════════════════════
975
+ */
976
+ .catch(({ data, log }) => {
977
+ log.error('Order processing failed', {
978
+ error: data instanceof Error ? data.message : String(data)
979
+ });
980
+
981
+ /**
982
+ * Build error XML that will be wrapped by onError handler.
983
+ * The onError handler will receive this string in ctx.data and wrap it
984
+ * in a Response object with proper status and headers.
985
+ */
986
+ const errorXml = `<?xml version="1.0" encoding="UTF-8"?>
987
+ <OrderProcessingResponse>
988
+ <Status>error</Status>
989
+ <Message>Failed to process order</Message>
990
+ <Error>${data instanceof Error ? data.message : String(data)}</Error>
991
+ <Timestamp>${new Date().toISOString()}</Timestamp>
992
+ </OrderProcessingResponse>`;
993
+
994
+ /**
995
+ * Return the error XML string.
996
+ * The onError handler will wrap it in a Response with status 500.
997
+ */
998
+ return errorXml;
999
+ });
1000
+ ```
1001
+
1002
+ ### 3. Service Implementation (services/order-ingestion.service.ts)
1003
+
1004
+ ```typescript
1005
+ /**
1006
+ * SFCC Order Ingestion Service
1007
+ *
1008
+ * Orchestrates the complete order ingestion process:
1009
+ * 1. Validate webhook payload
1010
+ * 2. Validate SFCC structure
1011
+ * 3. Apply field mapping with custom resolvers
1012
+ * 4. Create order via GraphQL API
1013
+ * 5. Audit trail (save input/output files)
1014
+ */
1015
+
1016
+ import { createClient, GraphQLMutationMapper } from '@fluentcommerce/fc-connect-sdk';
1017
+ import { allResolvers } from '../resolvers';
1018
+ import mappingConfig from '../config/sfcc-to-fluent-order-mapping.json' with { type: 'json' };
1019
+
1020
+ /**
1021
+ * Process SFCC order ingestion
1022
+ *
1023
+ * @param ctx - Versori context object containing fetch, connections, log, activation, data
1024
+ * @param executionStartTime - Workflow start time for duration tracking
1025
+ */
1026
+ export async function processOrderIngestion(ctx: any, executionStartTime: number) {
1027
+ const { log, data, activation } = ctx;
1028
+
1029
+ log.info('Starting SFCC order processing');
1030
+
1031
+ try {
1032
+ const sfccData = data;
1033
+
1034
+ if (!sfccData) {
1035
+ log.error('No order data received');
1036
+ return {
1037
+ success: false,
1038
+ error: 'No order data received',
1039
+ timestamp: new Date().toISOString(),
1040
+ duration: Date.now() - executionStartTime,
1041
+ };
1042
+ }
1043
+
1044
+ if (!sfccData.orders?.order) {
1045
+ log.error('Invalid order structure: missing orders.order');
1046
+ return {
1047
+ success: false,
1048
+ error: 'Invalid order structure: missing orders.order',
1049
+ timestamp: new Date().toISOString(),
1050
+ duration: Date.now() - executionStartTime,
1051
+ };
1052
+ }
1053
+
1054
+ const order = sfccData.orders.order;
1055
+ if (!order['original-order-no']) {
1056
+ log.error('Missing required field: original-order-no');
1057
+ return {
1058
+ success: false,
1059
+ error: 'Missing required field: original-order-no',
1060
+ timestamp: new Date().toISOString(),
1061
+ duration: Date.now() - executionStartTime,
1062
+ };
1063
+ }
1064
+
1065
+ if (!order.customer?.['customer-no']) {
1066
+ log.error('Missing required field: customer.customer-no');
1067
+ return {
1068
+ success: false,
1069
+ error: 'Missing required field: customer.customer-no',
1070
+ timestamp: new Date().toISOString(),
1071
+ duration: Date.now() - executionStartTime,
1072
+ };
1073
+ }
1074
+
1075
+ const fluentClient = await createClient(ctx);
1076
+ log.info('Fluent client initialized');
1077
+
1078
+ log.info('Applying mapping');
1079
+
1080
+ const retailerId = activation?.getVariable('fluentRetailerId') as string;
1081
+
1082
+ if (!retailerId) {
1083
+ log.error('Missing required variable: fluentRetailerId');
1084
+ return {
1085
+ success: false,
1086
+ error: 'Missing required configuration: fluentRetailerId',
1087
+ timestamp: new Date().toISOString(),
1088
+ duration: Date.now() - executionStartTime,
1089
+ };
1090
+ }
1091
+
1092
+ const productCatalogueRef = activation?.getVariable('productCatalogueRef') as string || 'PC:MASTER:2';
1093
+
1094
+ const mapper = new GraphQLMutationMapper(
1095
+ mappingConfig as any,
1096
+ log,
1097
+ {
1098
+ customResolvers: allResolvers,
1099
+ fluentClient: fluentClient as any,
1100
+ }
1101
+ );
1102
+
1103
+ const resolverContext = {
1104
+ fluentClient: fluentClient as any,
1105
+ config: {
1106
+ retailerId: retailerId,
1107
+ defaultOrderType: 'HD',
1108
+ defaultCountry: 'US',
1109
+ productCatalogueRef: productCatalogueRef,
1110
+ },
1111
+ };
1112
+
1113
+ const result = await mapper.mapWithNodes(sfccData, undefined, resolverContext);
1114
+
1115
+ if (!result.success) {
1116
+ log.error('Mapping failed', { errors: result.errors });
1117
+ return {
1118
+ success: false,
1119
+ error: `Mapping failed: ${result.errors?.join(', ')}`,
1120
+ timestamp: new Date().toISOString(),
1121
+ duration: Date.now() - executionStartTime,
1122
+ };
1123
+ }
1124
+
1125
+ const orderRef = sfccData.orders?.order?.['original-order-no'] || 'unknown';
1126
+
1127
+ log.info('Mapping completed', { orderRef });
1128
+
1129
+ // Save audit trail to KV storage (Versori-compatible - file system is read-only)
1130
+ try {
1131
+ const kv = openKv(':project:');
1132
+ const timestamp = new Date().toISOString();
1133
+ const auditKey = ['orders', 'audit', orderRef, timestamp];
1134
+
1135
+ await kv.set([...auditKey, 'sfcc-input'], sfccData);
1136
+ await kv.set([...auditKey, 'fluent-mapped'], result.data);
1137
+
1138
+ log.info('Audit trail saved to KV storage', { orderRef, timestamp });
1139
+ } catch (kvError: any) {
1140
+ log.warn('Failed to save audit trail to KV', { error: kvError.message });
1141
+ }
1142
+
1143
+ log.info('Creating order in Fluent Commerce', { orderRef });
1144
+
1145
+ const mutationRes = await (fluentClient as any).graphql({
1146
+ query: result.query,
1147
+ variables: result.variables
1148
+ });
1149
+
1150
+ if (mutationRes.errors && mutationRes.errors.length > 0) {
1151
+ log.error('GraphQL mutation failed', { errors: mutationRes.errors });
1152
+
1153
+ // Save error to KV storage
1154
+ try {
1155
+ const kv = openKv(':project:');
1156
+ const timestamp = new Date().toISOString();
1157
+ const auditKey = ['orders', 'audit', orderRef, timestamp, 'fluent-error'];
1158
+ await kv.set(auditKey, mutationRes);
1159
+ } catch (kvError) {
1160
+ // Ignore KV save errors
1161
+ }
1162
+
1163
+ return {
1164
+ success: false,
1165
+ error: `GraphQL mutation failed: ${mutationRes.errors.map((e: any) => e.message).join(', ')}`,
1166
+ timestamp: new Date().toISOString(),
1167
+ duration: Date.now() - executionStartTime,
1168
+ };
1169
+ }
1170
+
1171
+ const createOrderData = (mutationRes as any)?.data?.createOrder;
1172
+
1173
+ if (!createOrderData) {
1174
+ log.error('No order data returned from API');
1175
+ return {
1176
+ success: false,
1177
+ error: 'No order data returned from GraphQL mutation',
1178
+ timestamp: new Date().toISOString(),
1179
+ duration: Date.now() - executionStartTime,
1180
+ };
1181
+ }
1182
+
1183
+ // Save successful response to KV storage
1184
+ try {
1185
+ const kv = openKv(':project:');
1186
+ const timestamp = new Date().toISOString();
1187
+ const auditKey = ['orders', 'audit', orderRef, timestamp, 'fluent-response'];
1188
+ await kv.set(auditKey, mutationRes);
1189
+ } catch (kvError) {
1190
+ // Ignore KV save errors
1191
+ }
1192
+
1193
+ log.info('Order created in Fluent Commerce', {
1194
+ orderId: createOrderData?.id,
1195
+ orderRef: createOrderData?.ref,
1196
+ });
1197
+
1198
+ return {
1199
+ success: true,
1200
+ data: {
1201
+ sfccOrderRef: orderRef,
1202
+ fluentOrderId: createOrderData?.id,
1203
+ fluentOrderRef: createOrderData?.ref,
1204
+ timestamp: new Date().toISOString(),
1205
+ },
1206
+ duration: Date.now() - executionStartTime,
1207
+ };
1208
+ } catch (error: any) {
1209
+ log.error('Fatal error', {
1210
+ error: error instanceof Error ? error.message : String(error),
1211
+ stack: error instanceof Error ? error.stack : undefined,
1212
+ });
1213
+
1214
+ return {
1215
+ success: false,
1216
+ error: error instanceof Error ? error.message : String(error),
1217
+ timestamp: new Date().toISOString(),
1218
+ duration: Date.now() - executionStartTime,
1219
+ };
1220
+ }
1221
+ }
1222
+ ```
1223
+
1224
+ ### 4. Mapping Configuration (config/sfcc-to-fluent-order-mapping.json)
1225
+
1226
+ ```json
1227
+ {
1228
+ "direction": "ingest",
1229
+ "sourceFormat": "xml",
1230
+ "mutation": "createOrder",
1231
+ "returnFields": ["id", "ref", "status", "totalPrice"],
1232
+ "fields": {
1233
+ "ref": {
1234
+ "source": "orders.order.original-order-no",
1235
+ "resolver": "sdk.toString",
1236
+ "comment": "Order reference from SFCC native field (REQUIRED)"
1237
+ },
1238
+ "type": {
1239
+ "resolver": "custom.deriveOrderType",
1240
+ "comment": "Derive from shipment count: 1 shipment = HD, multiple = MULTI (REQUIRED)"
1241
+ },
1242
+ "retailer.id": {
1243
+ "value": "${RETAILER_ID}",
1244
+ "comment": "From environment variable or configuration (REQUIRED)"
1245
+ },
1246
+ "totalPrice": {
1247
+ "source": "orders.order.totals.order-total.net-price",
1248
+ "resolver": "sdk.parseFloat",
1249
+ "comment": "Order total net price from SFCC totals (REQUIRED)"
1250
+ },
1251
+ "totalTaxPrice": {
1252
+ "source": "orders.order.totals.order-total.tax",
1253
+ "resolver": "sdk.parseFloat",
1254
+ "comment": "Order total tax from SFCC totals (REQUIRED)"
1255
+ },
1256
+ "attributes": {
1257
+ "resolver": "custom.buildOrderAttributes",
1258
+ "comment": "Order-level attributes: order-date, created-by, invoice-no, totals JSON"
1259
+ },
1260
+ "customer": {
1261
+ "fields": {
1262
+ "ref": {
1263
+ "source": "orders.order.customer.customer-no",
1264
+ "resolver": "sdk.toString",
1265
+ "comment": "Customer reference from SFCC (REQUIRED)"
1266
+ },
1267
+ "firstName": {
1268
+ "source": "orders.order.customer.customer-name",
1269
+ "resolver": "custom.extractFirstName",
1270
+ "comment": "Extract first name from customer-name"
1271
+ },
1272
+ "lastName": {
1273
+ "source": "orders.order.customer.customer-name",
1274
+ "resolver": "custom.extractLastName",
1275
+ "comment": "Extract last name from customer-name"
1276
+ },
1277
+ "primaryEmail": {
1278
+ "source": "orders.order.customer.customer-email",
1279
+ "resolver": "sdk.toString",
1280
+ "comment": "Customer email from SFCC (REQUIRED)"
1281
+ }
1282
+ }
1283
+ },
1284
+ "billingAddress": {
1285
+ "fields": {
1286
+ "name": {
1287
+ "source": "orders.order.customer.billing-address.first-name",
1288
+ "resolver": "custom.combineBillingName",
1289
+ "comment": "Combine first-name + last-name"
1290
+ },
1291
+ "street": {
1292
+ "source": "orders.order.customer.billing-address.address1",
1293
+ "resolver": "sdk.toString",
1294
+ "comment": "Billing address line 1 (REQUIRED)"
1295
+ },
1296
+ "street2": {
1297
+ "source": "orders.order.customer.billing-address.address2",
1298
+ "resolver": "sdk.toString",
1299
+ "comment": "Billing address line 2 (optional)"
1300
+ },
1301
+ "city": {
1302
+ "source": "orders.order.customer.billing-address.city",
1303
+ "resolver": "sdk.toString",
1304
+ "comment": "Billing city (REQUIRED)"
1305
+ },
1306
+ "state": {
1307
+ "source": "orders.order.customer.billing-address.state-code",
1308
+ "resolver": "sdk.toString",
1309
+ "comment": "Billing state code (REQUIRED)"
1310
+ },
1311
+ "postcode": {
1312
+ "source": "orders.order.customer.billing-address.postal-code",
1313
+ "resolver": "sdk.toString",
1314
+ "comment": "Billing postal code (REQUIRED)"
1315
+ },
1316
+ "country": {
1317
+ "source": "orders.order.customer.billing-address.country-code",
1318
+ "resolver": "custom.normalizeCountryCode",
1319
+ "comment": "Normalize country code (us → US)"
1320
+ }
1321
+ },
1322
+ "attributes": {
1323
+ "resolver": "custom.buildBillingAddressAttributes",
1324
+ "comment": "Billing address phone and custom attributes"
1325
+ }
1326
+ },
1327
+ "items": {
1328
+ "source": "orders.order.product-lineitems.product-lineitem",
1329
+ "isArray": true,
1330
+ "fields": {
1331
+ "ref": {
1332
+ "source": "$.position",
1333
+ "resolver": "custom.createItemRef",
1334
+ "comment": "Item reference from position (REQUIRED)"
1335
+ },
1336
+ "productRef": {
1337
+ "source": "$.product-id",
1338
+ "resolver": "sdk.toString",
1339
+ "comment": "Product reference from SFCC product-id (REQUIRED)"
1340
+ },
1341
+ "productCatalogueRef": {
1342
+ "resolver": "custom.getProductCatalogueRef",
1343
+ "comment": "Default product catalogue"
1344
+ },
1345
+ "quantity": {
1346
+ "source": "$.quantity",
1347
+ "resolver": "sdk.parseFloat",
1348
+ "comment": "Quantity from SFCC (REQUIRED)"
1349
+ },
1350
+ "price": {
1351
+ "source": "$.base-price",
1352
+ "resolver": "sdk.parseFloat",
1353
+ "comment": "Base price per item from SFCC (REQUIRED)"
1354
+ },
1355
+ "totalPrice": {
1356
+ "source": "$.net-price",
1357
+ "resolver": "sdk.parseFloat",
1358
+ "comment": "Total price (net-price) from SFCC (REQUIRED)"
1359
+ },
1360
+ "taxPrice": {
1361
+ "source": "$.tax",
1362
+ "resolver": "sdk.parseFloat",
1363
+ "comment": "Tax amount from SFCC (REQUIRED)"
1364
+ },
1365
+ "currency": {
1366
+ "source": "orders.order.currency",
1367
+ "resolver": "sdk.toString",
1368
+ "comment": "Currency from order level (REQUIRED)"
1369
+ },
1370
+ "attributes": {
1371
+ "resolver": "custom.buildItemAttributes",
1372
+ "comment": "Item attributes: position, tax-basis, tax-rate, shipment-id, gift, custom-attributes"
1373
+ }
1374
+ }
1375
+ },
1376
+ "fulfilmentChoices": {
1377
+ "source": "orders.order.shipments.shipment",
1378
+ "isArray": true,
1379
+ "fields": {
1380
+ "type": {
1381
+ "source": "$.shipping-method",
1382
+ "resolver": "custom.mapShippingMethodToFulfilmentType",
1383
+ "comment": "Map ISPU → BOPIS, STANDARD_SHIPPING → SHIP_TO_HOME (REQUIRED)"
1384
+ },
1385
+ "pickupLocationRef": {
1386
+ "source": "$.custom-attributes.custom-attribute[attribute-id=fromStoreId]",
1387
+ "resolver": "sdk.toString",
1388
+ "comment": "Store ID for BOPIS orders"
1389
+ },
1390
+ "deliveryType": {
1391
+ "source": "orders.order.shipping-lineitems.shipping-lineitem.item-id",
1392
+ "resolver": "sdk.toString",
1393
+ "comment": "Delivery type (STANDARD_SHIPPING, etc.)"
1394
+ },
1395
+ "deliveryAddress": {
1396
+ "fields": {
1397
+ "name": {
1398
+ "source": "$.shipping-address.first-name",
1399
+ "resolver": "custom.combineShippingName",
1400
+ "comment": "Combine first-name + last-name"
1401
+ },
1402
+ "street": {
1403
+ "source": "$.shipping-address.address1",
1404
+ "resolver": "sdk.toString",
1405
+ "comment": "Shipping address line 1 (REQUIRED)"
1406
+ },
1407
+ "street2": {
1408
+ "source": "$.shipping-address.address2",
1409
+ "resolver": "sdk.toString",
1410
+ "comment": "Shipping address line 2 (optional)"
1411
+ },
1412
+ "city": {
1413
+ "source": "$.shipping-address.city",
1414
+ "resolver": "sdk.toString",
1415
+ "comment": "Shipping city (REQUIRED)"
1416
+ },
1417
+ "state": {
1418
+ "source": "$.shipping-address.state-code",
1419
+ "resolver": "sdk.toString",
1420
+ "comment": "Shipping state code (REQUIRED)"
1421
+ },
1422
+ "postcode": {
1423
+ "source": "$.shipping-address.postal-code",
1424
+ "resolver": "sdk.toString",
1425
+ "comment": "Shipping postal code (REQUIRED)"
1426
+ },
1427
+ "country": {
1428
+ "source": "$.shipping-address.country-code",
1429
+ "resolver": "custom.normalizeCountryCode",
1430
+ "comment": "Normalize country code (US → US)"
1431
+ }
1432
+ },
1433
+ "attributes": {
1434
+ "resolver": "custom.buildFulfilmentAddressAttributes",
1435
+ "comment": "Shipping address phone and custom attributes"
1436
+ }
1437
+ },
1438
+ "attributes": {
1439
+ "resolver": "custom.buildFulfilmentChoicesAttributes",
1440
+ "comment": "Fulfilment attributes: gift, totals JSON (merchandize-total, shipping-total, shipment-total), custom-attributes"
1441
+ }
1442
+ }
1443
+ },
1444
+ "financialTransactions": {
1445
+ "source": "orders.order.payments.payment",
1446
+ "isArray": true,
1447
+ "fields": {
1448
+ "ref": {
1449
+ "source": "$.transaction-id",
1450
+ "resolver": "sdk.toString",
1451
+ "comment": "Transaction ID from SFCC (REQUIRED)"
1452
+ },
1453
+ "type": {
1454
+ "value": "AUTHORIZATION",
1455
+ "comment": "Payment type (REQUIRED)"
1456
+ },
1457
+ "amount": {
1458
+ "source": "$.amount",
1459
+ "resolver": "sdk.parseFloat",
1460
+ "comment": "Payment amount from SFCC (REQUIRED)"
1461
+ },
1462
+ "currency": {
1463
+ "source": "orders.order.currency",
1464
+ "resolver": "sdk.toString",
1465
+ "comment": "Currency from order level (REQUIRED)"
1466
+ },
1467
+ "paymentMethod": {
1468
+ "source": "$.custom-method.method-name",
1469
+ "resolver": "custom.mapPaymentMethod",
1470
+ "comment": "Payment method (KLARNA, CreditCard, etc.) (REQUIRED)"
1471
+ },
1472
+ "externalTransactionCode": {
1473
+ "source": "$.custom-attributes.custom-attribute[attribute-id=klarnaAuthorizationCode]",
1474
+ "resolver": "sdk.toString",
1475
+ "comment": "Authorization code (Klarna, etc.)"
1476
+ },
1477
+ "externalTransactionId": {
1478
+ "source": "$.custom-method.custom-attributes.custom-attribute[attribute-id=klarnaClientToken]",
1479
+ "resolver": "sdk.toString",
1480
+ "comment": "External transaction ID (Klarna client token, etc.)"
1481
+ },
1482
+ "attributes": {
1483
+ "resolver": "custom.buildPaymentAttributes",
1484
+ "comment": "Payment attributes: processor-id, custom-attributes"
1485
+ }
1486
+ }
1487
+ }
1488
+ },
1489
+ "returnFields": ["id", "ref", "status", "totalPrice"]
1490
+ }
1491
+ ```
1492
+
1493
+ **Note:** The `returnFields` array specifies which fields are returned in the GraphQL mutation response. These fields are available in `mutationRes.data.createOrder` after executing the mutation. If omitted, defaults to `["id", "ref"]`.
1494
+
1495
+ ### 5. Custom Resolvers (src/resolvers/order-resolvers.ts)
1496
+
1497
+ ```typescript
1498
+ /**
1499
+ * Order Resolvers for SFCC Native → Fluent
1500
+ *
1501
+ * Maps from SFCC native structure to Fluent order fields
1502
+ */
1503
+
1504
+ import type { ResolverMap } from './types';
1505
+
1506
+ export const orderResolvers: ResolverMap = {
1507
+ /**
1508
+ * Derive order type from SFCC order structure
1509
+ * Example: 1 shipment → "HD", multiple shipments → "MULTI"
1510
+ */
1511
+ 'custom.deriveOrderType': (value: any, data: any, config: any, helpers: any): string => {
1512
+ const sfccData = data.root || data || {};
1513
+ const shipments = helpers.ensureArray(helpers.get(sfccData, 'orders.order.shipments.shipment'));
1514
+
1515
+ if (shipments.length > 1) {
1516
+ return 'MULTI';
1517
+ }
1518
+
1519
+ return config.defaultOrderType || 'HD';
1520
+ },
1521
+
1522
+ /**
1523
+ * Get product catalogue reference
1524
+ * Returns default catalogue ref for all items
1525
+ */
1526
+ 'custom.getProductCatalogueRef': (value: any, data: any, config: any, helpers: any): string => {
1527
+ return process.env.PRODUCT_CATALOGUE_REF || 'PC:MASTER:2';
1528
+ },
1529
+
1530
+ /**
1531
+ * Build order-level attributes from SFCC fields
1532
+ * Extracts order metadata (order-date, created-by, invoice-no, totals)
1533
+ */
1534
+ 'custom.buildOrderAttributes': (
1535
+ value: any,
1536
+ data: any,
1537
+ config: any,
1538
+ helpers: any
1539
+ ): Array<{ name: string; type: string; value: any }> => {
1540
+ const sfccData = data.root || data || {};
1541
+ const order = helpers.get(sfccData, 'orders.order');
1542
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1543
+
1544
+ // Order date
1545
+ const orderDate = helpers.get(order, 'order-date');
1546
+ if (orderDate) {
1547
+ attributes.push({
1548
+ name: 'order-date',
1549
+ type: 'String',
1550
+ value: orderDate,
1551
+ });
1552
+ }
1553
+
1554
+ // Created by
1555
+ const createdBy = helpers.get(order, 'created-by');
1556
+ if (createdBy) {
1557
+ attributes.push({
1558
+ name: 'created-by',
1559
+ type: 'String',
1560
+ value: createdBy,
1561
+ });
1562
+ }
1563
+
1564
+ // Invoice number
1565
+ const invoiceNo = helpers.get(order, 'invoice-no');
1566
+ if (invoiceNo) {
1567
+ attributes.push({
1568
+ name: 'invoice-no',
1569
+ type: 'String',
1570
+ value: invoiceNo,
1571
+ });
1572
+ }
1573
+
1574
+ // Order totals as JSON
1575
+ const totals = helpers.get(order, 'totals');
1576
+ if (totals) {
1577
+ attributes.push({
1578
+ name: 'totals',
1579
+ type: 'JSON',
1580
+ value: JSON.stringify(totals),
1581
+ });
1582
+ }
1583
+
1584
+ return attributes;
1585
+ },
1586
+ };
1587
+ ```
1588
+
1589
+ ### 6. Additional Resolvers (src/resolvers/fulfillment-resolvers.ts, item-resolvers.ts, payment-resolvers.ts)
1590
+
1591
+ ```typescript
1592
+ /**
1593
+ * Fulfillment Resolvers
1594
+ * Handle SFCC shipment data → Fluent fulfilmentChoices
1595
+ */
1596
+
1597
+ export const fulfillmentResolvers: ResolverMap = {
1598
+ /**
1599
+ * Extract first name from SFCC customer-name field
1600
+ * Example: "John Smith" → "John"
1601
+ */
1602
+ 'custom.extractFirstName': (value: any, data: any, config: any, helpers: any): string => {
1603
+ if (!value) return '';
1604
+ const parts = String(value).trim().split(/\s+/);
1605
+ return parts[0] || '';
1606
+ },
1607
+
1608
+ /**
1609
+ * Extract last name from SFCC customer-name field
1610
+ * Example: "John Smith" → "Smith"
1611
+ */
1612
+ 'custom.extractLastName': (value: any, data: any, config: any, helpers: any): string => {
1613
+ if (!value) return '';
1614
+ const parts = String(value).trim().split(/\s+/);
1615
+ return parts.length > 1 ? parts.slice(1).join(' ') : '';
1616
+ },
1617
+
1618
+ /**
1619
+ * Combine billing address first-name and last-name
1620
+ * Example: "John" + "Smith" → "John Smith"
1621
+ */
1622
+ 'custom.combineBillingName': (value: any, data: any, config: any, helpers: any): string => {
1623
+ const sfccData = data.root || data || {};
1624
+ const firstName = value || helpers.get(sfccData, 'orders.order.customer.billing-address.first-name') || '';
1625
+ const lastName = helpers.get(sfccData, 'orders.order.customer.billing-address.last-name') || '';
1626
+ return `${firstName} ${lastName}`.trim();
1627
+ },
1628
+
1629
+ /**
1630
+ * Combine shipping address first-name and last-name
1631
+ * Example: "Acme" + "Store" → "Acme Store"
1632
+ */
1633
+ 'custom.combineShippingName': (value: any, data: any, config: any, helpers: any): string => {
1634
+ const sfccData = data.root || data || {};
1635
+ const firstName = value || helpers.get(sfccData, 'orders.order.shipments.shipment.shipping-address.first-name') || '';
1636
+ const lastName = helpers.get(sfccData, 'orders.order.shipments.shipment.shipping-address.last-name') || '';
1637
+ return `${firstName} ${lastName}`.trim();
1638
+ },
1639
+
1640
+ /**
1641
+ * Normalize country code to uppercase
1642
+ * Example: "us" → "US", "US" → "US"
1643
+ */
1644
+ 'custom.normalizeCountryCode': (value: any, data: any, config: any, helpers: any): string => {
1645
+ if (!value) return config.defaultCountry || 'US';
1646
+ return String(value).toUpperCase();
1647
+ },
1648
+
1649
+ /**
1650
+ * Map SFCC shipping-method to Fluent fulfilment type
1651
+ * Example: "ISPU" → "BOPIS", "STANDARD_SHIPPING" → "SHIP_TO_HOME"
1652
+ */
1653
+ 'custom.mapShippingMethodToFulfilmentType': (value: any, data: any, config: any, helpers: any): string => {
1654
+ const shippingMethod = String(value || '').toUpperCase();
1655
+ const mapping: Record<string, string> = {
1656
+ ISPU: 'BOPIS',
1657
+ STANDARD_SHIPPING: 'SHIP_TO_HOME',
1658
+ EXPRESS_SHIPPING: 'SHIP_TO_HOME',
1659
+ GROUND_SHIPPING: 'SHIP_TO_HOME',
1660
+ };
1661
+ return mapping[shippingMethod] || 'SHIP_TO_HOME';
1662
+ },
1663
+
1664
+ /**
1665
+ * Build billing address attributes (phone)
1666
+ */
1667
+ 'custom.buildBillingAddressAttributes': (
1668
+ value: any,
1669
+ data: any,
1670
+ config: any,
1671
+ helpers: any
1672
+ ): Array<{ name: string; type: string; value: any }> => {
1673
+ const sfccData = data.root || data || {};
1674
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1675
+
1676
+ const phone = helpers.get(sfccData, 'orders.order.customer.billing-address.phone');
1677
+ if (phone) {
1678
+ attributes.push({ name: 'phone', type: 'String', value: String(phone) });
1679
+ }
1680
+
1681
+ return attributes;
1682
+ },
1683
+
1684
+ /**
1685
+ * Build fulfilment address attributes (phone)
1686
+ */
1687
+ 'custom.buildFulfilmentAddressAttributes': (
1688
+ value: any,
1689
+ data: any,
1690
+ config: any,
1691
+ helpers: any
1692
+ ): Array<{ name: string; type: string; value: any }> => {
1693
+ const sfccData = data.root || data || {};
1694
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1695
+
1696
+ const phone = helpers.get(sfccData, 'orders.order.shipments.shipment.shipping-address.phone');
1697
+ if (phone) {
1698
+ attributes.push({ name: 'phone', type: 'String', value: String(phone) });
1699
+ }
1700
+
1701
+ return attributes;
1702
+ },
1703
+
1704
+ /**
1705
+ * Build fulfilment choices attributes
1706
+ * Includes gift flag, totals JSON, and custom attributes
1707
+ */
1708
+ 'custom.buildFulfilmentChoicesAttributes': (
1709
+ value: any,
1710
+ data: any,
1711
+ config: any,
1712
+ helpers: any
1713
+ ): Array<{ name: string; type: string; value: any }> => {
1714
+ const sfccData = data.root || data || {};
1715
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1716
+
1717
+ // Gift flag
1718
+ const gift = helpers.get(sfccData, 'orders.order.shipments.shipment.gift');
1719
+ if (gift !== undefined) {
1720
+ attributes.push({ name: 'gift', type: 'Boolean', value: gift === 'true' || gift === true });
1721
+ }
1722
+
1723
+ // Shipment totals as JSON
1724
+ const totals = helpers.get(sfccData, 'orders.order.shipments.shipment.totals');
1725
+ if (totals) {
1726
+ attributes.push({
1727
+ name: 'totals',
1728
+ type: 'JSON',
1729
+ value: JSON.stringify(totals),
1730
+ });
1731
+ }
1732
+
1733
+ // Custom attributes (fromStoreId, shipmentType, etc.)
1734
+ const customAttrs = helpers.ensureArray(
1735
+ helpers.get(sfccData, 'orders.order.shipments.shipment.custom-attributes.custom-attribute')
1736
+ );
1737
+ for (const attr of customAttrs) {
1738
+ const attrId = attr['@attribute-id'];
1739
+ const attrValue = attr._ || attr['#text'] || attr;
1740
+ if (attrId && attrValue !== undefined) {
1741
+ attributes.push({
1742
+ name: attrId,
1743
+ type: 'String',
1744
+ value: String(attrValue),
1745
+ });
1746
+ }
1747
+ }
1748
+
1749
+ return attributes;
1750
+ },
1751
+ };
1752
+
1753
+ /**
1754
+ * Item Resolvers
1755
+ * Handle SFCC product-lineitem data → Fluent order items
1756
+ */
1757
+
1758
+ export const itemResolvers: ResolverMap = {
1759
+ /**
1760
+ * Create item reference from position
1761
+ * Example: position "1" → "item_1"
1762
+ */
1763
+ 'custom.createItemRef': (value: any, data: any, config: any, helpers: any): string => {
1764
+ const position = String(value || '1');
1765
+ return `item_${position}`;
1766
+ },
1767
+
1768
+ /**
1769
+ * Build item attributes
1770
+ * Includes position, tax-basis, tax-rate, shipment-id, gift, custom-attributes
1771
+ */
1772
+ 'custom.buildItemAttributes': (
1773
+ value: any,
1774
+ data: any,
1775
+ config: any,
1776
+ helpers: any
1777
+ ): Array<{ name: string; type: string; value: any }> => {
1778
+ const itemData = data || {};
1779
+ const sfccData = data.root || data || {};
1780
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1781
+
1782
+ // Position
1783
+ const position = itemData.position || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.position');
1784
+ if (position) {
1785
+ attributes.push({ name: 'position', type: 'String', value: String(position) });
1786
+ }
1787
+
1788
+ // Tax basis
1789
+ const taxBasis = itemData['tax-basis'] || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.tax-basis');
1790
+ if (taxBasis) {
1791
+ attributes.push({ name: 'tax-basis', type: 'String', value: String(taxBasis) });
1792
+ }
1793
+
1794
+ // Tax rate
1795
+ const taxRate = itemData['tax-rate'] || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.tax-rate');
1796
+ if (taxRate) {
1797
+ attributes.push({ name: 'tax-rate', type: 'String', value: String(taxRate) });
1798
+ }
1799
+
1800
+ // Shipment ID
1801
+ const shipmentId = itemData['shipment-id'] || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.shipment-id');
1802
+ if (shipmentId) {
1803
+ attributes.push({ name: 'shipment-id', type: 'String', value: String(shipmentId) });
1804
+ }
1805
+
1806
+ // Gift flag
1807
+ const gift = itemData.gift || helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.gift');
1808
+ if (gift !== undefined) {
1809
+ attributes.push({ name: 'gift', type: 'Boolean', value: gift === 'true' || gift === true });
1810
+ }
1811
+
1812
+ // Custom attributes (fromStoreId, storeQty, etc.)
1813
+ const customAttrs = helpers.ensureArray(
1814
+ itemData['custom-attributes']?.['custom-attribute'] ||
1815
+ helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem.custom-attributes.custom-attribute')
1816
+ );
1817
+ for (const attr of customAttrs) {
1818
+ const attrId = attr['@attribute-id'];
1819
+ const attrValue = attr._ || attr['#text'] || attr;
1820
+ if (attrId && attrValue !== undefined) {
1821
+ attributes.push({
1822
+ name: attrId,
1823
+ type: 'String',
1824
+ value: String(attrValue),
1825
+ });
1826
+ }
1827
+ }
1828
+
1829
+ return attributes;
1830
+ },
1831
+ };
1832
+
1833
+ /**
1834
+ * Payment Resolvers
1835
+ * Handle SFCC payment data → Fluent financialTransactions
1836
+ */
1837
+
1838
+ export const paymentResolvers: ResolverMap = {
1839
+ /**
1840
+ * Map SFCC payment method to Fluent payment method
1841
+ * Example: "KLARNA" → "KLARNA", "CreditCard" → "CreditCard"
1842
+ */
1843
+ 'custom.mapPaymentMethod': (value: any, data: any, config: any, helpers: any): string => {
1844
+ const methodName = String(value || '').toUpperCase();
1845
+ const mapping: Record<string, string> = {
1846
+ KLARNA: 'KLARNA',
1847
+ CREDITCARD: 'CreditCard',
1848
+ PAYPAL: 'PayPal',
1849
+ APPLEPAY: 'ApplePay',
1850
+ GOOGLEPAY: 'GooglePay',
1851
+ };
1852
+ return mapping[methodName] || methodName;
1853
+ },
1854
+
1855
+ /**
1856
+ * Build payment attributes
1857
+ * Includes processor-id, custom-attributes (klarnaAuthorizationCode, etc.)
1858
+ */
1859
+ 'custom.buildPaymentAttributes': (
1860
+ value: any,
1861
+ data: any,
1862
+ config: any,
1863
+ helpers: any
1864
+ ): Array<{ name: string; type: string; value: any }> => {
1865
+ const paymentData = data || {};
1866
+ const sfccData = data.root || data || {};
1867
+ const attributes: Array<{ name: string; type: string; value: any }> = [];
1868
+
1869
+ // Processor ID
1870
+ const processorId = paymentData['processor-id'] || helpers.get(sfccData, 'orders.order.payments.payment.processor-id');
1871
+ if (processorId) {
1872
+ attributes.push({ name: 'processor-id', type: 'String', value: String(processorId) });
1873
+ }
1874
+
1875
+ // Custom attributes
1876
+ const customAttrs = helpers.ensureArray(
1877
+ paymentData['custom-attributes']?.['custom-attribute'] ||
1878
+ helpers.get(sfccData, 'orders.order.payments.payment.custom-attributes.custom-attribute')
1879
+ );
1880
+ for (const attr of customAttrs) {
1881
+ const attrId = attr['@attribute-id'];
1882
+ const attrValue = attr._ || attr['#text'] || attr;
1883
+ if (attrId && attrValue !== undefined) {
1884
+ attributes.push({
1885
+ name: attrId,
1886
+ type: 'String',
1887
+ value: String(attrValue),
1888
+ });
1889
+ }
1890
+ }
1891
+
1892
+ return attributes;
1893
+ },
1894
+ };
1895
+ ```
1896
+
1897
+ ### 7. Resolver Types (src/resolvers/types.ts)
1898
+
1899
+ ```typescript
1900
+ /**
1901
+ * Type definitions for custom resolvers
1902
+ */
1903
+
1904
+ export interface ResolverHelpers {
1905
+ logger?: any;
1906
+ fluentClient?: any;
1907
+ get: (obj: any, path: string) => any;
1908
+ ensureArray: (val: any) => any[];
1909
+ }
1910
+
1911
+ export type ResolverFunction = (
1912
+ value: any,
1913
+ data: any,
1914
+ config: any,
1915
+ helpers: ResolverHelpers
1916
+ ) => any | Promise<any>;
1917
+
1918
+ export type ResolverMap = Record<string, ResolverFunction>;
1919
+ ```
1920
+
1921
+ ### 8. Export All Resolvers (src/resolvers/index.ts)
1922
+
1923
+ ```typescript
1924
+ /**
1925
+ * All custom resolvers for SFCC native mapping
1926
+ */
1927
+
1928
+ import { orderResolvers } from './order-resolvers';
1929
+ import { fulfillmentResolvers } from './fulfillment-resolvers';
1930
+ import { itemResolvers } from './item-resolvers';
1931
+ import { paymentResolvers } from './payment-resolvers';
1932
+
1933
+ export const allResolvers = {
1934
+ ...orderResolvers,
1935
+ ...fulfillmentResolvers,
1936
+ ...itemResolvers,
1937
+ ...paymentResolvers,
1938
+ };
1939
+
1940
+ // Export individual resolver maps for testing
1941
+ export { orderResolvers, fulfillmentResolvers, itemResolvers, paymentResolvers };
1942
+ ```
1943
+
1944
+ ### 9. Versori Deployment Files
1945
+
1946
+ #### package.json
1947
+
1948
+ ```json
1949
+ {
1950
+ "name": "sfcc-order-webhook",
1951
+ "version": "1.0.0",
1952
+ "type": "module",
1953
+ "dependencies": {
1954
+ "@fluentcommerce/fc-connect-sdk": "^0.1.41",
1955
+ "@versori/run": "latest"
1956
+ }
1957
+ }
1958
+ ```
1959
+
1960
+ #### import_map.json
1961
+
1962
+ ```json
1963
+ {
1964
+ "imports": {
1965
+ "@fluentcommerce/fc-connect-sdk": "https://esm.sh/@fluentcommerce/fc-connect-sdk@0.1.41",
1966
+ "@versori/run": "https://esm.sh/@versori/run@latest",
1967
+ "node:buffer": "https://deno.land/std@0.224.0/node/buffer.ts",
1968
+ "node:fs": "https://deno.land/std@0.224.0/node/fs.ts",
1969
+ "node:path": "https://deno.land/std@0.224.0/node/path.ts"
1970
+ }
1971
+ }
1972
+ ```
1973
+
1974
+ ---
1975
+
1976
+ ## Common Issues
1977
+
1978
+ ### Issue 1: "Missing required field: orders.order"
1979
+
1980
+ **Cause**: XML structure doesn't match expected SFCC format
1981
+
1982
+ **Solution**: Validate XML structure
1983
+
1984
+ ```typescript
1985
+ // Check structure
1986
+ if (!sfccData.orders?.order) {
1987
+ throw new Error('Invalid XML structure: missing orders.order');
1988
+ }
1989
+ ```
1990
+
1991
+ ### Issue 2: "Order type is undefined"
1992
+
1993
+ **Cause**: Resolver not deriving order type correctly
1994
+
1995
+ **Solution**: Check shipments array
1996
+
1997
+ ```typescript
1998
+ const shipments = helpers.ensureArray(helpers.get(sfccData, 'orders.order.shipments.shipment'));
1999
+ return shipments.length > 1 ? 'MULTI' : 'HD';
2000
+ ```
2001
+
2002
+ ### Issue 3: "Expected array at path but got object"
2003
+
2004
+ **Cause**: XML parser doesn't create arrays for single items
2005
+
2006
+ **Solution**: Use `helpers.ensureArray()` in resolvers
2007
+
2008
+ ```typescript
2009
+ // ✅ CORRECT - wraps single items
2010
+ const items = helpers.ensureArray(helpers.get(sfccData, 'orders.order.product-lineitems.product-lineitem'));
2011
+ ```
2012
+
2013
+ ---
2014
+
2015
+ ## Production Checklist
2016
+
2017
+ Before deploying to production:
2018
+
2019
+ - [ ] Set proper environment variables in Versori (RETAILER_ID, PRODUCT_CATALOGUE_REF)
2020
+ - [ ] Configure error alerting/monitoring
2021
+ - [ ] Test with real SFCC webhook payload
2022
+ - [ ] Test all resolver edge cases (missing data, null values)
2023
+ - [ ] Set up log aggregation
2024
+ - [ ] Configure connection retry logic for transient failures
2025
+ - [ ] Document any customer-specific field mappings
2026
+
2027
+ ---
2028
+
2029
+ **Next Steps**: For more advanced SFCC integration patterns, explore the Radial XML version of this template for orders requiring Radial-specific fields.