@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55

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