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