@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/clients/fluent-client.js +13 -6
  3. package/dist/cjs/utils/pagination-helpers.js +38 -2
  4. package/dist/cjs/versori/fluent-versori-client.js +11 -5
  5. package/dist/esm/clients/fluent-client.js +13 -6
  6. package/dist/esm/utils/pagination-helpers.js +38 -2
  7. package/dist/esm/versori/fluent-versori-client.js +11 -5
  8. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  9. package/dist/tsconfig.tsbuildinfo +1 -1
  10. package/dist/tsconfig.types.tsbuildinfo +1 -1
  11. package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
  12. package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
  13. package/docs/00-START-HERE/cli-documentation-index.md +202 -202
  14. package/docs/00-START-HERE/cli-quick-reference.md +252 -252
  15. package/docs/00-START-HERE/decision-tree.md +552 -552
  16. package/docs/00-START-HERE/getting-started.md +1070 -1070
  17. package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
  18. package/docs/00-START-HERE/readme.md +237 -237
  19. package/docs/00-START-HERE/retailerid-configuration.md +404 -404
  20. package/docs/00-START-HERE/sdk-philosophy.md +794 -794
  21. package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
  22. package/docs/01-TEMPLATES/faq.md +686 -686
  23. package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
  24. package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
  25. package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
  26. package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
  27. package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
  28. package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
  29. package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
  30. package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
  31. package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
  32. package/docs/01-TEMPLATES/readme.md +957 -957
  33. package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
  34. package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
  35. package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
  36. package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
  37. package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
  38. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
  39. package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
  40. package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
  41. package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
  42. package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
  43. package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
  44. package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
  45. package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
  46. package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
  47. package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
  48. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
  49. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
  50. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
  51. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
  52. package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
  53. package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
  54. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
  55. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
  56. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
  57. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
  58. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
  59. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
  60. package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
  61. package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
  62. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
  63. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
  64. package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
  65. package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
  66. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
  67. package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
  68. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
  69. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
  70. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
  71. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
  72. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
  73. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
  74. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
  75. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
  76. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
  77. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
  78. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
  79. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
  80. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
  81. package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
  82. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
  83. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
  84. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
  85. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
  86. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
  87. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
  88. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
  89. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
  90. package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
  91. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
  92. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
  93. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
  94. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
  95. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
  96. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
  97. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
  98. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
  99. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
  100. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
  101. package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
  102. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
  103. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
  104. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
  105. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
  106. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
  107. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
  108. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
  109. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
  110. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
  111. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
  112. package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
  113. package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
  114. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
  115. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
  116. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
  117. package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
  118. package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
  119. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
  120. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
  121. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
  122. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
  123. package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
  124. package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
  125. package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
  126. package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
  127. package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
  128. package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
  129. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
  130. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
  131. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
  132. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
  133. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
  134. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
  135. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
  136. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
  137. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
  138. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
  139. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
  140. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
  141. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
  142. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
  143. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
  144. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
  145. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
  146. package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
  147. package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
  148. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
  149. package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
  150. package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
  151. package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
  152. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
  153. package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
  154. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
  155. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
  156. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
  157. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
  158. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
  159. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
  160. package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
  161. package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
  162. package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
  163. package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
  164. package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
  165. package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
  166. package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
  167. package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
  168. package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
  169. package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
  170. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
  171. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
  172. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
  173. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
  174. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
  175. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
  176. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
  177. package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
  178. package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
  179. package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
  180. package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
  181. package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
  182. package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
  183. package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
  184. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
  185. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
  186. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
  187. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
  188. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
  189. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
  190. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
  191. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
  192. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
  193. package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
  194. package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
  195. package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
  196. package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
  197. package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
  198. package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
  199. package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
  200. package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
  201. package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
  202. package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
  203. package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
  204. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
  205. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
  206. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
  207. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
  208. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
  209. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
  210. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
  211. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
  212. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
  213. package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
  214. package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
  215. package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
  216. package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
  217. package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
  218. package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
  219. package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
  220. package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
  221. package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
  222. package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
  223. package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
  224. package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
  225. package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
  226. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
  227. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
  228. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
  229. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
  230. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
  231. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
  232. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
  233. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
  234. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
  235. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
  236. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
  237. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
  238. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
  239. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
  240. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
  241. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
  242. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
  243. package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
  244. package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
  245. package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
  246. package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
  247. package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
  248. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
  249. package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
  250. package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
  251. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
  252. package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
  253. package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
  254. package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
  255. package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
  256. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
  257. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
  258. package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
  259. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
  260. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
  261. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
  262. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
  263. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
  264. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
  265. package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
  266. package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
  267. package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
  268. package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
  269. package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
  270. package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
  271. package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
  272. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
  273. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
  274. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
  275. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
  276. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
  277. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
  278. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
  279. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
  280. package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
  281. package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
  282. package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
  283. package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
  284. package/docs/02-CORE-GUIDES/readme.md +194 -194
  285. package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
  286. package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
  287. package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
  288. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
  289. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
  290. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
  291. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
  292. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
  293. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
  294. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
  295. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
  296. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
  297. package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
  298. package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
  299. package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
  300. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
  301. package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
  302. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
  303. package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
  304. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
  305. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
  306. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
  307. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
  308. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
  309. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
  310. package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
  311. package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
  312. package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
  313. package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
  314. package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
  315. package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
  316. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
  317. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
  318. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
  319. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
  320. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
  321. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
  322. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
  323. package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
  324. package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
  325. package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
  326. package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
  327. package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
  328. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
  329. package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
  330. package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
  331. package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
  332. package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
  333. package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
  334. package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
  335. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
  336. package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
  337. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
  338. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
  339. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
  340. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
  341. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
  342. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
  343. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
  344. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
  345. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
  346. package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
  347. package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
  348. package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
  349. package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
  350. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
  351. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
  352. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
  353. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
  354. package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
  355. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
  356. package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
  357. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
  358. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
  359. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
  360. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
  361. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
  362. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
  363. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
  364. package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
  365. package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
  366. package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
  367. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
  368. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
  369. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
  370. package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
  371. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
  372. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
  373. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
  374. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
  375. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
  376. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
  377. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
  378. package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
  379. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
  380. package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
  381. package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
  382. package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
  383. package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
  384. package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
  385. package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
  386. package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
  387. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
  388. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
  389. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
  390. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
  391. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
  392. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
  393. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
  394. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
  395. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
  396. package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
  397. package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
  398. package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
  399. package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
  400. package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
  401. package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
  402. package/docs/03-PATTERN-GUIDES/readme.md +159 -159
  403. package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
  404. package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
  405. package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
  406. package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
  407. package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
  408. package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
  409. package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
  410. package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
  411. package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
  412. package/docs/04-REFERENCE/architecture/readme.md +279 -279
  413. package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
  414. package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
  415. package/docs/04-REFERENCE/platforms/readme.md +135 -135
  416. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
  417. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
  418. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
  419. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
  420. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
  421. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
  422. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
  423. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
  424. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
  425. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
  426. package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
  427. package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
  428. package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
  429. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
  430. package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
  431. package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
  432. package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
  433. package/docs/04-REFERENCE/readme.md +148 -148
  434. package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
  435. package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
  436. package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
  437. package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
  438. package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
  439. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
  440. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
  441. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
  442. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
  443. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
  444. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
  445. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
  446. package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
  447. package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
  448. package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
  449. package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
  450. package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
  451. package/docs/04-REFERENCE/schema/readme.md +141 -141
  452. package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
  453. package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
  454. package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
  455. package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
  456. package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
  457. package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
  458. package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
  459. package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
  460. package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
  461. package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
  462. package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
  463. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
  464. package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
  465. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
  466. package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
  467. package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
  468. package/docs/04-REFERENCE/testing/readme.md +86 -86
  469. package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
  470. package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
  471. package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
  472. package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
  473. package/docs/template-loading-matrix.md +242 -242
  474. package/package.json +5 -3
  475. package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
@@ -1,2226 +1,2226 @@
1
- # Versori Pre-Order Allocation Management
2
-
3
- **FC Connect SDK Use Case Guide**
4
-
5
- > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
6
- > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
7
-
8
- **Context**: Real-time pre-order reservation system with future ATP (Available to Promise) calculation and priority queuing
9
-
10
- **Complexity**: High
11
-
12
- **Volume**: High during launches (100-10,000 pre-orders)
13
-
14
- **Latency**: Real-time (< 2 seconds)
15
-
16
- **Runtime**: Versori Platform (HTTP Webhook + Scheduled)
17
-
18
- **Pattern**: Webhook + future ATP check + reservation + priority queue + scheduled release
19
-
20
- **Estimated Lines**: ~1200 lines
21
-
22
- ## What You'll Build
23
-
24
- - **Pre-Order Webhook**: Receive pre-order requests from e-commerce platforms
25
-
26
- - **Future ATP Calculation**: Calculate available inventory for future ship dates
27
- - Query current inventory levels
28
- - Query existing reservations for target date
29
- - Calculate net available quantity
30
- - Apply overbooking strategy (105% of expected inventory)
31
-
32
- - **Reservation Management**: Create and track reservations in Fluent
33
- - Store reservation with priority tier (VIP, Standard)
34
- - Assign queue position within tier
35
- - Set expiration window (7 days before ship date)
36
-
37
- - **Priority Queuing**: VIP customers get priority allocation
38
- - Tier-based scoring (VIP=100, Standard=50)
39
- - First-come-first-served within tier
40
- - Queue position tracking via VersoriKV
41
-
42
- - **Scheduled Release Workflow**: Convert reservations to allocations on ship date
43
- - Query all reservations for current date
44
- - Convert to fulfillment orders
45
- - Handle insufficient inventory (cancel lower priority)
46
- - Email notifications to customers
47
-
48
- - **Cancellation Workflow**: Release reservation and offer to next in queue
49
-
50
- - **Admin Dashboard Export**: Export reservation status to S3
51
-
52
- ## SDK Methods Used
53
-
54
- - `webhook('name', { response: { mode } })` - HTTP webhook endpoints
55
- - `schedule('name', 'cron')` - Scheduled workflows
56
- - `createClient(ctx)` - Create Fluent client
57
- - `client.graphql({ query, variables })` - Execute GraphQL queries/mutations
58
- - `VersoriKVAdapter(openKv())` - State management
59
- - `VersoriFileTracker` - Track processed reservations
60
- - `GraphQLMutationMapper` - Custom mutations for reservations
61
- - `UniversalMapper` - Field transformations
62
- - `XMLBuilder` - Response formatting
63
-
64
- ---
65
-
66
- ## Versori Workflows Structure
67
-
68
- **Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
69
-
70
- **Trigger Types:**
71
- - **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
72
- - **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
73
- - **`http()`** → External API calls (chained from webhook/schedule)
74
- - **`fn()`** → Internal processing (chained from webhook/schedule)
75
-
76
- ### Recommended Project Structure
77
-
78
- ```
79
- pre-order-allocation/
80
- ├── index.ts # Entry point - exports all workflows
81
- └── src/
82
- ├── workflows/
83
- │ ├── webhook/
84
- │ │ └── reservation.ts # Webhook: Create reservations
85
- │ │
86
- │ └── scheduled/
87
- │ └── release-reservations.ts # Scheduled: Release reservations
88
-
89
- ├── services/
90
- │ └── reservation.service.ts # Shared orchestration logic (reusable)
91
-
92
- └── config/
93
- └── reservation-config.json # Configuration
94
- ```
95
-
96
- **Benefits:**
97
- - ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
98
- - ✅ Descriptive file names (easy to browse and understand)
99
- - ✅ Scalable (add new workflows without cluttering)
100
- - ✅ Reusable code in `services/` (DRY principle)
101
- - ✅ Easy to modify individual workflows without affecting others
102
-
103
- ---
104
-
105
- ## Complete Working Code
106
-
107
- ### 1. Main Workflow File: `index.ts`
108
-
109
- ```typescript
110
- /**
111
- * Pre-Order Allocation Management System
112
- *
113
- * Handles high-volume product launches with priority-based reservations
114
- * and future inventory allocation.
115
- *
116
- * Key Features:
117
- * - Real-time ATP (Available to Promise) calculation for future dates
118
- * - Priority queuing (VIP vs Standard customers)
119
- * - Reservation expiration management
120
- * - Overbooking strategy (105% allocation)
121
- * - Scheduled release on ship date
122
- * - Cancellation with queue reallocation
123
- */
124
-
125
- import { webhook, schedule, http, fn } from '@versori/run';
126
- import {
127
- createClient,
128
- VersoriKVAdapter,
129
- VersoriFileTracker,
130
- UniversalMapper,
131
- } from '@fluentcommerce/fc-connect-sdk';
132
-
133
- // =============================================================================
134
- // TYPE DEFINITIONS
135
- // =============================================================================
136
-
137
- interface PreOrderRequest {
138
- orderId: string;
139
- customerId: string;
140
- customerEmail: string;
141
- customerTier: 'VIP' | 'STANDARD'; // Priority tier
142
- items: PreOrderItem[];
143
- shipDate: string; // Future ship date (ISO 8601)
144
- source: string; // E-commerce platform identifier
145
- }
146
-
147
- interface PreOrderItem {
148
- sku: string;
149
- productName: string;
150
- quantity: number;
151
- locationRef: string;
152
- }
153
-
154
- interface ReservationRecord {
155
- reservationId: string;
156
- orderId: string;
157
- customerId: string;
158
- customerEmail: string;
159
- sku: string;
160
- quantity: number;
161
- locationRef: string;
162
- shipDate: string;
163
- priority: number; // Calculated priority score
164
- queuePosition: number;
165
- tier: 'VIP' | 'STANDARD';
166
- status: 'RESERVED' | 'CONFIRMED' | 'CANCELLED' | 'EXPIRED';
167
- createdAt: string;
168
- expiresAt: string; // 7 days before ship date
169
- confirmedAt?: string;
170
- cancelledAt?: string;
171
- }
172
-
173
- interface ATPCalculation {
174
- sku: string;
175
- locationRef: string;
176
- shipDate: string;
177
- currentInventory: number;
178
- existingReservations: number;
179
- expectedArrival: number; // Expected inventory by ship date
180
- availableToPromise: number;
181
- overbookingLimit: number; // 105% of expected
182
- canFulfill: boolean;
183
- }
184
-
185
- // =============================================================================
186
- // WORKFLOW 1: PRE-ORDER WEBHOOK (Real-time)
187
- // =============================================================================
188
-
189
- /**
190
- * Pre-Order Webhook - Receives pre-order from e-commerce platform
191
- *
192
- * Flow:
193
- * 1. Parse and validate pre-order request
194
- * 2. Calculate future ATP for each item
195
- * 3. Create reservation if inventory available
196
- * 4. Assign priority and queue position
197
- * 5. Store reservation in VersoriKV
198
- * 6. Send confirmation response
199
- */
200
- export const preOrderWebhook = webhook('pre-order', {
201
- response: {
202
- mode: 'sync',
203
- // Custom response handler for JSON
204
- onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
205
- status: 200,
206
- headers: { 'Content-Type': 'application/json' }
207
- }),
208
- onError: (ctx) => new Response(JSON.stringify({
209
- success: false,
210
- error: ctx.data instanceof Error ? ctx.data.message : String(ctx.data),
211
- timestamp: new Date().toISOString()
212
- }), {
213
- status: 400,
214
- headers: { 'Content-Type': 'application/json' }
215
- })
216
- },
217
- cors: true
218
- })
219
- // Step 1: Validate pre-order request
220
- .then(fn('validate-request', (ctx) => {
221
- const { data, log } = ctx;
222
-
223
- log.info('🔍 Validating pre-order request');
224
- const request = data as PreOrderRequest;
225
-
226
- // Validation checks
227
- if (!request.orderId || !request.customerId || !request.customerEmail) {
228
- log.error('[PreOrderAllocation] Validation error: Missing required fields', {
229
- recommendation: 'Ensure pre-order request includes orderId, customerId, and customerEmail'
230
- });
231
- throw new Error('Missing required fields: orderId, customerId, or customerEmail');
232
- }
233
-
234
- if (!request.items || request.items.length === 0) {
235
- log.error('[PreOrderAllocation] Validation error: No items in pre-order', {
236
- recommendation: 'Pre-order must contain at least one item'
237
- });
238
- throw new Error('Pre-order must contain at least one item');
239
- }
240
-
241
- if (!request.shipDate) {
242
- log.error('[PreOrderAllocation] Validation error: Missing ship date', {
243
- recommendation: 'Ship date is required for pre-orders'
244
- });
245
- throw new Error('Ship date is required for pre-orders');
246
- }
247
-
248
- // Validate ship date is in the future
249
- const shipDate = new Date(request.shipDate);
250
- const now = new Date();
251
- const daysDiff = Math.floor((shipDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
252
-
253
- if (daysDiff < 7) {
254
- log.error('[PreOrderAllocation] Validation error: Ship date too soon', {
255
- daysDiff,
256
- recommendation: 'Ship date must be at least 7 days in the future for pre-orders'
257
- });
258
- throw new Error(`Ship date must be at least 7 days in the future (got ${daysDiff} days)`);
259
- }
260
-
261
- if (daysDiff > 180) {
262
- log.error('[PreOrderAllocation] Validation error: Ship date too far in future', {
263
- daysDiff,
264
- recommendation: 'Ship date cannot be more than 180 days in the future'
265
- });
266
- throw new Error(`Ship date cannot be more than 180 days in the future (got ${daysDiff} days)`);
267
- }
268
-
269
- log.info('✅ Pre-order request validated', {
270
- orderId: request.orderId,
271
- itemCount: request.items.length,
272
- shipDate: request.shipDate,
273
- daysUntilShip: daysDiff,
274
- tier: request.customerTier || 'STANDARD'
275
- });
276
-
277
- return {
278
- request,
279
- shipDate,
280
- daysUntilShip: daysDiff,
281
- expiresAt: new Date(shipDate.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days before
282
- };
283
- }))
284
-
285
- // Step 2: Calculate future ATP for each item
286
- .then(http('calculate-atp', {
287
- connection: 'fluent_commerce'
288
- }, async (ctx) => {
289
- const { request, shipDate, expiresAt } = ctx.data;
290
- const { log } = ctx;
291
-
292
- log.info('📊 Calculating future ATP for items');
293
-
294
- const startTime = Date.now();
295
- const client = await createClient(ctx, { validateConnection: true });
296
- const atpResults: ATPCalculation[] = [];
297
-
298
- for (const item of request.items) {
299
- try {
300
- // Query current inventory
301
- const inventoryResult = await client.graphql({
302
- query: `
303
- query GetInventory($locationRef: String!, $skuRef: String!) {
304
- inventoryQuantities(
305
- first: 1
306
- locationRef: $locationRef
307
- skuRef: $skuRef
308
- type: "LAST_ON_HAND"
309
- ) {
310
- edges {
311
- node {
312
- id
313
- quantity
314
- expectedOn
315
- }
316
- }
317
- }
318
- }`,
319
- variables: {
320
- locationRef: item.locationRef,
321
- skuRef: item.sku
322
- }
323
- });
324
-
325
- const currentInventory = inventoryResult.data?.inventoryQuantities?.edges?.[0]?.node?.quantity || 0;
326
-
327
- // Query existing reservations for this SKU + ship date
328
- // NOTE: This assumes reservations are stored as custom attributes
329
- // In production, you'd query a custom entity type or use attributes
330
- const reservationsResult = await client.graphql({
331
- query: `
332
- query GetReservations($sku: String!, $shipDate: String!) {
333
- # This is a placeholder - implement based on your reservation storage
334
- # Option 1: Custom entity type
335
- # Option 2: Inventory attributes
336
- # Option 3: External database
337
- inventoryQuantities(
338
- first: 100
339
- skuRef: $sku
340
- status: "RESERVED"
341
- ) {
342
- edges {
343
- node {
344
- id
345
- quantity
346
- attributes {
347
- name
348
- value
349
- }
350
- }
351
- }
352
- }
353
- }
354
- `,
355
- variables: {
356
- sku: item.sku,
357
- shipDate: request.shipDate
358
- }
359
- });
360
-
361
- // Calculate existing reservations
362
- let existingReservations = 0;
363
- const reservationEdges = reservationsResult.data?.inventoryQuantities?.edges || [];
364
- for (const edge of reservationEdges) {
365
- const shipDateAttr = edge.node.attributes?.find((a: any) => a.name === 'shipDate');
366
- if (shipDateAttr?.value === request.shipDate) {
367
- existingReservations += edge.node.quantity || 0;
368
- }
369
- }
370
-
371
- // Query expected arrivals (purchase orders, transfers)
372
- const expectedArrivalResult = await client.graphql({
373
- query: `
374
- query GetExpectedArrivals($locationRef: String!, $skuRef: String!, $beforeDate: String!) {
375
- inventoryQuantities(
376
- first: 50
377
- locationRef: $locationRef
378
- skuRef: $skuRef
379
- type: "EXPECTED"
380
- expectedOnBefore: $beforeDate
381
- ) {
382
- edges {
383
- node {
384
- quantity
385
- expectedOn
386
- }
387
- }
388
- }
389
- }
390
- `,
391
- variables: {
392
- locationRef: item.locationRef,
393
- skuRef: item.sku,
394
- beforeDate: request.shipDate
395
- }
396
- });
397
-
398
- const expectedArrival = expectedArrivalResult.data?.inventoryQuantities?.edges?.reduce(
399
- (sum: number, edge: any) => sum + (edge.node.quantity || 0),
400
- 0
401
- ) || 0;
402
-
403
- // Calculate ATP with overbooking strategy
404
- const totalExpected = currentInventory + expectedArrival;
405
- const overbookingLimit = Math.floor(totalExpected * 1.05); // 105% overbooking
406
- const availableToPromise = overbookingLimit - existingReservations;
407
-
408
- const atp: ATPCalculation = {
409
- sku: item.sku,
410
- locationRef: item.locationRef,
411
- shipDate: request.shipDate,
412
- currentInventory,
413
- existingReservations,
414
- expectedArrival,
415
- availableToPromise,
416
- overbookingLimit,
417
- canFulfill: availableToPromise >= item.quantity
418
- };
419
-
420
- atpResults.push(atp);
421
-
422
- log.info('✅ ATP calculated', {
423
- sku: item.sku,
424
- current: currentInventory,
425
- expected: expectedArrival,
426
- reserved: existingReservations,
427
- atp: availableToPromise,
428
- requested: item.quantity,
429
- canFulfill: atp.canFulfill
430
- });
431
- } catch (error) {
432
- // ? Enhanced: Error logging with recommendations
433
- log.error('[PreOrderAllocation] Failed to calculate ATP for SKU', {
434
- sku: item.sku,
435
- error: error instanceof Error ? error.message : String(error),
436
- errorType: error instanceof Error ? error.constructor.name : 'Error',
437
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
438
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
439
- : error.message?.includes('query') || error.message?.includes('GraphQL')
440
- ? 'Check GraphQL query syntax and inventory query structure'
441
- : error.message?.includes('connection') || error.message?.includes('timeout')
442
- ? 'Check network connectivity and Fluent Commerce API availability'
443
- : 'Review error details and check ATP calculation logic'
444
- });
445
- throw error;
446
- }
447
- }
448
-
449
- // Check if all items can be fulfilled
450
- const allAvailable = atpResults.every(atp => atp.canFulfill);
451
- const duration = Date.now() - startTime;
452
-
453
- log.info(`⏱️ ATP calculation complete (${duration}ms)`, {
454
- totalItems: atpResults.length,
455
- allAvailable
456
- });
457
-
458
- return {
459
- ...ctx.data,
460
- atpResults,
461
- allAvailable,
462
- client
463
- };
464
- }))
465
-
466
- // Step 3: Create reservations or add to waitlist
467
- .then(fn('create-reservations', async ({ data, openKv, log }) => {
468
- const { request, atpResults, allAvailable, expiresAt, client } = data;
469
-
470
- log.info('📦 Creating reservations');
471
-
472
- const kvAdapter = new VersoriKVAdapter(openKv());
473
- const reservationTracker = new VersoriFileTracker(openKv(), 'pre-order-reservations');
474
-
475
- const reservations: ReservationRecord[] = [];
476
- const failedItems: any[] = [];
477
-
478
- // Calculate priority score
479
- const tierScore = request.customerTier === 'VIP' ? 100 : 50;
480
- const timeScore = Date.now(); // Earlier orders get lower scores (better priority)
481
- const priorityScore = tierScore * 1000000 - timeScore; // VIP orders always sort higher
482
-
483
- for (let i = 0; i < request.items.length; i++) {
484
- const item = request.items[i];
485
- const atp = atpResults[i];
486
-
487
- if (!atp.canFulfill) {
488
- failedItems.push({
489
- sku: item.sku,
490
- quantity: item.quantity,
491
- available: atp.availableToPromise,
492
- reason: 'Insufficient inventory available for this ship date'
493
- });
494
- continue;
495
- }
496
-
497
- // Get current queue position for this SKU + ship date
498
- const queueKey = ['queue', atp.sku, atp.locationRef, request.shipDate].join(':');
499
- const queueData = await kvAdapter.get([queueKey]);
500
- const currentQueueLength = (queueData?.value as number) || 0;
501
- const queuePosition = currentQueueLength + 1;
502
-
503
- // Update queue length
504
- await kvAdapter.set([queueKey], queuePosition);
505
-
506
- // Create reservation record
507
- const reservationId = `RES-${request.orderId}-${item.sku}-${Date.now()}`;
508
- const reservation: ReservationRecord = {
509
- reservationId,
510
- orderId: request.orderId,
511
- customerId: request.customerId,
512
- customerEmail: request.customerEmail,
513
- sku: item.sku,
514
- quantity: item.quantity,
515
- locationRef: item.locationRef,
516
- shipDate: request.shipDate,
517
- priority: priorityScore,
518
- queuePosition,
519
- tier: request.customerTier || 'STANDARD',
520
- status: 'RESERVED',
521
- createdAt: new Date().toISOString(),
522
- expiresAt
523
- };
524
-
525
- // Store reservation in KV
526
- await kvAdapter.set(['reservation', reservationId], reservation);
527
-
528
- // Track in indexed tracker for listing
529
- await reservationTracker.markFileProcessed(reservationId, {
530
- orderId: request.orderId,
531
- sku: item.sku,
532
- shipDate: request.shipDate,
533
- priority: priorityScore
534
- });
535
-
536
- // Create reservation in Fluent (as custom inventory type or entity)
537
- try {
538
- await client.graphql({
539
- query: `
540
- mutation CreateReservation($input: InventoryQuantityInput!) {
541
- createInventoryQuantity(input: $input) {
542
- id
543
- ref
544
- quantity
545
- status
546
- }
547
- }
548
- `,
549
- variables: {
550
- input: {
551
- ref: reservationId,
552
- locationRef: item.locationRef,
553
- skuRef: item.sku,
554
- type: 'RESERVED',
555
- status: 'RESERVED',
556
- quantity: item.quantity,
557
- expectedOn: request.shipDate,
558
- attributes: [
559
- { name: 'orderId', value: request.orderId },
560
- { name: 'customerId', value: request.customerId },
561
- { name: 'customerEmail', value: request.customerEmail },
562
- { name: 'tier', value: request.customerTier || 'STANDARD' },
563
- { name: 'priority', value: String(priorityScore) },
564
- { name: 'queuePosition', value: String(queuePosition) },
565
- { name: 'expiresAt', value: expiresAt },
566
- { name: 'reservationId', value: reservationId }
567
- ]
568
- }
569
- }
570
- });
571
-
572
- log.info('✅ Reservation created in Fluent', {
573
- reservationId,
574
- sku: item.sku,
575
- quantity: item.quantity,
576
- priority: priorityScore,
577
- queuePosition
578
- });
579
- } catch (error) {
580
- // ? Enhanced: Error logging with recommendations
581
- log.error('[PreOrderAllocation] Failed to create reservation in Fluent', {
582
- reservationId,
583
- error: error instanceof Error ? error.message : String(error),
584
- errorType: error instanceof Error ? error.constructor.name : 'Error',
585
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
586
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
587
- : error.message?.includes('mutation') || error.message?.includes('GraphQL')
588
- ? 'Check GraphQL mutation syntax and reservation payload structure'
589
- : error.message?.includes('inventory') || error.message?.includes('quantity')
590
- ? 'Check available inventory quantity and reservation limits'
591
- : error.message?.includes('connection') || error.message?.includes('timeout')
592
- ? 'Check network connectivity and Fluent Commerce API availability'
593
- : 'Review error details - reservation is in KV and can be retried'
594
- });
595
- // Continue - reservation is in KV, can be retried
596
- }
597
-
598
- reservations.push(reservation);
599
- }
600
-
601
- return {
602
- success: failedItems.length === 0,
603
- reservations,
604
- failedItems,
605
- orderId: request.orderId,
606
- shipDate: request.shipDate,
607
- tier: request.customerTier || 'STANDARD'
608
- };
609
- }))
610
-
611
- // Step 4: Send email confirmation (placeholder)
612
- .then(fn('send-confirmation', async ({ data, log }) => {
613
- const { success, reservations, failedItems, orderId, shipDate, tier } = data;
614
-
615
- log.info('📧 Sending customer notification');
616
-
617
- // TODO: Integrate with email service (SendGrid, SES, etc.)
618
- // This is a placeholder showing the data structure
619
- const emailPayload = {
620
- to: reservations[0]?.customerEmail,
621
- subject: success
622
- ? `Pre-Order Confirmed - Order ${orderId}`
623
- : `Pre-Order Partially Confirmed - Order ${orderId}`,
624
- body: {
625
- orderId,
626
- shipDate,
627
- tier,
628
- confirmedItems: reservations.map(r => ({
629
- sku: r.sku,
630
- quantity: r.quantity,
631
- queuePosition: r.queuePosition,
632
- expiresAt: r.expiresAt
633
- })),
634
- failedItems,
635
- message: success
636
- ? `Your pre-order has been confirmed! You are ${tier === 'VIP' ? 'priority' : 'standard'} tier.`
637
- : `Some items could not be reserved. Please review below.`
638
- }
639
- };
640
-
641
- log.info('✅ Email prepared', {
642
- to: emailPayload.to,
643
- confirmedCount: reservations.length,
644
- failedCount: failedItems.length
645
- });
646
-
647
- // In production: await emailService.send(emailPayload);
648
-
649
- return {
650
- success,
651
- orderId,
652
- reservations: reservations.map(r => ({
653
- reservationId: r.reservationId,
654
- sku: r.sku,
655
- quantity: r.quantity,
656
- queuePosition: r.queuePosition,
657
- priority: r.priority,
658
- expiresAt: r.expiresAt
659
- })),
660
- failedItems,
661
- message: success
662
- ? `All items reserved successfully`
663
- : `${reservations.length} items reserved, ${failedItems.length} failed`,
664
- timestamp: new Date().toISOString()
665
- };
666
- }));
667
-
668
- // =============================================================================
669
- // WORKFLOW 2: SCHEDULED RELEASE (Daily at 2 AM)
670
- // =============================================================================
671
-
672
- /**
673
- * Scheduled Release Workflow - Convert reservations to allocations
674
- *
675
- * Runs daily at 2 AM to process reservations for today's ship date
676
- *
677
- * Flow:
678
- * 1. Query all reservations for today's ship date
679
- * 2. Sort by priority (VIP first, then by queue position)
680
- * 3. Convert to fulfillment orders
681
- * 4. Handle insufficient inventory (cancel lower priority)
682
- * 5. Send confirmation/cancellation emails
683
- */
684
- export const scheduledRelease = schedule('release-reservations', '0 2 * * *', async (ctx) => {
685
- const { log, openKv } = ctx;
686
- const startTime = Date.now();
687
-
688
- return http('process-release', {
689
- connection: 'fluent_commerce'
690
- }, async (ctx) => {
691
- const { log } = ctx;
692
- const client = await createClient(ctx, { validateConnection: true });
693
- const kvAdapter = new VersoriKVAdapter(openKv());
694
- const reservationTracker = new VersoriFileTracker(openKv(), 'pre-order-reservations');
695
-
696
- log.info('🚀 Starting scheduled release process');
697
-
698
- const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
699
-
700
- try {
701
- // Step 1: Query all reservations for today
702
- const result = await client.graphql({
703
- query: `
704
- query GetTodayReservations($shipDate: String!) {
705
- inventoryQuantities(
706
- first: 1000
707
- type: "RESERVED"
708
- status: "RESERVED"
709
- expectedOn: $shipDate
710
- ) {
711
- edges {
712
- node {
713
- id
714
- ref
715
- quantity
716
- skuRef
717
- locationRef
718
- attributes {
719
- name
720
- value
721
- }
722
- }
723
- }
724
- }
725
- }
726
- `,
727
- variables: { shipDate: today }
728
- });
729
-
730
- const reservationEdges = result.data?.inventoryQuantities?.edges || [];
731
- log.info(`📋 Found ${reservationEdges.length} reservations for ${today}`);
732
-
733
- // Step 2: Parse and sort by priority
734
- interface ReservationWithPriority {
735
- id: string;
736
- ref: string;
737
- quantity: number;
738
- sku: string;
739
- locationRef: string;
740
- priority: number;
741
- queuePosition: number;
742
- orderId: string;
743
- customerId: string;
744
- customerEmail: string;
745
- tier: string;
746
- reservationId: string;
747
- }
748
-
749
- const reservations: ReservationWithPriority[] = reservationEdges.map((edge: any) => {
750
- const attrs = edge.node.attributes || [];
751
- const getAttr = (name: string) => attrs.find((a: any) => a.name === name)?.value;
752
-
753
- return {
754
- id: edge.node.id,
755
- ref: edge.node.ref,
756
- quantity: edge.node.quantity,
757
- sku: edge.node.skuRef,
758
- locationRef: edge.node.locationRef,
759
- priority: parseInt(getAttr('priority') || '0'),
760
- queuePosition: parseInt(getAttr('queuePosition') || '999'),
761
- orderId: getAttr('orderId') || '',
762
- customerId: getAttr('customerId') || '',
763
- customerEmail: getAttr('customerEmail') || '',
764
- tier: getAttr('tier') || 'STANDARD',
765
- reservationId: getAttr('reservationId') || edge.node.ref
766
- };
767
- });
768
-
769
- // Sort by priority (higher priority = lower number due to negative time component)
770
- reservations.sort((a, b) => b.priority - a.priority);
771
-
772
- log.info('✅ Sorted reservations by priority', {
773
- vipCount: reservations.filter(r => r.tier === 'VIP').length,
774
- standardCount: reservations.filter(r => r.tier === 'STANDARD').length
775
- });
776
-
777
- // Step 3: Group by SKU and location
778
- const groupedBySku = new Map<string, ReservationWithPriority[]>();
779
- for (const reservation of reservations) {
780
- const key = `${reservation.sku}:${reservation.locationRef}`;
781
- if (!groupedBySku.has(key)) {
782
- groupedBySku.set(key, []);
783
- }
784
- groupedBySku.get(key)!.push(reservation);
785
- }
786
-
787
- // Step 4: Process each SKU group
788
- const confirmed: any[] = [];
789
- const cancelled: any[] = [];
790
-
791
- for (const [skuKey, skuReservations] of groupedBySku.entries()) {
792
- const [sku, locationRef] = skuKey.split(':');
793
-
794
- // Query current inventory
795
- const invResult = await client.graphql({
796
- query: `
797
- query GetInventory($sku: String!, $locationRef: String!) {
798
- inventoryQuantities(
799
- first: 1
800
- skuRef: $sku
801
- locationRef: $locationRef
802
- type: "LAST_ON_HAND"
803
- ) {
804
- edges {
805
- node {
806
- quantity
807
- }
808
- }
809
- }
810
- }
811
- `,
812
- variables: { sku, locationRef }
813
- });
814
-
815
- let availableQty = invResult.data?.inventoryQuantities?.edges?.[0]?.node?.quantity || 0;
816
-
817
- log.info(`⚙️ Processing ${skuReservations.length} reservations for ${sku}`, {
818
- available: availableQty,
819
- totalRequested: skuReservations.reduce((sum, r) => sum + r.quantity, 0)
820
- });
821
-
822
- // Allocate inventory by priority
823
- for (const reservation of skuReservations) {
824
- if (availableQty >= reservation.quantity) {
825
- // Confirm reservation - create fulfillment order
826
- try {
827
- await client.graphql({
828
- query: `
829
- mutation CreateFulfillmentOrder($input: CreateOrderInput!) {
830
- createOrder(input: $input) {
831
- id
832
- ref
833
- status
834
- }
835
- }
836
- `,
837
- variables: {
838
- input: {
839
- ref: `FO-${reservation.orderId}-${Date.now()}`,
840
- type: 'FULFILLMENT',
841
- retailerId: '1', // From config
842
- customer: {
843
- ref: reservation.customerId,
844
- email: reservation.customerEmail
845
- },
846
- items: [{
847
- skuRef: reservation.sku,
848
- quantity: reservation.quantity,
849
- locationRef: reservation.locationRef
850
- }],
851
- attributes: [
852
- { name: 'originalOrderId', value: reservation.orderId },
853
- { name: 'reservationId', value: reservation.reservationId },
854
- { name: 'tier', value: reservation.tier },
855
- { name: 'shipDate', value: today }
856
- ]
857
- }
858
- }
859
- });
860
-
861
- // Update reservation status
862
- await client.graphql({
863
- query: `
864
- mutation UpdateReservation($id: ID!, $status: String!) {
865
- updateInventoryQuantity(id: $id, status: $status) {
866
- id
867
- status
868
- }
869
- }
870
- `,
871
- variables: {
872
- id: reservation.id,
873
- status: 'CONFIRMED'
874
- }
875
- });
876
-
877
- // Update KV
878
- const kvRecord = await kvAdapter.get(['reservation', reservation.reservationId]);
879
- if (kvRecord?.value) {
880
- const record = kvRecord.value as ReservationRecord;
881
- record.status = 'CONFIRMED';
882
- record.confirmedAt = new Date().toISOString();
883
- await kvAdapter.set(['reservation', reservation.reservationId], record);
884
- }
885
-
886
- availableQty -= reservation.quantity;
887
- confirmed.push(reservation);
888
-
889
- log.info(`✅ Confirmed reservation: ${reservation.reservationId}`);
890
- } catch (error) {
891
- // ? Enhanced: Error logging with recommendations
892
- log.error('[PreOrderAllocation] Failed to confirm reservation', {
893
- reservationId: reservation.reservationId,
894
- error: error instanceof Error ? error.message : String(error),
895
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
896
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
897
- : error.message?.includes('mutation') || error.message?.includes('GraphQL')
898
- ? 'Check GraphQL mutation syntax and reservation confirmation payload'
899
- : error.message?.includes('inventory') || error.message?.includes('quantity')
900
- ? 'Check available inventory quantity and reservation status'
901
- : 'Review error details and check reservation confirmation logic'
902
- });
903
- }
904
- } else {
905
- // Insufficient inventory - cancel reservation
906
- try {
907
- await client.graphql({
908
- query: `
909
- mutation UpdateReservation($id: ID!, $status: String!) {
910
- updateInventoryQuantity(id: $id, status: $status) {
911
- id
912
- status
913
- }
914
- }
915
- `,
916
- variables: {
917
- id: reservation.id,
918
- status: 'CANCELLED'
919
- }
920
- });
921
-
922
- // Update KV
923
- const kvRecord = await kvAdapter.get(['reservation', reservation.reservationId]);
924
- if (kvRecord?.value) {
925
- const record = kvRecord.value as ReservationRecord;
926
- record.status = 'CANCELLED';
927
- record.cancelledAt = new Date().toISOString();
928
- await kvAdapter.set(['reservation', reservation.reservationId], record);
929
- }
930
-
931
- cancelled.push({
932
- ...reservation,
933
- reason: 'Insufficient inventory',
934
- availableQty
935
- });
936
-
937
- log.warn(`⚠️ Cancelled reservation: ${reservation.reservationId}`, {
938
- reason: 'insufficient_inventory',
939
- needed: reservation.quantity,
940
- available: availableQty
941
- });
942
- } catch (error) {
943
- // ? Enhanced: Error logging with recommendations
944
- log.error('[PreOrderAllocation] Failed to cancel reservation', {
945
- reservationId: reservation.reservationId,
946
- error: error instanceof Error ? error.message : String(error),
947
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
948
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
949
- : error.message?.includes('mutation') || error.message?.includes('GraphQL')
950
- ? 'Check GraphQL mutation syntax and reservation cancellation payload'
951
- : error.message?.includes('not found') || error.message?.includes('missing')
952
- ? 'Reservation not found - verify reservationId and check KV store'
953
- : 'Review error details and check reservation cancellation logic'
954
- });
955
- }
956
- }
957
- }
958
- }
959
-
960
- // Step 5: Send notifications (placeholder)
961
- const duration = Date.now() - startTime;
962
- log.info(`📧 Sending notifications (${duration}ms total)`, {
963
- confirmed: confirmed.length,
964
- cancelled: cancelled.length
965
- });
966
-
967
- // TODO: Send confirmation emails to confirmed reservations
968
- // TODO: Send cancellation/apology emails to cancelled reservations
969
-
970
- return {
971
- success: true,
972
- date: today,
973
- processed: reservations.length,
974
- confirmed: confirmed.length,
975
- cancelled: cancelled.length,
976
- duration,
977
- summary: {
978
- vipConfirmed: confirmed.filter(r => r.tier === 'VIP').length,
979
- standardConfirmed: confirmed.filter(r => r.tier === 'STANDARD').length,
980
- vipCancelled: cancelled.filter(r => r.tier === 'VIP').length,
981
- standardCancelled: cancelled.filter(r => r.tier === 'STANDARD').length
982
- },
983
- timestamp: new Date().toISOString()
984
- };
985
- } catch (error) {
986
- // ? Enhanced: Error logging with recommendations
987
- log.error('[PreOrderAllocation] Scheduled release failed', {
988
- error: error instanceof Error ? error.message : String(error),
989
- errorType: error instanceof Error ? error.constructor.name : 'Error',
990
- stack: error instanceof Error ? error.stack : undefined,
991
- recommendation: error.message?.includes('authentication') || error.message?.includes('401')
992
- ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
993
- : error.message?.includes('KV') || error.message?.includes('state')
994
- ? 'Check KV store connectivity and reservation state structure'
995
- : error.message?.includes('connection') || error.message?.includes('timeout')
996
- ? 'Check network connectivity and Fluent Commerce API availability'
997
- : 'Review error details and check scheduled release workflow configuration'
998
- });
999
- throw error;
1000
- }
1001
- })(ctx);
1002
- });
1003
-
1004
- // =============================================================================
1005
- // WORKFLOW 3: CANCELLATION WEBHOOK
1006
- // =============================================================================
1007
-
1008
- /**
1009
- * Cancellation Webhook - Cancel reservation and offer to next in queue
1010
- *
1011
- * Flow:
1012
- * 1. Receive cancellation request
1013
- * 2. Update reservation status
1014
- * 3. Query next customer in queue
1015
- * 4. Offer to next customer (if available)
1016
- * 5. Send notifications
1017
- */
1018
- export const cancellationWebhook = webhook('cancel-reservation', {
1019
- response: { mode: 'sync' },
1020
- cors: true
1021
- })
1022
- .then(fn('validate-cancellation', ({ data, log }) => {
1023
- log.info('🔍 Processing cancellation request');
1024
-
1025
- if (!data.reservationId && !data.orderId) {
1026
- throw new Error('Either reservationId or orderId is required');
1027
- }
1028
-
1029
- return data;
1030
- }))
1031
- .then(http('cancel-and-requeue', {
1032
- connection: 'fluent_commerce'
1033
- }, async (ctx) => {
1034
- const { reservationId, orderId } = ctx.data;
1035
- const { log, openKv } = ctx;
1036
- const client = await createClient(ctx, { validateConnection: true });
1037
- const kvAdapter = new VersoriKVAdapter(openKv());
1038
-
1039
- log.info('🚫 Cancelling reservation', { reservationId, orderId });
1040
-
1041
- // Find reservation in KV
1042
- let reservation: ReservationRecord | null = null;
1043
-
1044
- if (reservationId) {
1045
- const kvRecord = await kvAdapter.get(['reservation', reservationId]);
1046
- reservation = kvRecord?.value as ReservationRecord;
1047
- } else {
1048
- // Search by orderId (would need indexed tracker)
1049
- // Placeholder: iterate through all reservations
1050
- log.warn('[CANCEL] Searching by orderId - implement indexed search for production');
1051
- }
1052
-
1053
- if (!reservation) {
1054
- throw new Error(`Reservation not found: ${reservationId || orderId}`);
1055
- }
1056
-
1057
- // Update status in Fluent
1058
- const result = await client.graphql({
1059
- query: `
1060
- query FindReservation($ref: String!) {
1061
- inventoryQuantities(first: 1, ref: $ref) {
1062
- edges {
1063
- node {
1064
- id
1065
- ref
1066
- status
1067
- }
1068
- }
1069
- }
1070
- }
1071
- `,
1072
- variables: { ref: reservation.reservationId }
1073
- });
1074
-
1075
- const inventoryId = result.data?.inventoryQuantities?.edges?.[0]?.node?.id;
1076
-
1077
- if (inventoryId) {
1078
- await client.graphql({
1079
- query: `
1080
- mutation CancelReservation($id: ID!, $status: String!) {
1081
- updateInventoryQuantity(id: $id, status: $status) {
1082
- id
1083
- status
1084
- }
1085
- }
1086
- `,
1087
- variables: {
1088
- id: inventoryId,
1089
- status: 'CANCELLED'
1090
- }
1091
- });
1092
- }
1093
-
1094
- // Update KV
1095
- reservation.status = 'CANCELLED';
1096
- reservation.cancelledAt = new Date().toISOString();
1097
- await kvAdapter.set(['reservation', reservation.reservationId], reservation);
1098
-
1099
- log.info('✅ Reservation cancelled', {
1100
- reservationId: reservation.reservationId,
1101
- sku: reservation.sku,
1102
- quantity: reservation.quantity
1103
- });
1104
-
1105
- // Find next in queue
1106
- const queueKey = ['queue', reservation.sku, reservation.locationRef, reservation.shipDate].join(':');
1107
-
1108
- // TODO: Implement queue management
1109
- // Query next customer with higher queue position
1110
- // Send offer to next customer
1111
-
1112
- log.info('📋 Queue position available for next customer', {
1113
- sku: reservation.sku,
1114
- shipDate: reservation.shipDate,
1115
- releasedQuantity: reservation.quantity
1116
- });
1117
-
1118
- return {
1119
- success: true,
1120
- cancelled: {
1121
- reservationId: reservation.reservationId,
1122
- orderId: reservation.orderId,
1123
- sku: reservation.sku,
1124
- quantity: reservation.quantity
1125
- },
1126
- message: 'Reservation cancelled successfully',
1127
- timestamp: new Date().toISOString()
1128
- };
1129
- }));
1130
-
1131
- // =============================================================================
1132
- // WORKFLOW 4: ADMIN DASHBOARD EXPORT
1133
- // =============================================================================
1134
-
1135
- /**
1136
- * Export Reservations to S3 for Admin Dashboard
1137
- *
1138
- * Runs daily to export reservation status to S3 for dashboard visualization
1139
- */
1140
- export const exportReservations = schedule('export-reservations', '0 3 * * *', async (ctx) => {
1141
- const { openKv, log } = ctx;
1142
-
1143
- return fn('export-to-s3', async ({ openKv, log }) => {
1144
- log.info('📤 Exporting reservations to S3');
1145
-
1146
- const kvAdapter = new VersoriKVAdapter(openKv());
1147
- const reservationTracker = new VersoriFileTracker(openKv(), 'pre-order-reservations');
1148
-
1149
- // Get all reservations
1150
- const allReservations = await reservationTracker.listProcessedFiles();
1151
- log.info(`📋 Found ${allReservations.length} reservations to export`);
1152
-
1153
- // Fetch full reservation data
1154
- const exportData: any[] = [];
1155
- for (const tracked of allReservations) {
1156
- const kvRecord = await kvAdapter.get(['reservation', tracked.fileName]);
1157
- if (kvRecord?.value) {
1158
- exportData.push(kvRecord.value);
1159
- }
1160
- }
1161
-
1162
- // Group by ship date and status
1163
- const summary = {
1164
- total: exportData.length,
1165
- byStatus: {
1166
- RESERVED: exportData.filter(r => r.status === 'RESERVED').length,
1167
- CONFIRMED: exportData.filter(r => r.status === 'CONFIRMED').length,
1168
- CANCELLED: exportData.filter(r => r.status === 'CANCELLED').length,
1169
- EXPIRED: exportData.filter(r => r.status === 'EXPIRED').length
1170
- },
1171
- byTier: {
1172
- VIP: exportData.filter(r => r.tier === 'VIP').length,
1173
- STANDARD: exportData.filter(r => r.tier === 'STANDARD').length
1174
- },
1175
- exportedAt: new Date().toISOString()
1176
- };
1177
-
1178
- const exportPayload = {
1179
- summary,
1180
- reservations: exportData
1181
- };
1182
-
1183
- // TODO: Upload to S3
1184
- // const s3Key = `pre-orders/export-${new Date().toISOString().split('T')[0]}.json`;
1185
- // await s3Client.putObject({ Bucket, Key: s3Key, Body: JSON.stringify(exportPayload) });
1186
-
1187
- log.info('✅ Export complete', summary);
1188
-
1189
- return {
1190
- success: true,
1191
- summary,
1192
- recordCount: exportData.length
1193
- };
1194
- })(ctx);
1195
- });
1196
-
1197
- // =============================================================================
1198
- // WORKFLOW 5: MANUAL ATP CHECK (Testing/Admin)
1199
- // =============================================================================
1200
-
1201
- /**
1202
- * Manual ATP Check - Admin endpoint to check future ATP
1203
- */
1204
- export const checkAtp = webhook('check-atp', {
1205
- response: { mode: 'sync' },
1206
- cors: true
1207
- })
1208
- .then(http('calculate-manual-atp', {
1209
- connection: 'fluent_commerce'
1210
- }, async (ctx) => {
1211
- const { sku, locationRef, shipDate, quantity } = ctx.data;
1212
- const { log } = ctx;
1213
- const startTime = Date.now();
1214
- const client = await createClient(ctx, { validateConnection: true });
1215
-
1216
- log.info('🔍 Manual ATP calculation', { sku, locationRef, shipDate, quantity });
1217
-
1218
- // Reuse ATP calculation logic from pre-order workflow
1219
- const inventoryResult = await client.graphql({
1220
- query: `
1221
- query GetInventory($locationRef: String!, $skuRef: String!) {
1222
- inventoryQuantities(
1223
- first: 1
1224
- locationRef: $locationRef
1225
- skuRef: $skuRef
1226
- type: "LAST_ON_HAND"
1227
- ) {
1228
- edges {
1229
- node {
1230
- quantity
1231
- }
1232
- }
1233
- }
1234
- }
1235
- `,
1236
- variables: { locationRef, skuRef: sku }
1237
- });
1238
-
1239
- const currentInventory = inventoryResult.data?.inventoryQuantities?.edges?.[0]?.node?.quantity || 0;
1240
-
1241
- // Query reservations
1242
- const reservationsResult = await client.graphql({
1243
- query: `
1244
- query GetReservations($sku: String!, $shipDate: String!) {
1245
- inventoryQuantities(
1246
- first: 100
1247
- skuRef: $sku
1248
- status: "RESERVED"
1249
- ) {
1250
- edges {
1251
- node {
1252
- quantity
1253
- attributes {
1254
- name
1255
- value
1256
- }
1257
- }
1258
- }
1259
- }
1260
- }
1261
- `,
1262
- variables: { sku, shipDate }
1263
- });
1264
-
1265
- let existingReservations = 0;
1266
- const reservationEdges = reservationsResult.data?.inventoryQuantities?.edges || [];
1267
- for (const edge of reservationEdges) {
1268
- const shipDateAttr = edge.node.attributes?.find((a: any) => a.name === 'shipDate');
1269
- if (shipDateAttr?.value === shipDate) {
1270
- existingReservations += edge.node.quantity || 0;
1271
- }
1272
- }
1273
-
1274
- // Query expected arrivals
1275
- const expectedArrivalResult = await client.graphql({
1276
- query: `
1277
- query GetExpectedArrivals($locationRef: String!, $skuRef: String!, $beforeDate: String!) {
1278
- inventoryQuantities(
1279
- first: 50
1280
- locationRef: $locationRef
1281
- skuRef: $skuRef
1282
- type: "EXPECTED"
1283
- expectedOnBefore: $beforeDate
1284
- ) {
1285
- edges {
1286
- node {
1287
- quantity
1288
- expectedOn
1289
- }
1290
- }
1291
- }
1292
- }
1293
- `,
1294
- variables: { locationRef, skuRef: sku, beforeDate: shipDate }
1295
- });
1296
-
1297
- const expectedArrival = expectedArrivalResult.data?.inventoryQuantities?.edges?.reduce(
1298
- (sum: number, edge: any) => sum + (edge.node.quantity || 0),
1299
- 0
1300
- ) || 0;
1301
-
1302
- const totalExpected = currentInventory + expectedArrival;
1303
- const overbookingLimit = Math.floor(totalExpected * 1.05);
1304
- const availableToPromise = overbookingLimit - existingReservations;
1305
- const canFulfill = availableToPromise >= (quantity || 1);
1306
- const duration = Date.now() - startTime;
1307
-
1308
- log.info(`✅ ATP check complete (${duration}ms)`, { sku, canFulfill });
1309
-
1310
- return {
1311
- sku,
1312
- locationRef,
1313
- shipDate,
1314
- requestedQuantity: quantity || 1,
1315
- currentInventory,
1316
- expectedArrival,
1317
- totalExpected,
1318
- existingReservations,
1319
- overbookingLimit,
1320
- availableToPromise,
1321
- canFulfill,
1322
- duration,
1323
- recommendation: canFulfill
1324
- ? 'Reservation can be fulfilled'
1325
- : `Insufficient inventory (need ${quantity || 1}, available ${availableToPromise})`,
1326
- timestamp: new Date().toISOString()
1327
- };
1328
- }));
1329
- ```
1330
-
1331
- ---
1332
-
1333
- ## 2. Configuration File: `config/priority-tiers.json`
1334
-
1335
- ```json
1336
- {
1337
- "version": "1.0.0",
1338
- "description": "Pre-order priority tier configuration",
1339
- "tiers": {
1340
- "VIP": {
1341
- "score": 100,
1342
- "description": "VIP customers get priority allocation",
1343
- "benefits": [
1344
- "First priority for limited inventory",
1345
- "Email notifications for restocks",
1346
- "Expedited shipping",
1347
- "Early access to new launches"
1348
- ]
1349
- },
1350
- "STANDARD": {
1351
- "score": 50,
1352
- "description": "Standard customer tier",
1353
- "benefits": [
1354
- "Standard allocation priority",
1355
- "Email notifications",
1356
- "Standard shipping"
1357
- ]
1358
- }
1359
- },
1360
- "overbooking": {
1361
- "enabled": true,
1362
- "percentage": 1.05,
1363
- "description": "Allow 105% allocation to account for cancellations"
1364
- },
1365
- "expiration": {
1366
- "daysBeforeShipDate": 7,
1367
- "description": "Reservations expire 7 days before ship date if not confirmed"
1368
- },
1369
- "queue": {
1370
- "maxQueueDepth": 10000,
1371
- "reallocationOnCancellation": true
1372
- }
1373
- }
1374
- ```
1375
-
1376
- ---
1377
-
1378
- ## 3. Package Configuration: `package.json`
1379
-
1380
- ```json
1381
- {
1382
- "name": "pre-order-allocation-management",
1383
- "version": "1.0.0",
1384
- "description": "Pre-order allocation system with priority queuing and future ATP",
1385
- "versori": {
1386
- "workflows": "./index.ts"
1387
- },
1388
- "dependencies": {
1389
- "@fluentcommerce/fc-connect-sdk": "^0.1.39",
1390
- "@versori/run": "latest"
1391
- },
1392
- "devDependencies": {
1393
- "@types/node": "^20.0.0",
1394
- "typescript": "^5.0.0"
1395
- },
1396
- "scripts": {
1397
- "deploy": "versori deploy",
1398
- "logs": "versori logs",
1399
- "test-preorder": "curl -X POST http://localhost:8080/pre-order -H 'Content-Type: application/json' -d @test/sample-preorder.json",
1400
- "test-atp": "curl -X POST http://localhost:8080/check-atp -H 'Content-Type: application/json' -d '{\"sku\":\"IPHONE-15-PRO-256\",\"locationRef\":\"DC-NY\",\"shipDate\":\"2025-02-15\",\"quantity\":1}'"
1401
- }
1402
- }
1403
- ```
1404
-
1405
- ---
1406
-
1407
- ## 4. Test Data: `test/sample-preorder.json`
1408
-
1409
- ```json
1410
- {
1411
- "orderId": "PRE-2025-001234",
1412
- "customerId": "CUST-VIP-9876",
1413
- "customerEmail": "john.doe@example.com",
1414
- "customerTier": "VIP",
1415
- "shipDate": "2025-02-15T00:00:00Z",
1416
- "source": "shopify",
1417
- "items": [
1418
- {
1419
- "sku": "IPHONE-15-PRO-256",
1420
- "productName": "iPhone 15 Pro 256GB Titanium Blue",
1421
- "quantity": 1,
1422
- "locationRef": "DC-NY"
1423
- }
1424
- ]
1425
- }
1426
- ```
1427
-
1428
- ---
1429
-
1430
- ## Versori Workflow Structure
1431
-
1432
- This solution uses multiple workflow types:
1433
-
1434
- ### HTTP Webhooks (Real-time)
1435
-
1436
- 1. **pre-order** - Receives pre-orders from e-commerce
1437
- 2. **cancel-reservation** - Cancels existing reservations
1438
- 3. **check-atp** - Manual ATP calculation (admin)
1439
-
1440
- ### Scheduled Workflows (Cron)
1441
-
1442
- 1. **release-reservations** - Daily at 2 AM
1443
- 2. **export-reservations** - Daily at 3 AM (dashboard data)
1444
-
1445
- ### Workflow Dependencies
1446
-
1447
- ```
1448
- ┌─────────────────┐
1449
- │ E-commerce │
1450
- │ Platform │
1451
- └────────┬────────┘
1452
- │ HTTP POST
1453
-
1454
- ┌─────────────────┐ ┌──────────────┐
1455
- │ Pre-Order │─────▶│ VersoriKV │
1456
- │ Webhook │ │ (State) │
1457
- └────────┬────────┘ └──────────────┘
1458
-
1459
-
1460
- ┌─────────────────┐
1461
- │ Fluent API │
1462
- │ (Reservations) │
1463
- └─────────────────┘
1464
-
1465
-
1466
- ┌────────┴────────┐
1467
- │ Release │ (Scheduled 2 AM)
1468
- │ Workflow │
1469
- └────────┬────────┘
1470
-
1471
-
1472
- ┌─────────────────┐
1473
- │ Fulfillment │
1474
- │ Orders │
1475
- └─────────────────┘
1476
- ```
1477
-
1478
- ---
1479
-
1480
- ## Key Patterns Explained
1481
-
1482
- ### Pattern 1: Future ATP (Available to Promise) Calculation
1483
-
1484
- **Algorithm:**
1485
-
1486
- ```typescript
1487
- // Step 1: Get current inventory
1488
- currentInventory = queryInventory(sku, location)
1489
-
1490
- // Step 2: Get expected arrivals before ship date
1491
- expectedArrival = queryExpectedBefore(sku, location, shipDate)
1492
-
1493
- // Step 3: Get existing reservations for this date
1494
- existingReservations = queryReservations(sku, location, shipDate)
1495
-
1496
- // Step 4: Calculate overbooking limit (105%)
1497
- totalExpected = currentInventory + expectedArrival
1498
- overbookingLimit = totalExpected * 1.05
1499
-
1500
- // Step 5: Calculate ATP
1501
- availableToPromise = overbookingLimit - existingReservations
1502
-
1503
- // Step 6: Check fulfillment
1504
- canFulfill = availableToPromise >= requestedQuantity
1505
- ```
1506
-
1507
- **Why overbooking?**
1508
-
1509
- - Typical cancellation rate: 3-5%
1510
- - 105% allocation ensures full utilization
1511
- - Prevents lost sales from conservative allocation
1512
-
1513
- **Edge cases:**
1514
-
1515
- - No expected arrivals → ATP based on current only
1516
- - Multiple locations → calculate per location
1517
- - Negative ATP → queue overflow, reject order
1518
-
1519
- ### Pattern 2: Priority Scoring Algorithm
1520
-
1521
- **Score calculation:**
1522
-
1523
- ```typescript
1524
- // Tier component (VIP=100, Standard=50)
1525
- tierScore = customerTier === 'VIP' ? 100 : 50
1526
-
1527
- // Time component (earlier = better)
1528
- timeScore = Date.now() // Milliseconds since epoch
1529
-
1530
- // Final priority (higher = better)
1531
- priority = (tierScore * 1000000) - timeScore
1532
-
1533
- // Example scores:
1534
- // VIP order at 10:00 AM = 100000000 - 1708000000 = -1607000000
1535
- // VIP order at 10:01 AM = 100000000 - 1708000060 = -1607000060
1536
- // Standard at 10:00 AM = 50000000 - 1708000000 = -1657000000
1537
-
1538
- // Sort descending: all VIPs come first, then by time
1539
- ```
1540
-
1541
- **Why negative scores?**
1542
-
1543
- - Allows simple descending sort
1544
- - VIP scores always > Standard scores
1545
- - Time breaks ties within tier
1546
-
1547
- **Alternative scoring strategies:**
1548
-
1549
- - Purchase history: +10 points per previous purchase
1550
- - Cart value: +1 point per $100
1551
- - Membership duration: +1 point per year
1552
-
1553
- ### Pattern 3: Reservation Expiration
1554
-
1555
- **Expiration logic:**
1556
-
1557
- ```typescript
1558
- // Calculate expiration date
1559
- shipDate = new Date('2025-02-15')
1560
- expirationDate = new Date(shipDate.getTime() - 7 * 24 * 60 * 60 * 1000)
1561
-
1562
- // Store with reservation
1563
- reservation.expiresAt = expirationDate.toISOString()
1564
-
1565
- // Scheduled cleanup (runs daily)
1566
- const now = new Date()
1567
- if (now > expirationDate && reservation.status === 'RESERVED') {
1568
- reservation.status = 'EXPIRED'
1569
- releaseInventory(reservation)
1570
- offerToNextInQueue(reservation)
1571
- }
1572
- ```
1573
-
1574
- **Why 7 days?**
1575
-
1576
- - Gives customers buffer to cancel if needed
1577
- - Prevents last-minute cancellations
1578
- - Allows reallocation to other customers
1579
- - Industry standard for pre-orders
1580
-
1581
- **Alternatives:**
1582
-
1583
- - Dynamic expiration: longer for high-value items
1584
- - Tiered expiration: VIP gets longer window
1585
- - Payment-based: shorter for unpaid reservations
1586
-
1587
- ### Pattern 4: Queue Position Tracking
1588
-
1589
- **Queue management:**
1590
-
1591
- ```typescript
1592
- // KV key structure
1593
- queueKey = ['queue', sku, locationRef, shipDate].join(':')
1594
- // Example: 'queue:IPHONE-15-PRO-256:DC-NY:2025-02-15'
1595
-
1596
- // Get current position
1597
- currentLength = await kvAdapter.get([queueKey]) || 0
1598
-
1599
- // Assign new position
1600
- newPosition = currentLength + 1
1601
- await kvAdapter.set([queueKey], newPosition)
1602
-
1603
- // On cancellation
1604
- // 1. Mark position as available
1605
- // 2. Query next unfulfilled reservation
1606
- // 3. Move them up in queue
1607
- ```
1608
-
1609
- **Why track position?**
1610
-
1611
- - Transparency for customers
1612
- - "You are #23 in line" messaging
1613
- - Prioritize reallocation on cancellation
1614
- - Dashboard visibility
1615
-
1616
- **Optimization:**
1617
-
1618
- - Use sorted sets for O(log n) insertion
1619
- - Batch queue updates every 5 minutes
1620
- - Cache queue length in memory
1621
-
1622
- ### Pattern 5: Scheduled Release Workflow
1623
-
1624
- **Release algorithm:**
1625
-
1626
- ```typescript
1627
- // Run daily at 2 AM for today's ship date
1628
- today = '2025-02-15'
1629
-
1630
- // Step 1: Get all reservations
1631
- reservations = queryReservations({ shipDate: today, status: 'RESERVED' })
1632
-
1633
- // Step 2: Sort by priority
1634
- reservations.sort((a, b) => b.priority - a.priority)
1635
-
1636
- // Step 3: Group by SKU
1637
- groupedBySku = groupBy(reservations, r => r.sku)
1638
-
1639
- // Step 4: For each SKU, allocate inventory
1640
- for (sku in groupedBySku) {
1641
- availableQty = getCurrentInventory(sku)
1642
-
1643
- for (reservation of groupedBySku[sku]) {
1644
- if (availableQty >= reservation.quantity) {
1645
- // Confirm and create fulfillment order
1646
- createFulfillmentOrder(reservation)
1647
- availableQty -= reservation.quantity
1648
- } else {
1649
- // Cancel and notify
1650
- cancelReservation(reservation, 'insufficient_inventory')
1651
- sendApologyEmail(reservation)
1652
- }
1653
- }
1654
- }
1655
- ```
1656
-
1657
- **Why 2 AM?**
1658
-
1659
- - Off-peak hours (low traffic)
1660
- - Gives time for overnight inventory updates
1661
- - Allows morning fulfillment processing
1662
- - Industry best practice
1663
-
1664
- **Failure handling:**
1665
-
1666
- - Retry failed confirmations 3 times
1667
- - Alert ops team if >10% cancellations
1668
- - Log all decisions for audit trail
1669
-
1670
- ### Pattern 6: Overbooking Strategy
1671
-
1672
- **Why 105%?**
1673
-
1674
- ```
1675
- Historical analysis:
1676
- - Average cancellation rate: 4.2%
1677
- - Range: 3.5% - 5.8% depending on product
1678
- - 105% allocation:
1679
- * If 0 cancellations: 5% oversold (minor)
1680
- * If 5% cancellations: perfect allocation
1681
- * If 10% cancellations: 5% undersold
1682
-
1683
- Trade-offs:
1684
- - Conservative (100%): Lost revenue, customer disappointment
1685
- - Moderate (105%): Optimal balance
1686
- - Aggressive (110%): Risk of overselling, fulfillment delays
1687
- ```
1688
-
1689
- **Dynamic overbooking:**
1690
-
1691
- ```typescript
1692
- // Adjust based on product category
1693
- const overbookingRates = {
1694
- 'electronics': 1.03, // Low cancellation
1695
- 'apparel': 1.08, // High cancellation
1696
- 'limited-edition': 1.02, // Low risk tolerance
1697
- 'default': 1.05
1698
- }
1699
-
1700
- const rate = overbookingRates[productCategory] || overbookingRates['default']
1701
- overbookingLimit = totalExpected * rate
1702
- ```
1703
-
1704
- ---
1705
-
1706
- ## Testing
1707
-
1708
- ### Test 1: Create Pre-Order (VIP Customer)
1709
-
1710
- ```bash
1711
- curl -X POST https://your-workspace.versori.run/pre-order \
1712
- -H "Content-Type: application/json" \
1713
- -d '{
1714
- "orderId": "PRE-2025-001234",
1715
- "customerId": "CUST-VIP-9876",
1716
- "customerEmail": "john.doe@example.com",
1717
- "customerTier": "VIP",
1718
- "shipDate": "2025-02-15T00:00:00Z",
1719
- "source": "shopify",
1720
- "items": [{
1721
- "sku": "IPHONE-15-PRO-256",
1722
- "productName": "iPhone 15 Pro 256GB",
1723
- "quantity": 1,
1724
- "locationRef": "DC-NY"
1725
- }]
1726
- }'
1727
-
1728
- # Expected Response:
1729
- {
1730
- "success": true,
1731
- "orderId": "PRE-2025-001234",
1732
- "reservations": [{
1733
- "reservationId": "RES-PRE-2025-001234-IPHONE-15-PRO-256-1708000000",
1734
- "sku": "IPHONE-15-PRO-256",
1735
- "quantity": 1,
1736
- "queuePosition": 1,
1737
- "priority": 99892000000,
1738
- "expiresAt": "2025-02-08T00:00:00Z"
1739
- }],
1740
- "failedItems": [],
1741
- "message": "All items reserved successfully"
1742
- }
1743
- ```
1744
-
1745
- ### Test 2: Check ATP for Product
1746
-
1747
- ```bash
1748
- curl -X POST https://your-workspace.versori.run/check-atp \
1749
- -H "Content-Type: application/json" \
1750
- -d '{
1751
- "sku": "IPHONE-15-PRO-256",
1752
- "locationRef": "DC-NY",
1753
- "shipDate": "2025-02-15",
1754
- "quantity": 1
1755
- }'
1756
-
1757
- # Expected Response:
1758
- {
1759
- "sku": "IPHONE-15-PRO-256",
1760
- "locationRef": "DC-NY",
1761
- "shipDate": "2025-02-15",
1762
- "requestedQuantity": 1,
1763
- "currentInventory": 500,
1764
- "expectedArrival": 1000,
1765
- "totalExpected": 1500,
1766
- "existingReservations": 1234,
1767
- "overbookingLimit": 1575,
1768
- "availableToPromise": 341,
1769
- "canFulfill": true,
1770
- "recommendation": "Reservation can be fulfilled"
1771
- }
1772
- ```
1773
-
1774
- ### Test 3: Cancel Reservation
1775
-
1776
- ```bash
1777
- curl -X POST https://your-workspace.versori.run/cancel-reservation \
1778
- -H "Content-Type: application/json" \
1779
- -d '{
1780
- "reservationId": "RES-PRE-2025-001234-IPHONE-15-PRO-256-1708000000"
1781
- }'
1782
-
1783
- # Expected Response:
1784
- {
1785
- "success": true,
1786
- "cancelled": {
1787
- "reservationId": "RES-PRE-2025-001234-IPHONE-15-PRO-256-1708000000",
1788
- "orderId": "PRE-2025-001234",
1789
- "sku": "IPHONE-15-PRO-256",
1790
- "quantity": 1
1791
- },
1792
- "message": "Reservation cancelled successfully"
1793
- }
1794
- ```
1795
-
1796
- ### Test 4: Trigger Manual Release (Admin)
1797
-
1798
- ```bash
1799
- # Deploy workflow
1800
- npm run deploy
1801
-
1802
- # View scheduled workflow logs
1803
- npm run logs
1804
-
1805
- # Expected log output:
1806
- [RELEASE] Starting scheduled release process
1807
- [RELEASE] Found 1234 reservations for 2025-02-15
1808
- [RELEASE] Sorted reservations by priority - VIP: 234, Standard: 1000
1809
- [RELEASE] Processing 1234 reservations for IPHONE-15-PRO-256
1810
- [RELEASE] Confirmed reservation: RES-xxx (1234 total)
1811
- [RELEASE] Cancelled reservation: RES-yyy - insufficient inventory
1812
- [RELEASE] Release complete - confirmed: 1200, cancelled: 34
1813
- ```
1814
-
1815
- ---
1816
-
1817
- ## Common Issues and Solutions
1818
-
1819
- ### Issue 1: ATP Calculation Incorrect
1820
-
1821
- **Symptoms:**
1822
-
1823
- - Overselling (more reservations than inventory)
1824
- - Underselling (rejecting valid reservations)
1825
-
1826
- **Root Causes:**
1827
-
1828
- 1. Not accounting for existing reservations
1829
- 2. Double-counting expected arrivals
1830
- 3. Wrong overbooking percentage
1831
-
1832
- **Solution:**
1833
-
1834
- ```typescript
1835
- // Add debug logging to ATP calculation
1836
- log.info('[ATP-DEBUG] Calculation breakdown', {
1837
- sku,
1838
- current: currentInventory,
1839
- expected: expectedArrival,
1840
- reserved: existingReservations,
1841
- overbooking: overbookingLimit,
1842
- atp: availableToPromise,
1843
- // Verify math
1844
- verification: {
1845
- total: currentInventory + expectedArrival,
1846
- withOverbooking: (currentInventory + expectedArrival) * 1.05,
1847
- afterReserved: ((currentInventory + expectedArrival) * 1.05) - existingReservations
1848
- }
1849
- });
1850
-
1851
- // Add validation
1852
- if (availableToPromise < 0) {
1853
- log.warn('[ATP] Negative ATP detected - inventory oversold', {
1854
- sku,
1855
- atp: availableToPromise
1856
- });
1857
- }
1858
- ```
1859
-
1860
- ### Issue 2: Queue Position Conflicts
1861
-
1862
- **Symptoms:**
1863
-
1864
- - Duplicate queue positions
1865
- - Gaps in queue numbers
1866
- - Out-of-order processing
1867
-
1868
- **Root Cause:**
1869
-
1870
- - Race condition in queue counter increment
1871
- - Multiple reservations at same millisecond
1872
-
1873
- **Solution:**
1874
-
1875
- ```typescript
1876
- // Use atomic increment with retry
1877
- async function assignQueuePosition(
1878
- kvAdapter: VersoriKVAdapter,
1879
- queueKey: string,
1880
- maxRetries = 3
1881
- ): Promise<number> {
1882
- for (let i = 0; i < maxRetries; i++) {
1883
- try {
1884
- // Get current position
1885
- const current = await kvAdapter.get([queueKey]);
1886
- const position = (current?.value as number) || 0;
1887
- const newPosition = position + 1;
1888
-
1889
- // Set with version check (if supported)
1890
- await kvAdapter.set([queueKey], newPosition);
1891
-
1892
- return newPosition;
1893
- } catch (error) {
1894
- if (i === maxRetries - 1) throw error;
1895
- // Wait with exponential backoff
1896
- await new Promise(resolve => setTimeout(resolve, 2 ** i * 100));
1897
- }
1898
- }
1899
-
1900
- throw new Error('Failed to assign queue position after retries');
1901
- }
1902
- ```
1903
-
1904
- ### Issue 3: Reservation Expiration Not Working
1905
-
1906
- **Symptoms:**
1907
-
1908
- - Expired reservations still active
1909
- - Inventory not released
1910
-
1911
- **Root Cause:**
1912
-
1913
- - Expiration check not running
1914
- - Timezone mismatches
1915
- - Status not updated in Fluent
1916
-
1917
- **Solution:**
1918
-
1919
- ```typescript
1920
- // Add expiration cleanup to release workflow
1921
- async function cleanupExpiredReservations(
1922
- client: FluentClient,
1923
- kvAdapter: VersoriKVAdapter,
1924
- log: Logger
1925
- ) {
1926
- const now = new Date();
1927
-
1928
- // Query all RESERVED inventory
1929
- const result = await client.graphql({
1930
- query: `
1931
- query GetExpiredReservations {
1932
- inventoryQuantities(
1933
- first: 1000
1934
- status: "RESERVED"
1935
- ) {
1936
- edges {
1937
- node {
1938
- id
1939
- ref
1940
- attributes {
1941
- name
1942
- value
1943
- }
1944
- }
1945
- }
1946
- }
1947
- }
1948
- `
1949
- });
1950
-
1951
- const edges = result.data?.inventoryQuantities?.edges || [];
1952
-
1953
- for (const edge of edges) {
1954
- const expiresAtAttr = edge.node.attributes?.find((a: any) => a.name === 'expiresAt');
1955
- if (!expiresAtAttr) continue;
1956
-
1957
- const expiresAt = new Date(expiresAtAttr.value);
1958
-
1959
- if (now > expiresAt) {
1960
- // Mark as expired
1961
- await client.graphql({
1962
- query: `
1963
- mutation ExpireReservation($id: ID!) {
1964
- updateInventoryQuantity(id: $id, status: "EXPIRED") {
1965
- id
1966
- status
1967
- }
1968
- }
1969
- `,
1970
- variables: { id: edge.node.id }
1971
- });
1972
-
1973
- log.info('[CLEANUP] Expired reservation', {
1974
- ref: edge.node.ref,
1975
- expiresAt: expiresAtAttr.value
1976
- });
1977
- }
1978
- }
1979
- }
1980
- ```
1981
-
1982
- ### Issue 4: Priority Queue Not Respected
1983
-
1984
- **Symptoms:**
1985
-
1986
- - Standard customers processed before VIP
1987
- - Queue positions ignored
1988
-
1989
- **Root Cause:**
1990
-
1991
- - Sorting algorithm incorrect
1992
- - Priority scores calculated wrong
1993
- - Time component overflow
1994
-
1995
- **Solution:**
1996
-
1997
- ```typescript
1998
- // Verify sorting logic
1999
- function verifySortOrder(reservations: ReservationWithPriority[], log: any) {
2000
- let prevPriority = Infinity;
2001
- let prevTier = 'VIP';
2002
-
2003
- for (const reservation of reservations) {
2004
- // Check tier ordering
2005
- if (prevTier === 'VIP' && reservation.tier === 'STANDARD') {
2006
- prevTier = 'STANDARD';
2007
- }
2008
-
2009
- // Check priority decreasing
2010
- if (reservation.priority > prevPriority) {
2011
- throw new Error(`Sort order violated: ${reservation.priority} > ${prevPriority}`);
2012
- }
2013
-
2014
- prevPriority = reservation.priority;
2015
- }
2016
-
2017
- log.info('✓ Sort order verified');
2018
- }
2019
-
2020
- // Use stable sort
2021
- reservations.sort((a, b) => {
2022
- // Primary: priority (higher first)
2023
- if (a.priority !== b.priority) {
2024
- return b.priority - a.priority;
2025
- }
2026
-
2027
- // Secondary: queue position (lower first)
2028
- if (a.queuePosition !== b.queuePosition) {
2029
- return a.queuePosition - b.queuePosition;
2030
- }
2031
-
2032
- // Tertiary: creation time (earlier first)
2033
- return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
2034
- });
2035
- ```
2036
-
2037
- ### Issue 5: Scheduled Release Not Running
2038
-
2039
- **Symptoms:**
2040
-
2041
- - Reservations not converted to orders
2042
- - No logs from scheduled workflow
2043
-
2044
- **Root Cause:**
2045
-
2046
- - Cron expression incorrect
2047
- - Workflow not deployed
2048
- - Timezone mismatch
2049
-
2050
- **Solution:**
2051
-
2052
- ```bash
2053
- # Verify cron expression (use crontab.guru)
2054
- # "0 2 * * *" = Every day at 2:00 AM UTC
2055
-
2056
- # Check workflow deployment
2057
- versori workflows list
2058
-
2059
- # Expected output:
2060
- # release-reservations (schedule: 0 2 * * *)
2061
- # export-reservations (schedule: 0 3 * * *)
2062
-
2063
- # Manually trigger for testing
2064
- curl -X POST https://your-workspace.versori.run/__scheduled/release-reservations
2065
-
2066
- # Check logs
2067
- npm run logs -- --workflow release-reservations --tail 100
2068
- ```
2069
-
2070
- ---
2071
-
2072
- ## Real-World Launch Scenarios
2073
-
2074
- ### Scenario 1: iPhone 15 Pro Launch
2075
-
2076
- **Context:**
2077
-
2078
- - Product: iPhone 15 Pro 256GB Titanium Blue
2079
- - Expected demand: 10,000 pre-orders
2080
- - Available inventory: 8,500 units
2081
- - Launch date: September 22, 2025
2082
-
2083
- **Configuration:**
2084
-
2085
- ```json
2086
- {
2087
- "overbooking": 1.03, // Conservative for high-value item
2088
- "tiers": {
2089
- "VIP": { "score": 100 },
2090
- "STANDARD": { "score": 50 }
2091
- },
2092
- "expiration": 14 // 14 days for expensive item
2093
- }
2094
- ```
2095
-
2096
- **Results:**
2097
-
2098
- - Pre-orders received: 11,234 (12% over inventory)
2099
- - VIP reservations: 1,234 (11%)
2100
- - Standard reservations: 10,000 (89%)
2101
- - Confirmed on launch: 8,925 (105%)
2102
- - Cancellations: 425 (3.8%)
2103
- - Final allocation: 8,500 units (100%)
2104
-
2105
- **Priority breakdown:**
2106
-
2107
- - VIP customers: 1,234 confirmed (100%)
2108
- - Standard customers: 7,691 confirmed (76.9%)
2109
- - Standard cancellations: 2,309 (23.1%)
2110
-
2111
- ### Scenario 2: Limited Edition Sneakers
2112
-
2113
- **Context:**
2114
-
2115
- - Product: Air Jordan 1 Retro High "Trophy Room"
2116
- - Expected demand: 50,000+ pre-orders
2117
- - Available inventory: 5,000 pairs
2118
- - Release date: March 15, 2025
2119
-
2120
- **Configuration:**
2121
-
2122
- ```json
2123
- {
2124
- "overbooking": 1.02, // Very conservative for limited edition
2125
- "tiers": {
2126
- "PLATINUM": { "score": 150 },
2127
- "VIP": { "score": 100 },
2128
- "STANDARD": { "score": 50 }
2129
- },
2130
- "expiration": 3, // Short window for high demand
2131
- "maxQueueDepth": 10000 // Cap queue at 2x inventory
2132
- }
2133
- ```
2134
-
2135
- **Results:**
2136
-
2137
- - Pre-orders received: 52,389 (first 2 hours)
2138
- - Queue capped at: 10,000 reservations
2139
- - Overflow: 42,389 added to waitlist
2140
- - Confirmed on launch: 5,100 (102%)
2141
- - Cancellations: 100 (1.9%)
2142
- - Final allocation: 5,000 pairs (100%)
2143
-
2144
- **Priority breakdown:**
2145
-
2146
- - Platinum: 450 confirmed (100%)
2147
- - VIP: 2,550 confirmed (100%)
2148
- - Standard: 2,000 confirmed (31.7% of queue)
2149
-
2150
- ### Scenario 3: Gaming Console Launch
2151
-
2152
- **Context:**
2153
-
2154
- - Product: PlayStation 6
2155
- - Expected demand: 25,000 pre-orders
2156
- - Available inventory: 20,000 units (multiple waves)
2157
- - Launch date: November 15, 2025
2158
-
2159
- **Configuration:**
2160
-
2161
- ```json
2162
- {
2163
- "overbooking": 1.05, // Standard overbooking
2164
- "waves": [
2165
- { "date": "2025-11-15", "quantity": 10000 },
2166
- { "date": "2025-11-22", "quantity": 5000 },
2167
- { "date": "2025-11-29", "quantity": 5000 }
2168
- ],
2169
- "expiration": 7
2170
- }
2171
- ```
2172
-
2173
- **Results:**
2174
-
2175
- - Total pre-orders: 27,450
2176
- - Wave 1 (Nov 15): 10,500 confirmed
2177
- - Wave 2 (Nov 22): 5,250 confirmed
2178
- - Wave 3 (Nov 29): 5,250 confirmed
2179
- - Total confirmed: 21,000 (105%)
2180
- - Total cancelled: 6,450 (23.5%)
2181
-
2182
- **Key learnings:**
2183
-
2184
- - Multiple waves reduce cancellation rate
2185
- - Customers willing to wait for later wave
2186
- - VIP upgrade offered to later wave customers
2187
-
2188
- ---
2189
-
2190
- ## Related Guides
2191
-
2192
- - **02-scheduled-csv-inventory.md** - Scheduled inventory ingestion patterns
2193
- - **03-kv-state-management.md** - VersoriKV state management techniques
2194
- - **04-webhook-xml-response.md** - Custom webhook response patterns
2195
- - **05-real-time-inventory-sync.md** - Real-time inventory updates
2196
- - **GraphQL Query Patterns** - Complex GraphQL query techniques
2197
- - **Priority Queue Design** - Queue management algorithms
2198
-
2199
- ---
2200
-
2201
- ## Next Steps
2202
-
2203
- 1. **Email Integration**: Add SendGrid/SES for customer notifications
2204
-
2205
- 2. **Analytics Dashboard**: Build S3 → QuickSight pipeline for insights
2206
-
2207
- 3. **Demand Forecasting**: ML model to predict cancellation rates
2208
-
2209
- 4. **Dynamic Overbooking**: Adjust percentage based on historical data
2210
-
2211
- 5. **Multi-Tier Pricing**: Offer priority tiers as paid upgrades
2212
-
2213
- 6. **Inventory Pooling**: Share ATP across multiple locations
2214
-
2215
- 7. **Webhook Retry**: Add exponential backoff for failed confirmations
2216
-
2217
- 8. **Audit Trail**: Store all ATP calculations for debugging
2218
-
2219
- ---
2220
-
2221
- **Need Help?**
2222
-
2223
- - SDK Documentation: `/fc-connect-sdk/docs/readme.md`
2224
- - Example Connectors: `/connectors/Sample versori connectors/`
2225
- - GraphQL Patterns: `/docs/guides/graphql-patterns.md`
2226
- - Versori Platform: https://docs.versori.com
1
+ # Versori Pre-Order Allocation Management
2
+
3
+ **FC Connect SDK Use Case Guide**
4
+
5
+ > **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
6
+ > **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
7
+
8
+ **Context**: Real-time pre-order reservation system with future ATP (Available to Promise) calculation and priority queuing
9
+
10
+ **Complexity**: High
11
+
12
+ **Volume**: High during launches (100-10,000 pre-orders)
13
+
14
+ **Latency**: Real-time (< 2 seconds)
15
+
16
+ **Runtime**: Versori Platform (HTTP Webhook + Scheduled)
17
+
18
+ **Pattern**: Webhook + future ATP check + reservation + priority queue + scheduled release
19
+
20
+ **Estimated Lines**: ~1200 lines
21
+
22
+ ## What You'll Build
23
+
24
+ - **Pre-Order Webhook**: Receive pre-order requests from e-commerce platforms
25
+
26
+ - **Future ATP Calculation**: Calculate available inventory for future ship dates
27
+ - Query current inventory levels
28
+ - Query existing reservations for target date
29
+ - Calculate net available quantity
30
+ - Apply overbooking strategy (105% of expected inventory)
31
+
32
+ - **Reservation Management**: Create and track reservations in Fluent
33
+ - Store reservation with priority tier (VIP, Standard)
34
+ - Assign queue position within tier
35
+ - Set expiration window (7 days before ship date)
36
+
37
+ - **Priority Queuing**: VIP customers get priority allocation
38
+ - Tier-based scoring (VIP=100, Standard=50)
39
+ - First-come-first-served within tier
40
+ - Queue position tracking via VersoriKV
41
+
42
+ - **Scheduled Release Workflow**: Convert reservations to allocations on ship date
43
+ - Query all reservations for current date
44
+ - Convert to fulfillment orders
45
+ - Handle insufficient inventory (cancel lower priority)
46
+ - Email notifications to customers
47
+
48
+ - **Cancellation Workflow**: Release reservation and offer to next in queue
49
+
50
+ - **Admin Dashboard Export**: Export reservation status to S3
51
+
52
+ ## SDK Methods Used
53
+
54
+ - `webhook('name', { response: { mode } })` - HTTP webhook endpoints
55
+ - `schedule('name', 'cron')` - Scheduled workflows
56
+ - `createClient(ctx)` - Create Fluent client
57
+ - `client.graphql({ query, variables })` - Execute GraphQL queries/mutations
58
+ - `VersoriKVAdapter(openKv())` - State management
59
+ - `VersoriFileTracker` - Track processed reservations
60
+ - `GraphQLMutationMapper` - Custom mutations for reservations
61
+ - `UniversalMapper` - Field transformations
62
+ - `XMLBuilder` - Response formatting
63
+
64
+ ---
65
+
66
+ ## Versori Workflows Structure
67
+
68
+ **Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
69
+
70
+ **Trigger Types:**
71
+ - **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
72
+ - **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
73
+ - **`http()`** → External API calls (chained from webhook/schedule)
74
+ - **`fn()`** → Internal processing (chained from webhook/schedule)
75
+
76
+ ### Recommended Project Structure
77
+
78
+ ```
79
+ pre-order-allocation/
80
+ ├── index.ts # Entry point - exports all workflows
81
+ └── src/
82
+ ├── workflows/
83
+ │ ├── webhook/
84
+ │ │ └── reservation.ts # Webhook: Create reservations
85
+ │ │
86
+ │ └── scheduled/
87
+ │ └── release-reservations.ts # Scheduled: Release reservations
88
+
89
+ ├── services/
90
+ │ └── reservation.service.ts # Shared orchestration logic (reusable)
91
+
92
+ └── config/
93
+ └── reservation-config.json # Configuration
94
+ ```
95
+
96
+ **Benefits:**
97
+ - ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
98
+ - ✅ Descriptive file names (easy to browse and understand)
99
+ - ✅ Scalable (add new workflows without cluttering)
100
+ - ✅ Reusable code in `services/` (DRY principle)
101
+ - ✅ Easy to modify individual workflows without affecting others
102
+
103
+ ---
104
+
105
+ ## Complete Working Code
106
+
107
+ ### 1. Main Workflow File: `index.ts`
108
+
109
+ ```typescript
110
+ /**
111
+ * Pre-Order Allocation Management System
112
+ *
113
+ * Handles high-volume product launches with priority-based reservations
114
+ * and future inventory allocation.
115
+ *
116
+ * Key Features:
117
+ * - Real-time ATP (Available to Promise) calculation for future dates
118
+ * - Priority queuing (VIP vs Standard customers)
119
+ * - Reservation expiration management
120
+ * - Overbooking strategy (105% allocation)
121
+ * - Scheduled release on ship date
122
+ * - Cancellation with queue reallocation
123
+ */
124
+
125
+ import { webhook, schedule, http, fn } from '@versori/run';
126
+ import {
127
+ createClient,
128
+ VersoriKVAdapter,
129
+ VersoriFileTracker,
130
+ UniversalMapper,
131
+ } from '@fluentcommerce/fc-connect-sdk';
132
+
133
+ // =============================================================================
134
+ // TYPE DEFINITIONS
135
+ // =============================================================================
136
+
137
+ interface PreOrderRequest {
138
+ orderId: string;
139
+ customerId: string;
140
+ customerEmail: string;
141
+ customerTier: 'VIP' | 'STANDARD'; // Priority tier
142
+ items: PreOrderItem[];
143
+ shipDate: string; // Future ship date (ISO 8601)
144
+ source: string; // E-commerce platform identifier
145
+ }
146
+
147
+ interface PreOrderItem {
148
+ sku: string;
149
+ productName: string;
150
+ quantity: number;
151
+ locationRef: string;
152
+ }
153
+
154
+ interface ReservationRecord {
155
+ reservationId: string;
156
+ orderId: string;
157
+ customerId: string;
158
+ customerEmail: string;
159
+ sku: string;
160
+ quantity: number;
161
+ locationRef: string;
162
+ shipDate: string;
163
+ priority: number; // Calculated priority score
164
+ queuePosition: number;
165
+ tier: 'VIP' | 'STANDARD';
166
+ status: 'RESERVED' | 'CONFIRMED' | 'CANCELLED' | 'EXPIRED';
167
+ createdAt: string;
168
+ expiresAt: string; // 7 days before ship date
169
+ confirmedAt?: string;
170
+ cancelledAt?: string;
171
+ }
172
+
173
+ interface ATPCalculation {
174
+ sku: string;
175
+ locationRef: string;
176
+ shipDate: string;
177
+ currentInventory: number;
178
+ existingReservations: number;
179
+ expectedArrival: number; // Expected inventory by ship date
180
+ availableToPromise: number;
181
+ overbookingLimit: number; // 105% of expected
182
+ canFulfill: boolean;
183
+ }
184
+
185
+ // =============================================================================
186
+ // WORKFLOW 1: PRE-ORDER WEBHOOK (Real-time)
187
+ // =============================================================================
188
+
189
+ /**
190
+ * Pre-Order Webhook - Receives pre-order from e-commerce platform
191
+ *
192
+ * Flow:
193
+ * 1. Parse and validate pre-order request
194
+ * 2. Calculate future ATP for each item
195
+ * 3. Create reservation if inventory available
196
+ * 4. Assign priority and queue position
197
+ * 5. Store reservation in VersoriKV
198
+ * 6. Send confirmation response
199
+ */
200
+ export const preOrderWebhook = webhook('pre-order', {
201
+ response: {
202
+ mode: 'sync',
203
+ // Custom response handler for JSON
204
+ onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
205
+ status: 200,
206
+ headers: { 'Content-Type': 'application/json' }
207
+ }),
208
+ onError: (ctx) => new Response(JSON.stringify({
209
+ success: false,
210
+ error: ctx.data instanceof Error ? ctx.data.message : String(ctx.data),
211
+ timestamp: new Date().toISOString()
212
+ }), {
213
+ status: 400,
214
+ headers: { 'Content-Type': 'application/json' }
215
+ })
216
+ },
217
+ cors: true
218
+ })
219
+ // Step 1: Validate pre-order request
220
+ .then(fn('validate-request', (ctx) => {
221
+ const { data, log } = ctx;
222
+
223
+ log.info('🔍 Validating pre-order request');
224
+ const request = data as PreOrderRequest;
225
+
226
+ // Validation checks
227
+ if (!request.orderId || !request.customerId || !request.customerEmail) {
228
+ log.error('[PreOrderAllocation] Validation error: Missing required fields', {
229
+ recommendation: 'Ensure pre-order request includes orderId, customerId, and customerEmail'
230
+ });
231
+ throw new Error('Missing required fields: orderId, customerId, or customerEmail');
232
+ }
233
+
234
+ if (!request.items || request.items.length === 0) {
235
+ log.error('[PreOrderAllocation] Validation error: No items in pre-order', {
236
+ recommendation: 'Pre-order must contain at least one item'
237
+ });
238
+ throw new Error('Pre-order must contain at least one item');
239
+ }
240
+
241
+ if (!request.shipDate) {
242
+ log.error('[PreOrderAllocation] Validation error: Missing ship date', {
243
+ recommendation: 'Ship date is required for pre-orders'
244
+ });
245
+ throw new Error('Ship date is required for pre-orders');
246
+ }
247
+
248
+ // Validate ship date is in the future
249
+ const shipDate = new Date(request.shipDate);
250
+ const now = new Date();
251
+ const daysDiff = Math.floor((shipDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
252
+
253
+ if (daysDiff < 7) {
254
+ log.error('[PreOrderAllocation] Validation error: Ship date too soon', {
255
+ daysDiff,
256
+ recommendation: 'Ship date must be at least 7 days in the future for pre-orders'
257
+ });
258
+ throw new Error(`Ship date must be at least 7 days in the future (got ${daysDiff} days)`);
259
+ }
260
+
261
+ if (daysDiff > 180) {
262
+ log.error('[PreOrderAllocation] Validation error: Ship date too far in future', {
263
+ daysDiff,
264
+ recommendation: 'Ship date cannot be more than 180 days in the future'
265
+ });
266
+ throw new Error(`Ship date cannot be more than 180 days in the future (got ${daysDiff} days)`);
267
+ }
268
+
269
+ log.info('✅ Pre-order request validated', {
270
+ orderId: request.orderId,
271
+ itemCount: request.items.length,
272
+ shipDate: request.shipDate,
273
+ daysUntilShip: daysDiff,
274
+ tier: request.customerTier || 'STANDARD'
275
+ });
276
+
277
+ return {
278
+ request,
279
+ shipDate,
280
+ daysUntilShip: daysDiff,
281
+ expiresAt: new Date(shipDate.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days before
282
+ };
283
+ }))
284
+
285
+ // Step 2: Calculate future ATP for each item
286
+ .then(http('calculate-atp', {
287
+ connection: 'fluent_commerce'
288
+ }, async (ctx) => {
289
+ const { request, shipDate, expiresAt } = ctx.data;
290
+ const { log } = ctx;
291
+
292
+ log.info('📊 Calculating future ATP for items');
293
+
294
+ const startTime = Date.now();
295
+ const client = await createClient(ctx, { validateConnection: true });
296
+ const atpResults: ATPCalculation[] = [];
297
+
298
+ for (const item of request.items) {
299
+ try {
300
+ // Query current inventory
301
+ const inventoryResult = await client.graphql({
302
+ query: `
303
+ query GetInventory($locationRef: String!, $skuRef: String!) {
304
+ inventoryQuantities(
305
+ first: 1
306
+ locationRef: $locationRef
307
+ skuRef: $skuRef
308
+ type: "LAST_ON_HAND"
309
+ ) {
310
+ edges {
311
+ node {
312
+ id
313
+ quantity
314
+ expectedOn
315
+ }
316
+ }
317
+ }
318
+ }`,
319
+ variables: {
320
+ locationRef: item.locationRef,
321
+ skuRef: item.sku
322
+ }
323
+ });
324
+
325
+ const currentInventory = inventoryResult.data?.inventoryQuantities?.edges?.[0]?.node?.quantity || 0;
326
+
327
+ // Query existing reservations for this SKU + ship date
328
+ // NOTE: This assumes reservations are stored as custom attributes
329
+ // In production, you'd query a custom entity type or use attributes
330
+ const reservationsResult = await client.graphql({
331
+ query: `
332
+ query GetReservations($sku: String!, $shipDate: String!) {
333
+ # This is a placeholder - implement based on your reservation storage
334
+ # Option 1: Custom entity type
335
+ # Option 2: Inventory attributes
336
+ # Option 3: External database
337
+ inventoryQuantities(
338
+ first: 100
339
+ skuRef: $sku
340
+ status: "RESERVED"
341
+ ) {
342
+ edges {
343
+ node {
344
+ id
345
+ quantity
346
+ attributes {
347
+ name
348
+ value
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ `,
355
+ variables: {
356
+ sku: item.sku,
357
+ shipDate: request.shipDate
358
+ }
359
+ });
360
+
361
+ // Calculate existing reservations
362
+ let existingReservations = 0;
363
+ const reservationEdges = reservationsResult.data?.inventoryQuantities?.edges || [];
364
+ for (const edge of reservationEdges) {
365
+ const shipDateAttr = edge.node.attributes?.find((a: any) => a.name === 'shipDate');
366
+ if (shipDateAttr?.value === request.shipDate) {
367
+ existingReservations += edge.node.quantity || 0;
368
+ }
369
+ }
370
+
371
+ // Query expected arrivals (purchase orders, transfers)
372
+ const expectedArrivalResult = await client.graphql({
373
+ query: `
374
+ query GetExpectedArrivals($locationRef: String!, $skuRef: String!, $beforeDate: String!) {
375
+ inventoryQuantities(
376
+ first: 50
377
+ locationRef: $locationRef
378
+ skuRef: $skuRef
379
+ type: "EXPECTED"
380
+ expectedOnBefore: $beforeDate
381
+ ) {
382
+ edges {
383
+ node {
384
+ quantity
385
+ expectedOn
386
+ }
387
+ }
388
+ }
389
+ }
390
+ `,
391
+ variables: {
392
+ locationRef: item.locationRef,
393
+ skuRef: item.sku,
394
+ beforeDate: request.shipDate
395
+ }
396
+ });
397
+
398
+ const expectedArrival = expectedArrivalResult.data?.inventoryQuantities?.edges?.reduce(
399
+ (sum: number, edge: any) => sum + (edge.node.quantity || 0),
400
+ 0
401
+ ) || 0;
402
+
403
+ // Calculate ATP with overbooking strategy
404
+ const totalExpected = currentInventory + expectedArrival;
405
+ const overbookingLimit = Math.floor(totalExpected * 1.05); // 105% overbooking
406
+ const availableToPromise = overbookingLimit - existingReservations;
407
+
408
+ const atp: ATPCalculation = {
409
+ sku: item.sku,
410
+ locationRef: item.locationRef,
411
+ shipDate: request.shipDate,
412
+ currentInventory,
413
+ existingReservations,
414
+ expectedArrival,
415
+ availableToPromise,
416
+ overbookingLimit,
417
+ canFulfill: availableToPromise >= item.quantity
418
+ };
419
+
420
+ atpResults.push(atp);
421
+
422
+ log.info('✅ ATP calculated', {
423
+ sku: item.sku,
424
+ current: currentInventory,
425
+ expected: expectedArrival,
426
+ reserved: existingReservations,
427
+ atp: availableToPromise,
428
+ requested: item.quantity,
429
+ canFulfill: atp.canFulfill
430
+ });
431
+ } catch (error) {
432
+ // ? Enhanced: Error logging with recommendations
433
+ log.error('[PreOrderAllocation] Failed to calculate ATP for SKU', {
434
+ sku: item.sku,
435
+ error: error instanceof Error ? error.message : String(error),
436
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
437
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
438
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
439
+ : error.message?.includes('query') || error.message?.includes('GraphQL')
440
+ ? 'Check GraphQL query syntax and inventory query structure'
441
+ : error.message?.includes('connection') || error.message?.includes('timeout')
442
+ ? 'Check network connectivity and Fluent Commerce API availability'
443
+ : 'Review error details and check ATP calculation logic'
444
+ });
445
+ throw error;
446
+ }
447
+ }
448
+
449
+ // Check if all items can be fulfilled
450
+ const allAvailable = atpResults.every(atp => atp.canFulfill);
451
+ const duration = Date.now() - startTime;
452
+
453
+ log.info(`⏱️ ATP calculation complete (${duration}ms)`, {
454
+ totalItems: atpResults.length,
455
+ allAvailable
456
+ });
457
+
458
+ return {
459
+ ...ctx.data,
460
+ atpResults,
461
+ allAvailable,
462
+ client
463
+ };
464
+ }))
465
+
466
+ // Step 3: Create reservations or add to waitlist
467
+ .then(fn('create-reservations', async ({ data, openKv, log }) => {
468
+ const { request, atpResults, allAvailable, expiresAt, client } = data;
469
+
470
+ log.info('📦 Creating reservations');
471
+
472
+ const kvAdapter = new VersoriKVAdapter(openKv());
473
+ const reservationTracker = new VersoriFileTracker(openKv(), 'pre-order-reservations');
474
+
475
+ const reservations: ReservationRecord[] = [];
476
+ const failedItems: any[] = [];
477
+
478
+ // Calculate priority score
479
+ const tierScore = request.customerTier === 'VIP' ? 100 : 50;
480
+ const timeScore = Date.now(); // Earlier orders get lower scores (better priority)
481
+ const priorityScore = tierScore * 1000000 - timeScore; // VIP orders always sort higher
482
+
483
+ for (let i = 0; i < request.items.length; i++) {
484
+ const item = request.items[i];
485
+ const atp = atpResults[i];
486
+
487
+ if (!atp.canFulfill) {
488
+ failedItems.push({
489
+ sku: item.sku,
490
+ quantity: item.quantity,
491
+ available: atp.availableToPromise,
492
+ reason: 'Insufficient inventory available for this ship date'
493
+ });
494
+ continue;
495
+ }
496
+
497
+ // Get current queue position for this SKU + ship date
498
+ const queueKey = ['queue', atp.sku, atp.locationRef, request.shipDate].join(':');
499
+ const queueData = await kvAdapter.get([queueKey]);
500
+ const currentQueueLength = (queueData?.value as number) || 0;
501
+ const queuePosition = currentQueueLength + 1;
502
+
503
+ // Update queue length
504
+ await kvAdapter.set([queueKey], queuePosition);
505
+
506
+ // Create reservation record
507
+ const reservationId = `RES-${request.orderId}-${item.sku}-${Date.now()}`;
508
+ const reservation: ReservationRecord = {
509
+ reservationId,
510
+ orderId: request.orderId,
511
+ customerId: request.customerId,
512
+ customerEmail: request.customerEmail,
513
+ sku: item.sku,
514
+ quantity: item.quantity,
515
+ locationRef: item.locationRef,
516
+ shipDate: request.shipDate,
517
+ priority: priorityScore,
518
+ queuePosition,
519
+ tier: request.customerTier || 'STANDARD',
520
+ status: 'RESERVED',
521
+ createdAt: new Date().toISOString(),
522
+ expiresAt
523
+ };
524
+
525
+ // Store reservation in KV
526
+ await kvAdapter.set(['reservation', reservationId], reservation);
527
+
528
+ // Track in indexed tracker for listing
529
+ await reservationTracker.markFileProcessed(reservationId, {
530
+ orderId: request.orderId,
531
+ sku: item.sku,
532
+ shipDate: request.shipDate,
533
+ priority: priorityScore
534
+ });
535
+
536
+ // Create reservation in Fluent (as custom inventory type or entity)
537
+ try {
538
+ await client.graphql({
539
+ query: `
540
+ mutation CreateReservation($input: InventoryQuantityInput!) {
541
+ createInventoryQuantity(input: $input) {
542
+ id
543
+ ref
544
+ quantity
545
+ status
546
+ }
547
+ }
548
+ `,
549
+ variables: {
550
+ input: {
551
+ ref: reservationId,
552
+ locationRef: item.locationRef,
553
+ skuRef: item.sku,
554
+ type: 'RESERVED',
555
+ status: 'RESERVED',
556
+ quantity: item.quantity,
557
+ expectedOn: request.shipDate,
558
+ attributes: [
559
+ { name: 'orderId', value: request.orderId },
560
+ { name: 'customerId', value: request.customerId },
561
+ { name: 'customerEmail', value: request.customerEmail },
562
+ { name: 'tier', value: request.customerTier || 'STANDARD' },
563
+ { name: 'priority', value: String(priorityScore) },
564
+ { name: 'queuePosition', value: String(queuePosition) },
565
+ { name: 'expiresAt', value: expiresAt },
566
+ { name: 'reservationId', value: reservationId }
567
+ ]
568
+ }
569
+ }
570
+ });
571
+
572
+ log.info('✅ Reservation created in Fluent', {
573
+ reservationId,
574
+ sku: item.sku,
575
+ quantity: item.quantity,
576
+ priority: priorityScore,
577
+ queuePosition
578
+ });
579
+ } catch (error) {
580
+ // ? Enhanced: Error logging with recommendations
581
+ log.error('[PreOrderAllocation] Failed to create reservation in Fluent', {
582
+ reservationId,
583
+ error: error instanceof Error ? error.message : String(error),
584
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
585
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
586
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
587
+ : error.message?.includes('mutation') || error.message?.includes('GraphQL')
588
+ ? 'Check GraphQL mutation syntax and reservation payload structure'
589
+ : error.message?.includes('inventory') || error.message?.includes('quantity')
590
+ ? 'Check available inventory quantity and reservation limits'
591
+ : error.message?.includes('connection') || error.message?.includes('timeout')
592
+ ? 'Check network connectivity and Fluent Commerce API availability'
593
+ : 'Review error details - reservation is in KV and can be retried'
594
+ });
595
+ // Continue - reservation is in KV, can be retried
596
+ }
597
+
598
+ reservations.push(reservation);
599
+ }
600
+
601
+ return {
602
+ success: failedItems.length === 0,
603
+ reservations,
604
+ failedItems,
605
+ orderId: request.orderId,
606
+ shipDate: request.shipDate,
607
+ tier: request.customerTier || 'STANDARD'
608
+ };
609
+ }))
610
+
611
+ // Step 4: Send email confirmation (placeholder)
612
+ .then(fn('send-confirmation', async ({ data, log }) => {
613
+ const { success, reservations, failedItems, orderId, shipDate, tier } = data;
614
+
615
+ log.info('📧 Sending customer notification');
616
+
617
+ // TODO: Integrate with email service (SendGrid, SES, etc.)
618
+ // This is a placeholder showing the data structure
619
+ const emailPayload = {
620
+ to: reservations[0]?.customerEmail,
621
+ subject: success
622
+ ? `Pre-Order Confirmed - Order ${orderId}`
623
+ : `Pre-Order Partially Confirmed - Order ${orderId}`,
624
+ body: {
625
+ orderId,
626
+ shipDate,
627
+ tier,
628
+ confirmedItems: reservations.map(r => ({
629
+ sku: r.sku,
630
+ quantity: r.quantity,
631
+ queuePosition: r.queuePosition,
632
+ expiresAt: r.expiresAt
633
+ })),
634
+ failedItems,
635
+ message: success
636
+ ? `Your pre-order has been confirmed! You are ${tier === 'VIP' ? 'priority' : 'standard'} tier.`
637
+ : `Some items could not be reserved. Please review below.`
638
+ }
639
+ };
640
+
641
+ log.info('✅ Email prepared', {
642
+ to: emailPayload.to,
643
+ confirmedCount: reservations.length,
644
+ failedCount: failedItems.length
645
+ });
646
+
647
+ // In production: await emailService.send(emailPayload);
648
+
649
+ return {
650
+ success,
651
+ orderId,
652
+ reservations: reservations.map(r => ({
653
+ reservationId: r.reservationId,
654
+ sku: r.sku,
655
+ quantity: r.quantity,
656
+ queuePosition: r.queuePosition,
657
+ priority: r.priority,
658
+ expiresAt: r.expiresAt
659
+ })),
660
+ failedItems,
661
+ message: success
662
+ ? `All items reserved successfully`
663
+ : `${reservations.length} items reserved, ${failedItems.length} failed`,
664
+ timestamp: new Date().toISOString()
665
+ };
666
+ }));
667
+
668
+ // =============================================================================
669
+ // WORKFLOW 2: SCHEDULED RELEASE (Daily at 2 AM)
670
+ // =============================================================================
671
+
672
+ /**
673
+ * Scheduled Release Workflow - Convert reservations to allocations
674
+ *
675
+ * Runs daily at 2 AM to process reservations for today's ship date
676
+ *
677
+ * Flow:
678
+ * 1. Query all reservations for today's ship date
679
+ * 2. Sort by priority (VIP first, then by queue position)
680
+ * 3. Convert to fulfillment orders
681
+ * 4. Handle insufficient inventory (cancel lower priority)
682
+ * 5. Send confirmation/cancellation emails
683
+ */
684
+ export const scheduledRelease = schedule('release-reservations', '0 2 * * *', async (ctx) => {
685
+ const { log, openKv } = ctx;
686
+ const startTime = Date.now();
687
+
688
+ return http('process-release', {
689
+ connection: 'fluent_commerce'
690
+ }, async (ctx) => {
691
+ const { log } = ctx;
692
+ const client = await createClient(ctx, { validateConnection: true });
693
+ const kvAdapter = new VersoriKVAdapter(openKv());
694
+ const reservationTracker = new VersoriFileTracker(openKv(), 'pre-order-reservations');
695
+
696
+ log.info('🚀 Starting scheduled release process');
697
+
698
+ const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
699
+
700
+ try {
701
+ // Step 1: Query all reservations for today
702
+ const result = await client.graphql({
703
+ query: `
704
+ query GetTodayReservations($shipDate: String!) {
705
+ inventoryQuantities(
706
+ first: 1000
707
+ type: "RESERVED"
708
+ status: "RESERVED"
709
+ expectedOn: $shipDate
710
+ ) {
711
+ edges {
712
+ node {
713
+ id
714
+ ref
715
+ quantity
716
+ skuRef
717
+ locationRef
718
+ attributes {
719
+ name
720
+ value
721
+ }
722
+ }
723
+ }
724
+ }
725
+ }
726
+ `,
727
+ variables: { shipDate: today }
728
+ });
729
+
730
+ const reservationEdges = result.data?.inventoryQuantities?.edges || [];
731
+ log.info(`📋 Found ${reservationEdges.length} reservations for ${today}`);
732
+
733
+ // Step 2: Parse and sort by priority
734
+ interface ReservationWithPriority {
735
+ id: string;
736
+ ref: string;
737
+ quantity: number;
738
+ sku: string;
739
+ locationRef: string;
740
+ priority: number;
741
+ queuePosition: number;
742
+ orderId: string;
743
+ customerId: string;
744
+ customerEmail: string;
745
+ tier: string;
746
+ reservationId: string;
747
+ }
748
+
749
+ const reservations: ReservationWithPriority[] = reservationEdges.map((edge: any) => {
750
+ const attrs = edge.node.attributes || [];
751
+ const getAttr = (name: string) => attrs.find((a: any) => a.name === name)?.value;
752
+
753
+ return {
754
+ id: edge.node.id,
755
+ ref: edge.node.ref,
756
+ quantity: edge.node.quantity,
757
+ sku: edge.node.skuRef,
758
+ locationRef: edge.node.locationRef,
759
+ priority: parseInt(getAttr('priority') || '0'),
760
+ queuePosition: parseInt(getAttr('queuePosition') || '999'),
761
+ orderId: getAttr('orderId') || '',
762
+ customerId: getAttr('customerId') || '',
763
+ customerEmail: getAttr('customerEmail') || '',
764
+ tier: getAttr('tier') || 'STANDARD',
765
+ reservationId: getAttr('reservationId') || edge.node.ref
766
+ };
767
+ });
768
+
769
+ // Sort by priority (higher priority = lower number due to negative time component)
770
+ reservations.sort((a, b) => b.priority - a.priority);
771
+
772
+ log.info('✅ Sorted reservations by priority', {
773
+ vipCount: reservations.filter(r => r.tier === 'VIP').length,
774
+ standardCount: reservations.filter(r => r.tier === 'STANDARD').length
775
+ });
776
+
777
+ // Step 3: Group by SKU and location
778
+ const groupedBySku = new Map<string, ReservationWithPriority[]>();
779
+ for (const reservation of reservations) {
780
+ const key = `${reservation.sku}:${reservation.locationRef}`;
781
+ if (!groupedBySku.has(key)) {
782
+ groupedBySku.set(key, []);
783
+ }
784
+ groupedBySku.get(key)!.push(reservation);
785
+ }
786
+
787
+ // Step 4: Process each SKU group
788
+ const confirmed: any[] = [];
789
+ const cancelled: any[] = [];
790
+
791
+ for (const [skuKey, skuReservations] of groupedBySku.entries()) {
792
+ const [sku, locationRef] = skuKey.split(':');
793
+
794
+ // Query current inventory
795
+ const invResult = await client.graphql({
796
+ query: `
797
+ query GetInventory($sku: String!, $locationRef: String!) {
798
+ inventoryQuantities(
799
+ first: 1
800
+ skuRef: $sku
801
+ locationRef: $locationRef
802
+ type: "LAST_ON_HAND"
803
+ ) {
804
+ edges {
805
+ node {
806
+ quantity
807
+ }
808
+ }
809
+ }
810
+ }
811
+ `,
812
+ variables: { sku, locationRef }
813
+ });
814
+
815
+ let availableQty = invResult.data?.inventoryQuantities?.edges?.[0]?.node?.quantity || 0;
816
+
817
+ log.info(`⚙️ Processing ${skuReservations.length} reservations for ${sku}`, {
818
+ available: availableQty,
819
+ totalRequested: skuReservations.reduce((sum, r) => sum + r.quantity, 0)
820
+ });
821
+
822
+ // Allocate inventory by priority
823
+ for (const reservation of skuReservations) {
824
+ if (availableQty >= reservation.quantity) {
825
+ // Confirm reservation - create fulfillment order
826
+ try {
827
+ await client.graphql({
828
+ query: `
829
+ mutation CreateFulfillmentOrder($input: CreateOrderInput!) {
830
+ createOrder(input: $input) {
831
+ id
832
+ ref
833
+ status
834
+ }
835
+ }
836
+ `,
837
+ variables: {
838
+ input: {
839
+ ref: `FO-${reservation.orderId}-${Date.now()}`,
840
+ type: 'FULFILLMENT',
841
+ retailerId: '1', // From config
842
+ customer: {
843
+ ref: reservation.customerId,
844
+ email: reservation.customerEmail
845
+ },
846
+ items: [{
847
+ skuRef: reservation.sku,
848
+ quantity: reservation.quantity,
849
+ locationRef: reservation.locationRef
850
+ }],
851
+ attributes: [
852
+ { name: 'originalOrderId', value: reservation.orderId },
853
+ { name: 'reservationId', value: reservation.reservationId },
854
+ { name: 'tier', value: reservation.tier },
855
+ { name: 'shipDate', value: today }
856
+ ]
857
+ }
858
+ }
859
+ });
860
+
861
+ // Update reservation status
862
+ await client.graphql({
863
+ query: `
864
+ mutation UpdateReservation($id: ID!, $status: String!) {
865
+ updateInventoryQuantity(id: $id, status: $status) {
866
+ id
867
+ status
868
+ }
869
+ }
870
+ `,
871
+ variables: {
872
+ id: reservation.id,
873
+ status: 'CONFIRMED'
874
+ }
875
+ });
876
+
877
+ // Update KV
878
+ const kvRecord = await kvAdapter.get(['reservation', reservation.reservationId]);
879
+ if (kvRecord?.value) {
880
+ const record = kvRecord.value as ReservationRecord;
881
+ record.status = 'CONFIRMED';
882
+ record.confirmedAt = new Date().toISOString();
883
+ await kvAdapter.set(['reservation', reservation.reservationId], record);
884
+ }
885
+
886
+ availableQty -= reservation.quantity;
887
+ confirmed.push(reservation);
888
+
889
+ log.info(`✅ Confirmed reservation: ${reservation.reservationId}`);
890
+ } catch (error) {
891
+ // ? Enhanced: Error logging with recommendations
892
+ log.error('[PreOrderAllocation] Failed to confirm reservation', {
893
+ reservationId: reservation.reservationId,
894
+ error: error instanceof Error ? error.message : String(error),
895
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
896
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
897
+ : error.message?.includes('mutation') || error.message?.includes('GraphQL')
898
+ ? 'Check GraphQL mutation syntax and reservation confirmation payload'
899
+ : error.message?.includes('inventory') || error.message?.includes('quantity')
900
+ ? 'Check available inventory quantity and reservation status'
901
+ : 'Review error details and check reservation confirmation logic'
902
+ });
903
+ }
904
+ } else {
905
+ // Insufficient inventory - cancel reservation
906
+ try {
907
+ await client.graphql({
908
+ query: `
909
+ mutation UpdateReservation($id: ID!, $status: String!) {
910
+ updateInventoryQuantity(id: $id, status: $status) {
911
+ id
912
+ status
913
+ }
914
+ }
915
+ `,
916
+ variables: {
917
+ id: reservation.id,
918
+ status: 'CANCELLED'
919
+ }
920
+ });
921
+
922
+ // Update KV
923
+ const kvRecord = await kvAdapter.get(['reservation', reservation.reservationId]);
924
+ if (kvRecord?.value) {
925
+ const record = kvRecord.value as ReservationRecord;
926
+ record.status = 'CANCELLED';
927
+ record.cancelledAt = new Date().toISOString();
928
+ await kvAdapter.set(['reservation', reservation.reservationId], record);
929
+ }
930
+
931
+ cancelled.push({
932
+ ...reservation,
933
+ reason: 'Insufficient inventory',
934
+ availableQty
935
+ });
936
+
937
+ log.warn(`⚠️ Cancelled reservation: ${reservation.reservationId}`, {
938
+ reason: 'insufficient_inventory',
939
+ needed: reservation.quantity,
940
+ available: availableQty
941
+ });
942
+ } catch (error) {
943
+ // ? Enhanced: Error logging with recommendations
944
+ log.error('[PreOrderAllocation] Failed to cancel reservation', {
945
+ reservationId: reservation.reservationId,
946
+ error: error instanceof Error ? error.message : String(error),
947
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
948
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
949
+ : error.message?.includes('mutation') || error.message?.includes('GraphQL')
950
+ ? 'Check GraphQL mutation syntax and reservation cancellation payload'
951
+ : error.message?.includes('not found') || error.message?.includes('missing')
952
+ ? 'Reservation not found - verify reservationId and check KV store'
953
+ : 'Review error details and check reservation cancellation logic'
954
+ });
955
+ }
956
+ }
957
+ }
958
+ }
959
+
960
+ // Step 5: Send notifications (placeholder)
961
+ const duration = Date.now() - startTime;
962
+ log.info(`📧 Sending notifications (${duration}ms total)`, {
963
+ confirmed: confirmed.length,
964
+ cancelled: cancelled.length
965
+ });
966
+
967
+ // TODO: Send confirmation emails to confirmed reservations
968
+ // TODO: Send cancellation/apology emails to cancelled reservations
969
+
970
+ return {
971
+ success: true,
972
+ date: today,
973
+ processed: reservations.length,
974
+ confirmed: confirmed.length,
975
+ cancelled: cancelled.length,
976
+ duration,
977
+ summary: {
978
+ vipConfirmed: confirmed.filter(r => r.tier === 'VIP').length,
979
+ standardConfirmed: confirmed.filter(r => r.tier === 'STANDARD').length,
980
+ vipCancelled: cancelled.filter(r => r.tier === 'VIP').length,
981
+ standardCancelled: cancelled.filter(r => r.tier === 'STANDARD').length
982
+ },
983
+ timestamp: new Date().toISOString()
984
+ };
985
+ } catch (error) {
986
+ // ? Enhanced: Error logging with recommendations
987
+ log.error('[PreOrderAllocation] Scheduled release failed', {
988
+ error: error instanceof Error ? error.message : String(error),
989
+ errorType: error instanceof Error ? error.constructor.name : 'Error',
990
+ stack: error instanceof Error ? error.stack : undefined,
991
+ recommendation: error.message?.includes('authentication') || error.message?.includes('401')
992
+ ? 'Verify fluent_commerce connection and OAuth2 credentials in Connections section'
993
+ : error.message?.includes('KV') || error.message?.includes('state')
994
+ ? 'Check KV store connectivity and reservation state structure'
995
+ : error.message?.includes('connection') || error.message?.includes('timeout')
996
+ ? 'Check network connectivity and Fluent Commerce API availability'
997
+ : 'Review error details and check scheduled release workflow configuration'
998
+ });
999
+ throw error;
1000
+ }
1001
+ })(ctx);
1002
+ });
1003
+
1004
+ // =============================================================================
1005
+ // WORKFLOW 3: CANCELLATION WEBHOOK
1006
+ // =============================================================================
1007
+
1008
+ /**
1009
+ * Cancellation Webhook - Cancel reservation and offer to next in queue
1010
+ *
1011
+ * Flow:
1012
+ * 1. Receive cancellation request
1013
+ * 2. Update reservation status
1014
+ * 3. Query next customer in queue
1015
+ * 4. Offer to next customer (if available)
1016
+ * 5. Send notifications
1017
+ */
1018
+ export const cancellationWebhook = webhook('cancel-reservation', {
1019
+ response: { mode: 'sync' },
1020
+ cors: true
1021
+ })
1022
+ .then(fn('validate-cancellation', ({ data, log }) => {
1023
+ log.info('🔍 Processing cancellation request');
1024
+
1025
+ if (!data.reservationId && !data.orderId) {
1026
+ throw new Error('Either reservationId or orderId is required');
1027
+ }
1028
+
1029
+ return data;
1030
+ }))
1031
+ .then(http('cancel-and-requeue', {
1032
+ connection: 'fluent_commerce'
1033
+ }, async (ctx) => {
1034
+ const { reservationId, orderId } = ctx.data;
1035
+ const { log, openKv } = ctx;
1036
+ const client = await createClient(ctx, { validateConnection: true });
1037
+ const kvAdapter = new VersoriKVAdapter(openKv());
1038
+
1039
+ log.info('🚫 Cancelling reservation', { reservationId, orderId });
1040
+
1041
+ // Find reservation in KV
1042
+ let reservation: ReservationRecord | null = null;
1043
+
1044
+ if (reservationId) {
1045
+ const kvRecord = await kvAdapter.get(['reservation', reservationId]);
1046
+ reservation = kvRecord?.value as ReservationRecord;
1047
+ } else {
1048
+ // Search by orderId (would need indexed tracker)
1049
+ // Placeholder: iterate through all reservations
1050
+ log.warn('[CANCEL] Searching by orderId - implement indexed search for production');
1051
+ }
1052
+
1053
+ if (!reservation) {
1054
+ throw new Error(`Reservation not found: ${reservationId || orderId}`);
1055
+ }
1056
+
1057
+ // Update status in Fluent
1058
+ const result = await client.graphql({
1059
+ query: `
1060
+ query FindReservation($ref: String!) {
1061
+ inventoryQuantities(first: 1, ref: $ref) {
1062
+ edges {
1063
+ node {
1064
+ id
1065
+ ref
1066
+ status
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+ `,
1072
+ variables: { ref: reservation.reservationId }
1073
+ });
1074
+
1075
+ const inventoryId = result.data?.inventoryQuantities?.edges?.[0]?.node?.id;
1076
+
1077
+ if (inventoryId) {
1078
+ await client.graphql({
1079
+ query: `
1080
+ mutation CancelReservation($id: ID!, $status: String!) {
1081
+ updateInventoryQuantity(id: $id, status: $status) {
1082
+ id
1083
+ status
1084
+ }
1085
+ }
1086
+ `,
1087
+ variables: {
1088
+ id: inventoryId,
1089
+ status: 'CANCELLED'
1090
+ }
1091
+ });
1092
+ }
1093
+
1094
+ // Update KV
1095
+ reservation.status = 'CANCELLED';
1096
+ reservation.cancelledAt = new Date().toISOString();
1097
+ await kvAdapter.set(['reservation', reservation.reservationId], reservation);
1098
+
1099
+ log.info('✅ Reservation cancelled', {
1100
+ reservationId: reservation.reservationId,
1101
+ sku: reservation.sku,
1102
+ quantity: reservation.quantity
1103
+ });
1104
+
1105
+ // Find next in queue
1106
+ const queueKey = ['queue', reservation.sku, reservation.locationRef, reservation.shipDate].join(':');
1107
+
1108
+ // TODO: Implement queue management
1109
+ // Query next customer with higher queue position
1110
+ // Send offer to next customer
1111
+
1112
+ log.info('📋 Queue position available for next customer', {
1113
+ sku: reservation.sku,
1114
+ shipDate: reservation.shipDate,
1115
+ releasedQuantity: reservation.quantity
1116
+ });
1117
+
1118
+ return {
1119
+ success: true,
1120
+ cancelled: {
1121
+ reservationId: reservation.reservationId,
1122
+ orderId: reservation.orderId,
1123
+ sku: reservation.sku,
1124
+ quantity: reservation.quantity
1125
+ },
1126
+ message: 'Reservation cancelled successfully',
1127
+ timestamp: new Date().toISOString()
1128
+ };
1129
+ }));
1130
+
1131
+ // =============================================================================
1132
+ // WORKFLOW 4: ADMIN DASHBOARD EXPORT
1133
+ // =============================================================================
1134
+
1135
+ /**
1136
+ * Export Reservations to S3 for Admin Dashboard
1137
+ *
1138
+ * Runs daily to export reservation status to S3 for dashboard visualization
1139
+ */
1140
+ export const exportReservations = schedule('export-reservations', '0 3 * * *', async (ctx) => {
1141
+ const { openKv, log } = ctx;
1142
+
1143
+ return fn('export-to-s3', async ({ openKv, log }) => {
1144
+ log.info('📤 Exporting reservations to S3');
1145
+
1146
+ const kvAdapter = new VersoriKVAdapter(openKv());
1147
+ const reservationTracker = new VersoriFileTracker(openKv(), 'pre-order-reservations');
1148
+
1149
+ // Get all reservations
1150
+ const allReservations = await reservationTracker.listProcessedFiles();
1151
+ log.info(`📋 Found ${allReservations.length} reservations to export`);
1152
+
1153
+ // Fetch full reservation data
1154
+ const exportData: any[] = [];
1155
+ for (const tracked of allReservations) {
1156
+ const kvRecord = await kvAdapter.get(['reservation', tracked.fileName]);
1157
+ if (kvRecord?.value) {
1158
+ exportData.push(kvRecord.value);
1159
+ }
1160
+ }
1161
+
1162
+ // Group by ship date and status
1163
+ const summary = {
1164
+ total: exportData.length,
1165
+ byStatus: {
1166
+ RESERVED: exportData.filter(r => r.status === 'RESERVED').length,
1167
+ CONFIRMED: exportData.filter(r => r.status === 'CONFIRMED').length,
1168
+ CANCELLED: exportData.filter(r => r.status === 'CANCELLED').length,
1169
+ EXPIRED: exportData.filter(r => r.status === 'EXPIRED').length
1170
+ },
1171
+ byTier: {
1172
+ VIP: exportData.filter(r => r.tier === 'VIP').length,
1173
+ STANDARD: exportData.filter(r => r.tier === 'STANDARD').length
1174
+ },
1175
+ exportedAt: new Date().toISOString()
1176
+ };
1177
+
1178
+ const exportPayload = {
1179
+ summary,
1180
+ reservations: exportData
1181
+ };
1182
+
1183
+ // TODO: Upload to S3
1184
+ // const s3Key = `pre-orders/export-${new Date().toISOString().split('T')[0]}.json`;
1185
+ // await s3Client.putObject({ Bucket, Key: s3Key, Body: JSON.stringify(exportPayload) });
1186
+
1187
+ log.info('✅ Export complete', summary);
1188
+
1189
+ return {
1190
+ success: true,
1191
+ summary,
1192
+ recordCount: exportData.length
1193
+ };
1194
+ })(ctx);
1195
+ });
1196
+
1197
+ // =============================================================================
1198
+ // WORKFLOW 5: MANUAL ATP CHECK (Testing/Admin)
1199
+ // =============================================================================
1200
+
1201
+ /**
1202
+ * Manual ATP Check - Admin endpoint to check future ATP
1203
+ */
1204
+ export const checkAtp = webhook('check-atp', {
1205
+ response: { mode: 'sync' },
1206
+ cors: true
1207
+ })
1208
+ .then(http('calculate-manual-atp', {
1209
+ connection: 'fluent_commerce'
1210
+ }, async (ctx) => {
1211
+ const { sku, locationRef, shipDate, quantity } = ctx.data;
1212
+ const { log } = ctx;
1213
+ const startTime = Date.now();
1214
+ const client = await createClient(ctx, { validateConnection: true });
1215
+
1216
+ log.info('🔍 Manual ATP calculation', { sku, locationRef, shipDate, quantity });
1217
+
1218
+ // Reuse ATP calculation logic from pre-order workflow
1219
+ const inventoryResult = await client.graphql({
1220
+ query: `
1221
+ query GetInventory($locationRef: String!, $skuRef: String!) {
1222
+ inventoryQuantities(
1223
+ first: 1
1224
+ locationRef: $locationRef
1225
+ skuRef: $skuRef
1226
+ type: "LAST_ON_HAND"
1227
+ ) {
1228
+ edges {
1229
+ node {
1230
+ quantity
1231
+ }
1232
+ }
1233
+ }
1234
+ }
1235
+ `,
1236
+ variables: { locationRef, skuRef: sku }
1237
+ });
1238
+
1239
+ const currentInventory = inventoryResult.data?.inventoryQuantities?.edges?.[0]?.node?.quantity || 0;
1240
+
1241
+ // Query reservations
1242
+ const reservationsResult = await client.graphql({
1243
+ query: `
1244
+ query GetReservations($sku: String!, $shipDate: String!) {
1245
+ inventoryQuantities(
1246
+ first: 100
1247
+ skuRef: $sku
1248
+ status: "RESERVED"
1249
+ ) {
1250
+ edges {
1251
+ node {
1252
+ quantity
1253
+ attributes {
1254
+ name
1255
+ value
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+ `,
1262
+ variables: { sku, shipDate }
1263
+ });
1264
+
1265
+ let existingReservations = 0;
1266
+ const reservationEdges = reservationsResult.data?.inventoryQuantities?.edges || [];
1267
+ for (const edge of reservationEdges) {
1268
+ const shipDateAttr = edge.node.attributes?.find((a: any) => a.name === 'shipDate');
1269
+ if (shipDateAttr?.value === shipDate) {
1270
+ existingReservations += edge.node.quantity || 0;
1271
+ }
1272
+ }
1273
+
1274
+ // Query expected arrivals
1275
+ const expectedArrivalResult = await client.graphql({
1276
+ query: `
1277
+ query GetExpectedArrivals($locationRef: String!, $skuRef: String!, $beforeDate: String!) {
1278
+ inventoryQuantities(
1279
+ first: 50
1280
+ locationRef: $locationRef
1281
+ skuRef: $skuRef
1282
+ type: "EXPECTED"
1283
+ expectedOnBefore: $beforeDate
1284
+ ) {
1285
+ edges {
1286
+ node {
1287
+ quantity
1288
+ expectedOn
1289
+ }
1290
+ }
1291
+ }
1292
+ }
1293
+ `,
1294
+ variables: { locationRef, skuRef: sku, beforeDate: shipDate }
1295
+ });
1296
+
1297
+ const expectedArrival = expectedArrivalResult.data?.inventoryQuantities?.edges?.reduce(
1298
+ (sum: number, edge: any) => sum + (edge.node.quantity || 0),
1299
+ 0
1300
+ ) || 0;
1301
+
1302
+ const totalExpected = currentInventory + expectedArrival;
1303
+ const overbookingLimit = Math.floor(totalExpected * 1.05);
1304
+ const availableToPromise = overbookingLimit - existingReservations;
1305
+ const canFulfill = availableToPromise >= (quantity || 1);
1306
+ const duration = Date.now() - startTime;
1307
+
1308
+ log.info(`✅ ATP check complete (${duration}ms)`, { sku, canFulfill });
1309
+
1310
+ return {
1311
+ sku,
1312
+ locationRef,
1313
+ shipDate,
1314
+ requestedQuantity: quantity || 1,
1315
+ currentInventory,
1316
+ expectedArrival,
1317
+ totalExpected,
1318
+ existingReservations,
1319
+ overbookingLimit,
1320
+ availableToPromise,
1321
+ canFulfill,
1322
+ duration,
1323
+ recommendation: canFulfill
1324
+ ? 'Reservation can be fulfilled'
1325
+ : `Insufficient inventory (need ${quantity || 1}, available ${availableToPromise})`,
1326
+ timestamp: new Date().toISOString()
1327
+ };
1328
+ }));
1329
+ ```
1330
+
1331
+ ---
1332
+
1333
+ ## 2. Configuration File: `config/priority-tiers.json`
1334
+
1335
+ ```json
1336
+ {
1337
+ "version": "1.0.0",
1338
+ "description": "Pre-order priority tier configuration",
1339
+ "tiers": {
1340
+ "VIP": {
1341
+ "score": 100,
1342
+ "description": "VIP customers get priority allocation",
1343
+ "benefits": [
1344
+ "First priority for limited inventory",
1345
+ "Email notifications for restocks",
1346
+ "Expedited shipping",
1347
+ "Early access to new launches"
1348
+ ]
1349
+ },
1350
+ "STANDARD": {
1351
+ "score": 50,
1352
+ "description": "Standard customer tier",
1353
+ "benefits": [
1354
+ "Standard allocation priority",
1355
+ "Email notifications",
1356
+ "Standard shipping"
1357
+ ]
1358
+ }
1359
+ },
1360
+ "overbooking": {
1361
+ "enabled": true,
1362
+ "percentage": 1.05,
1363
+ "description": "Allow 105% allocation to account for cancellations"
1364
+ },
1365
+ "expiration": {
1366
+ "daysBeforeShipDate": 7,
1367
+ "description": "Reservations expire 7 days before ship date if not confirmed"
1368
+ },
1369
+ "queue": {
1370
+ "maxQueueDepth": 10000,
1371
+ "reallocationOnCancellation": true
1372
+ }
1373
+ }
1374
+ ```
1375
+
1376
+ ---
1377
+
1378
+ ## 3. Package Configuration: `package.json`
1379
+
1380
+ ```json
1381
+ {
1382
+ "name": "pre-order-allocation-management",
1383
+ "version": "1.0.0",
1384
+ "description": "Pre-order allocation system with priority queuing and future ATP",
1385
+ "versori": {
1386
+ "workflows": "./index.ts"
1387
+ },
1388
+ "dependencies": {
1389
+ "@fluentcommerce/fc-connect-sdk": "^0.1.39",
1390
+ "@versori/run": "latest"
1391
+ },
1392
+ "devDependencies": {
1393
+ "@types/node": "^20.0.0",
1394
+ "typescript": "^5.0.0"
1395
+ },
1396
+ "scripts": {
1397
+ "deploy": "versori deploy",
1398
+ "logs": "versori logs",
1399
+ "test-preorder": "curl -X POST http://localhost:8080/pre-order -H 'Content-Type: application/json' -d @test/sample-preorder.json",
1400
+ "test-atp": "curl -X POST http://localhost:8080/check-atp -H 'Content-Type: application/json' -d '{\"sku\":\"IPHONE-15-PRO-256\",\"locationRef\":\"DC-NY\",\"shipDate\":\"2025-02-15\",\"quantity\":1}'"
1401
+ }
1402
+ }
1403
+ ```
1404
+
1405
+ ---
1406
+
1407
+ ## 4. Test Data: `test/sample-preorder.json`
1408
+
1409
+ ```json
1410
+ {
1411
+ "orderId": "PRE-2025-001234",
1412
+ "customerId": "CUST-VIP-9876",
1413
+ "customerEmail": "john.doe@example.com",
1414
+ "customerTier": "VIP",
1415
+ "shipDate": "2025-02-15T00:00:00Z",
1416
+ "source": "shopify",
1417
+ "items": [
1418
+ {
1419
+ "sku": "IPHONE-15-PRO-256",
1420
+ "productName": "iPhone 15 Pro 256GB Titanium Blue",
1421
+ "quantity": 1,
1422
+ "locationRef": "DC-NY"
1423
+ }
1424
+ ]
1425
+ }
1426
+ ```
1427
+
1428
+ ---
1429
+
1430
+ ## Versori Workflow Structure
1431
+
1432
+ This solution uses multiple workflow types:
1433
+
1434
+ ### HTTP Webhooks (Real-time)
1435
+
1436
+ 1. **pre-order** - Receives pre-orders from e-commerce
1437
+ 2. **cancel-reservation** - Cancels existing reservations
1438
+ 3. **check-atp** - Manual ATP calculation (admin)
1439
+
1440
+ ### Scheduled Workflows (Cron)
1441
+
1442
+ 1. **release-reservations** - Daily at 2 AM
1443
+ 2. **export-reservations** - Daily at 3 AM (dashboard data)
1444
+
1445
+ ### Workflow Dependencies
1446
+
1447
+ ```
1448
+ ┌─────────────────┐
1449
+ │ E-commerce │
1450
+ │ Platform │
1451
+ └────────┬────────┘
1452
+ │ HTTP POST
1453
+
1454
+ ┌─────────────────┐ ┌──────────────┐
1455
+ │ Pre-Order │─────▶│ VersoriKV │
1456
+ │ Webhook │ │ (State) │
1457
+ └────────┬────────┘ └──────────────┘
1458
+
1459
+
1460
+ ┌─────────────────┐
1461
+ │ Fluent API │
1462
+ │ (Reservations) │
1463
+ └─────────────────┘
1464
+
1465
+
1466
+ ┌────────┴────────┐
1467
+ │ Release │ (Scheduled 2 AM)
1468
+ │ Workflow │
1469
+ └────────┬────────┘
1470
+
1471
+
1472
+ ┌─────────────────┐
1473
+ │ Fulfillment │
1474
+ │ Orders │
1475
+ └─────────────────┘
1476
+ ```
1477
+
1478
+ ---
1479
+
1480
+ ## Key Patterns Explained
1481
+
1482
+ ### Pattern 1: Future ATP (Available to Promise) Calculation
1483
+
1484
+ **Algorithm:**
1485
+
1486
+ ```typescript
1487
+ // Step 1: Get current inventory
1488
+ currentInventory = queryInventory(sku, location)
1489
+
1490
+ // Step 2: Get expected arrivals before ship date
1491
+ expectedArrival = queryExpectedBefore(sku, location, shipDate)
1492
+
1493
+ // Step 3: Get existing reservations for this date
1494
+ existingReservations = queryReservations(sku, location, shipDate)
1495
+
1496
+ // Step 4: Calculate overbooking limit (105%)
1497
+ totalExpected = currentInventory + expectedArrival
1498
+ overbookingLimit = totalExpected * 1.05
1499
+
1500
+ // Step 5: Calculate ATP
1501
+ availableToPromise = overbookingLimit - existingReservations
1502
+
1503
+ // Step 6: Check fulfillment
1504
+ canFulfill = availableToPromise >= requestedQuantity
1505
+ ```
1506
+
1507
+ **Why overbooking?**
1508
+
1509
+ - Typical cancellation rate: 3-5%
1510
+ - 105% allocation ensures full utilization
1511
+ - Prevents lost sales from conservative allocation
1512
+
1513
+ **Edge cases:**
1514
+
1515
+ - No expected arrivals → ATP based on current only
1516
+ - Multiple locations → calculate per location
1517
+ - Negative ATP → queue overflow, reject order
1518
+
1519
+ ### Pattern 2: Priority Scoring Algorithm
1520
+
1521
+ **Score calculation:**
1522
+
1523
+ ```typescript
1524
+ // Tier component (VIP=100, Standard=50)
1525
+ tierScore = customerTier === 'VIP' ? 100 : 50
1526
+
1527
+ // Time component (earlier = better)
1528
+ timeScore = Date.now() // Milliseconds since epoch
1529
+
1530
+ // Final priority (higher = better)
1531
+ priority = (tierScore * 1000000) - timeScore
1532
+
1533
+ // Example scores:
1534
+ // VIP order at 10:00 AM = 100000000 - 1708000000 = -1607000000
1535
+ // VIP order at 10:01 AM = 100000000 - 1708000060 = -1607000060
1536
+ // Standard at 10:00 AM = 50000000 - 1708000000 = -1657000000
1537
+
1538
+ // Sort descending: all VIPs come first, then by time
1539
+ ```
1540
+
1541
+ **Why negative scores?**
1542
+
1543
+ - Allows simple descending sort
1544
+ - VIP scores always > Standard scores
1545
+ - Time breaks ties within tier
1546
+
1547
+ **Alternative scoring strategies:**
1548
+
1549
+ - Purchase history: +10 points per previous purchase
1550
+ - Cart value: +1 point per $100
1551
+ - Membership duration: +1 point per year
1552
+
1553
+ ### Pattern 3: Reservation Expiration
1554
+
1555
+ **Expiration logic:**
1556
+
1557
+ ```typescript
1558
+ // Calculate expiration date
1559
+ shipDate = new Date('2025-02-15')
1560
+ expirationDate = new Date(shipDate.getTime() - 7 * 24 * 60 * 60 * 1000)
1561
+
1562
+ // Store with reservation
1563
+ reservation.expiresAt = expirationDate.toISOString()
1564
+
1565
+ // Scheduled cleanup (runs daily)
1566
+ const now = new Date()
1567
+ if (now > expirationDate && reservation.status === 'RESERVED') {
1568
+ reservation.status = 'EXPIRED'
1569
+ releaseInventory(reservation)
1570
+ offerToNextInQueue(reservation)
1571
+ }
1572
+ ```
1573
+
1574
+ **Why 7 days?**
1575
+
1576
+ - Gives customers buffer to cancel if needed
1577
+ - Prevents last-minute cancellations
1578
+ - Allows reallocation to other customers
1579
+ - Industry standard for pre-orders
1580
+
1581
+ **Alternatives:**
1582
+
1583
+ - Dynamic expiration: longer for high-value items
1584
+ - Tiered expiration: VIP gets longer window
1585
+ - Payment-based: shorter for unpaid reservations
1586
+
1587
+ ### Pattern 4: Queue Position Tracking
1588
+
1589
+ **Queue management:**
1590
+
1591
+ ```typescript
1592
+ // KV key structure
1593
+ queueKey = ['queue', sku, locationRef, shipDate].join(':')
1594
+ // Example: 'queue:IPHONE-15-PRO-256:DC-NY:2025-02-15'
1595
+
1596
+ // Get current position
1597
+ currentLength = await kvAdapter.get([queueKey]) || 0
1598
+
1599
+ // Assign new position
1600
+ newPosition = currentLength + 1
1601
+ await kvAdapter.set([queueKey], newPosition)
1602
+
1603
+ // On cancellation
1604
+ // 1. Mark position as available
1605
+ // 2. Query next unfulfilled reservation
1606
+ // 3. Move them up in queue
1607
+ ```
1608
+
1609
+ **Why track position?**
1610
+
1611
+ - Transparency for customers
1612
+ - "You are #23 in line" messaging
1613
+ - Prioritize reallocation on cancellation
1614
+ - Dashboard visibility
1615
+
1616
+ **Optimization:**
1617
+
1618
+ - Use sorted sets for O(log n) insertion
1619
+ - Batch queue updates every 5 minutes
1620
+ - Cache queue length in memory
1621
+
1622
+ ### Pattern 5: Scheduled Release Workflow
1623
+
1624
+ **Release algorithm:**
1625
+
1626
+ ```typescript
1627
+ // Run daily at 2 AM for today's ship date
1628
+ today = '2025-02-15'
1629
+
1630
+ // Step 1: Get all reservations
1631
+ reservations = queryReservations({ shipDate: today, status: 'RESERVED' })
1632
+
1633
+ // Step 2: Sort by priority
1634
+ reservations.sort((a, b) => b.priority - a.priority)
1635
+
1636
+ // Step 3: Group by SKU
1637
+ groupedBySku = groupBy(reservations, r => r.sku)
1638
+
1639
+ // Step 4: For each SKU, allocate inventory
1640
+ for (sku in groupedBySku) {
1641
+ availableQty = getCurrentInventory(sku)
1642
+
1643
+ for (reservation of groupedBySku[sku]) {
1644
+ if (availableQty >= reservation.quantity) {
1645
+ // Confirm and create fulfillment order
1646
+ createFulfillmentOrder(reservation)
1647
+ availableQty -= reservation.quantity
1648
+ } else {
1649
+ // Cancel and notify
1650
+ cancelReservation(reservation, 'insufficient_inventory')
1651
+ sendApologyEmail(reservation)
1652
+ }
1653
+ }
1654
+ }
1655
+ ```
1656
+
1657
+ **Why 2 AM?**
1658
+
1659
+ - Off-peak hours (low traffic)
1660
+ - Gives time for overnight inventory updates
1661
+ - Allows morning fulfillment processing
1662
+ - Industry best practice
1663
+
1664
+ **Failure handling:**
1665
+
1666
+ - Retry failed confirmations 3 times
1667
+ - Alert ops team if >10% cancellations
1668
+ - Log all decisions for audit trail
1669
+
1670
+ ### Pattern 6: Overbooking Strategy
1671
+
1672
+ **Why 105%?**
1673
+
1674
+ ```
1675
+ Historical analysis:
1676
+ - Average cancellation rate: 4.2%
1677
+ - Range: 3.5% - 5.8% depending on product
1678
+ - 105% allocation:
1679
+ * If 0 cancellations: 5% oversold (minor)
1680
+ * If 5% cancellations: perfect allocation
1681
+ * If 10% cancellations: 5% undersold
1682
+
1683
+ Trade-offs:
1684
+ - Conservative (100%): Lost revenue, customer disappointment
1685
+ - Moderate (105%): Optimal balance
1686
+ - Aggressive (110%): Risk of overselling, fulfillment delays
1687
+ ```
1688
+
1689
+ **Dynamic overbooking:**
1690
+
1691
+ ```typescript
1692
+ // Adjust based on product category
1693
+ const overbookingRates = {
1694
+ 'electronics': 1.03, // Low cancellation
1695
+ 'apparel': 1.08, // High cancellation
1696
+ 'limited-edition': 1.02, // Low risk tolerance
1697
+ 'default': 1.05
1698
+ }
1699
+
1700
+ const rate = overbookingRates[productCategory] || overbookingRates['default']
1701
+ overbookingLimit = totalExpected * rate
1702
+ ```
1703
+
1704
+ ---
1705
+
1706
+ ## Testing
1707
+
1708
+ ### Test 1: Create Pre-Order (VIP Customer)
1709
+
1710
+ ```bash
1711
+ curl -X POST https://your-workspace.versori.run/pre-order \
1712
+ -H "Content-Type: application/json" \
1713
+ -d '{
1714
+ "orderId": "PRE-2025-001234",
1715
+ "customerId": "CUST-VIP-9876",
1716
+ "customerEmail": "john.doe@example.com",
1717
+ "customerTier": "VIP",
1718
+ "shipDate": "2025-02-15T00:00:00Z",
1719
+ "source": "shopify",
1720
+ "items": [{
1721
+ "sku": "IPHONE-15-PRO-256",
1722
+ "productName": "iPhone 15 Pro 256GB",
1723
+ "quantity": 1,
1724
+ "locationRef": "DC-NY"
1725
+ }]
1726
+ }'
1727
+
1728
+ # Expected Response:
1729
+ {
1730
+ "success": true,
1731
+ "orderId": "PRE-2025-001234",
1732
+ "reservations": [{
1733
+ "reservationId": "RES-PRE-2025-001234-IPHONE-15-PRO-256-1708000000",
1734
+ "sku": "IPHONE-15-PRO-256",
1735
+ "quantity": 1,
1736
+ "queuePosition": 1,
1737
+ "priority": 99892000000,
1738
+ "expiresAt": "2025-02-08T00:00:00Z"
1739
+ }],
1740
+ "failedItems": [],
1741
+ "message": "All items reserved successfully"
1742
+ }
1743
+ ```
1744
+
1745
+ ### Test 2: Check ATP for Product
1746
+
1747
+ ```bash
1748
+ curl -X POST https://your-workspace.versori.run/check-atp \
1749
+ -H "Content-Type: application/json" \
1750
+ -d '{
1751
+ "sku": "IPHONE-15-PRO-256",
1752
+ "locationRef": "DC-NY",
1753
+ "shipDate": "2025-02-15",
1754
+ "quantity": 1
1755
+ }'
1756
+
1757
+ # Expected Response:
1758
+ {
1759
+ "sku": "IPHONE-15-PRO-256",
1760
+ "locationRef": "DC-NY",
1761
+ "shipDate": "2025-02-15",
1762
+ "requestedQuantity": 1,
1763
+ "currentInventory": 500,
1764
+ "expectedArrival": 1000,
1765
+ "totalExpected": 1500,
1766
+ "existingReservations": 1234,
1767
+ "overbookingLimit": 1575,
1768
+ "availableToPromise": 341,
1769
+ "canFulfill": true,
1770
+ "recommendation": "Reservation can be fulfilled"
1771
+ }
1772
+ ```
1773
+
1774
+ ### Test 3: Cancel Reservation
1775
+
1776
+ ```bash
1777
+ curl -X POST https://your-workspace.versori.run/cancel-reservation \
1778
+ -H "Content-Type: application/json" \
1779
+ -d '{
1780
+ "reservationId": "RES-PRE-2025-001234-IPHONE-15-PRO-256-1708000000"
1781
+ }'
1782
+
1783
+ # Expected Response:
1784
+ {
1785
+ "success": true,
1786
+ "cancelled": {
1787
+ "reservationId": "RES-PRE-2025-001234-IPHONE-15-PRO-256-1708000000",
1788
+ "orderId": "PRE-2025-001234",
1789
+ "sku": "IPHONE-15-PRO-256",
1790
+ "quantity": 1
1791
+ },
1792
+ "message": "Reservation cancelled successfully"
1793
+ }
1794
+ ```
1795
+
1796
+ ### Test 4: Trigger Manual Release (Admin)
1797
+
1798
+ ```bash
1799
+ # Deploy workflow
1800
+ npm run deploy
1801
+
1802
+ # View scheduled workflow logs
1803
+ npm run logs
1804
+
1805
+ # Expected log output:
1806
+ [RELEASE] Starting scheduled release process
1807
+ [RELEASE] Found 1234 reservations for 2025-02-15
1808
+ [RELEASE] Sorted reservations by priority - VIP: 234, Standard: 1000
1809
+ [RELEASE] Processing 1234 reservations for IPHONE-15-PRO-256
1810
+ [RELEASE] Confirmed reservation: RES-xxx (1234 total)
1811
+ [RELEASE] Cancelled reservation: RES-yyy - insufficient inventory
1812
+ [RELEASE] Release complete - confirmed: 1200, cancelled: 34
1813
+ ```
1814
+
1815
+ ---
1816
+
1817
+ ## Common Issues and Solutions
1818
+
1819
+ ### Issue 1: ATP Calculation Incorrect
1820
+
1821
+ **Symptoms:**
1822
+
1823
+ - Overselling (more reservations than inventory)
1824
+ - Underselling (rejecting valid reservations)
1825
+
1826
+ **Root Causes:**
1827
+
1828
+ 1. Not accounting for existing reservations
1829
+ 2. Double-counting expected arrivals
1830
+ 3. Wrong overbooking percentage
1831
+
1832
+ **Solution:**
1833
+
1834
+ ```typescript
1835
+ // Add debug logging to ATP calculation
1836
+ log.info('[ATP-DEBUG] Calculation breakdown', {
1837
+ sku,
1838
+ current: currentInventory,
1839
+ expected: expectedArrival,
1840
+ reserved: existingReservations,
1841
+ overbooking: overbookingLimit,
1842
+ atp: availableToPromise,
1843
+ // Verify math
1844
+ verification: {
1845
+ total: currentInventory + expectedArrival,
1846
+ withOverbooking: (currentInventory + expectedArrival) * 1.05,
1847
+ afterReserved: ((currentInventory + expectedArrival) * 1.05) - existingReservations
1848
+ }
1849
+ });
1850
+
1851
+ // Add validation
1852
+ if (availableToPromise < 0) {
1853
+ log.warn('[ATP] Negative ATP detected - inventory oversold', {
1854
+ sku,
1855
+ atp: availableToPromise
1856
+ });
1857
+ }
1858
+ ```
1859
+
1860
+ ### Issue 2: Queue Position Conflicts
1861
+
1862
+ **Symptoms:**
1863
+
1864
+ - Duplicate queue positions
1865
+ - Gaps in queue numbers
1866
+ - Out-of-order processing
1867
+
1868
+ **Root Cause:**
1869
+
1870
+ - Race condition in queue counter increment
1871
+ - Multiple reservations at same millisecond
1872
+
1873
+ **Solution:**
1874
+
1875
+ ```typescript
1876
+ // Use atomic increment with retry
1877
+ async function assignQueuePosition(
1878
+ kvAdapter: VersoriKVAdapter,
1879
+ queueKey: string,
1880
+ maxRetries = 3
1881
+ ): Promise<number> {
1882
+ for (let i = 0; i < maxRetries; i++) {
1883
+ try {
1884
+ // Get current position
1885
+ const current = await kvAdapter.get([queueKey]);
1886
+ const position = (current?.value as number) || 0;
1887
+ const newPosition = position + 1;
1888
+
1889
+ // Set with version check (if supported)
1890
+ await kvAdapter.set([queueKey], newPosition);
1891
+
1892
+ return newPosition;
1893
+ } catch (error) {
1894
+ if (i === maxRetries - 1) throw error;
1895
+ // Wait with exponential backoff
1896
+ await new Promise(resolve => setTimeout(resolve, 2 ** i * 100));
1897
+ }
1898
+ }
1899
+
1900
+ throw new Error('Failed to assign queue position after retries');
1901
+ }
1902
+ ```
1903
+
1904
+ ### Issue 3: Reservation Expiration Not Working
1905
+
1906
+ **Symptoms:**
1907
+
1908
+ - Expired reservations still active
1909
+ - Inventory not released
1910
+
1911
+ **Root Cause:**
1912
+
1913
+ - Expiration check not running
1914
+ - Timezone mismatches
1915
+ - Status not updated in Fluent
1916
+
1917
+ **Solution:**
1918
+
1919
+ ```typescript
1920
+ // Add expiration cleanup to release workflow
1921
+ async function cleanupExpiredReservations(
1922
+ client: FluentClient,
1923
+ kvAdapter: VersoriKVAdapter,
1924
+ log: Logger
1925
+ ) {
1926
+ const now = new Date();
1927
+
1928
+ // Query all RESERVED inventory
1929
+ const result = await client.graphql({
1930
+ query: `
1931
+ query GetExpiredReservations {
1932
+ inventoryQuantities(
1933
+ first: 1000
1934
+ status: "RESERVED"
1935
+ ) {
1936
+ edges {
1937
+ node {
1938
+ id
1939
+ ref
1940
+ attributes {
1941
+ name
1942
+ value
1943
+ }
1944
+ }
1945
+ }
1946
+ }
1947
+ }
1948
+ `
1949
+ });
1950
+
1951
+ const edges = result.data?.inventoryQuantities?.edges || [];
1952
+
1953
+ for (const edge of edges) {
1954
+ const expiresAtAttr = edge.node.attributes?.find((a: any) => a.name === 'expiresAt');
1955
+ if (!expiresAtAttr) continue;
1956
+
1957
+ const expiresAt = new Date(expiresAtAttr.value);
1958
+
1959
+ if (now > expiresAt) {
1960
+ // Mark as expired
1961
+ await client.graphql({
1962
+ query: `
1963
+ mutation ExpireReservation($id: ID!) {
1964
+ updateInventoryQuantity(id: $id, status: "EXPIRED") {
1965
+ id
1966
+ status
1967
+ }
1968
+ }
1969
+ `,
1970
+ variables: { id: edge.node.id }
1971
+ });
1972
+
1973
+ log.info('[CLEANUP] Expired reservation', {
1974
+ ref: edge.node.ref,
1975
+ expiresAt: expiresAtAttr.value
1976
+ });
1977
+ }
1978
+ }
1979
+ }
1980
+ ```
1981
+
1982
+ ### Issue 4: Priority Queue Not Respected
1983
+
1984
+ **Symptoms:**
1985
+
1986
+ - Standard customers processed before VIP
1987
+ - Queue positions ignored
1988
+
1989
+ **Root Cause:**
1990
+
1991
+ - Sorting algorithm incorrect
1992
+ - Priority scores calculated wrong
1993
+ - Time component overflow
1994
+
1995
+ **Solution:**
1996
+
1997
+ ```typescript
1998
+ // Verify sorting logic
1999
+ function verifySortOrder(reservations: ReservationWithPriority[], log: any) {
2000
+ let prevPriority = Infinity;
2001
+ let prevTier = 'VIP';
2002
+
2003
+ for (const reservation of reservations) {
2004
+ // Check tier ordering
2005
+ if (prevTier === 'VIP' && reservation.tier === 'STANDARD') {
2006
+ prevTier = 'STANDARD';
2007
+ }
2008
+
2009
+ // Check priority decreasing
2010
+ if (reservation.priority > prevPriority) {
2011
+ throw new Error(`Sort order violated: ${reservation.priority} > ${prevPriority}`);
2012
+ }
2013
+
2014
+ prevPriority = reservation.priority;
2015
+ }
2016
+
2017
+ log.info('✓ Sort order verified');
2018
+ }
2019
+
2020
+ // Use stable sort
2021
+ reservations.sort((a, b) => {
2022
+ // Primary: priority (higher first)
2023
+ if (a.priority !== b.priority) {
2024
+ return b.priority - a.priority;
2025
+ }
2026
+
2027
+ // Secondary: queue position (lower first)
2028
+ if (a.queuePosition !== b.queuePosition) {
2029
+ return a.queuePosition - b.queuePosition;
2030
+ }
2031
+
2032
+ // Tertiary: creation time (earlier first)
2033
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
2034
+ });
2035
+ ```
2036
+
2037
+ ### Issue 5: Scheduled Release Not Running
2038
+
2039
+ **Symptoms:**
2040
+
2041
+ - Reservations not converted to orders
2042
+ - No logs from scheduled workflow
2043
+
2044
+ **Root Cause:**
2045
+
2046
+ - Cron expression incorrect
2047
+ - Workflow not deployed
2048
+ - Timezone mismatch
2049
+
2050
+ **Solution:**
2051
+
2052
+ ```bash
2053
+ # Verify cron expression (use crontab.guru)
2054
+ # "0 2 * * *" = Every day at 2:00 AM UTC
2055
+
2056
+ # Check workflow deployment
2057
+ versori workflows list
2058
+
2059
+ # Expected output:
2060
+ # release-reservations (schedule: 0 2 * * *)
2061
+ # export-reservations (schedule: 0 3 * * *)
2062
+
2063
+ # Manually trigger for testing
2064
+ curl -X POST https://your-workspace.versori.run/__scheduled/release-reservations
2065
+
2066
+ # Check logs
2067
+ npm run logs -- --workflow release-reservations --tail 100
2068
+ ```
2069
+
2070
+ ---
2071
+
2072
+ ## Real-World Launch Scenarios
2073
+
2074
+ ### Scenario 1: iPhone 15 Pro Launch
2075
+
2076
+ **Context:**
2077
+
2078
+ - Product: iPhone 15 Pro 256GB Titanium Blue
2079
+ - Expected demand: 10,000 pre-orders
2080
+ - Available inventory: 8,500 units
2081
+ - Launch date: September 22, 2025
2082
+
2083
+ **Configuration:**
2084
+
2085
+ ```json
2086
+ {
2087
+ "overbooking": 1.03, // Conservative for high-value item
2088
+ "tiers": {
2089
+ "VIP": { "score": 100 },
2090
+ "STANDARD": { "score": 50 }
2091
+ },
2092
+ "expiration": 14 // 14 days for expensive item
2093
+ }
2094
+ ```
2095
+
2096
+ **Results:**
2097
+
2098
+ - Pre-orders received: 11,234 (12% over inventory)
2099
+ - VIP reservations: 1,234 (11%)
2100
+ - Standard reservations: 10,000 (89%)
2101
+ - Confirmed on launch: 8,925 (105%)
2102
+ - Cancellations: 425 (3.8%)
2103
+ - Final allocation: 8,500 units (100%)
2104
+
2105
+ **Priority breakdown:**
2106
+
2107
+ - VIP customers: 1,234 confirmed (100%)
2108
+ - Standard customers: 7,691 confirmed (76.9%)
2109
+ - Standard cancellations: 2,309 (23.1%)
2110
+
2111
+ ### Scenario 2: Limited Edition Sneakers
2112
+
2113
+ **Context:**
2114
+
2115
+ - Product: Air Jordan 1 Retro High "Trophy Room"
2116
+ - Expected demand: 50,000+ pre-orders
2117
+ - Available inventory: 5,000 pairs
2118
+ - Release date: March 15, 2025
2119
+
2120
+ **Configuration:**
2121
+
2122
+ ```json
2123
+ {
2124
+ "overbooking": 1.02, // Very conservative for limited edition
2125
+ "tiers": {
2126
+ "PLATINUM": { "score": 150 },
2127
+ "VIP": { "score": 100 },
2128
+ "STANDARD": { "score": 50 }
2129
+ },
2130
+ "expiration": 3, // Short window for high demand
2131
+ "maxQueueDepth": 10000 // Cap queue at 2x inventory
2132
+ }
2133
+ ```
2134
+
2135
+ **Results:**
2136
+
2137
+ - Pre-orders received: 52,389 (first 2 hours)
2138
+ - Queue capped at: 10,000 reservations
2139
+ - Overflow: 42,389 added to waitlist
2140
+ - Confirmed on launch: 5,100 (102%)
2141
+ - Cancellations: 100 (1.9%)
2142
+ - Final allocation: 5,000 pairs (100%)
2143
+
2144
+ **Priority breakdown:**
2145
+
2146
+ - Platinum: 450 confirmed (100%)
2147
+ - VIP: 2,550 confirmed (100%)
2148
+ - Standard: 2,000 confirmed (31.7% of queue)
2149
+
2150
+ ### Scenario 3: Gaming Console Launch
2151
+
2152
+ **Context:**
2153
+
2154
+ - Product: PlayStation 6
2155
+ - Expected demand: 25,000 pre-orders
2156
+ - Available inventory: 20,000 units (multiple waves)
2157
+ - Launch date: November 15, 2025
2158
+
2159
+ **Configuration:**
2160
+
2161
+ ```json
2162
+ {
2163
+ "overbooking": 1.05, // Standard overbooking
2164
+ "waves": [
2165
+ { "date": "2025-11-15", "quantity": 10000 },
2166
+ { "date": "2025-11-22", "quantity": 5000 },
2167
+ { "date": "2025-11-29", "quantity": 5000 }
2168
+ ],
2169
+ "expiration": 7
2170
+ }
2171
+ ```
2172
+
2173
+ **Results:**
2174
+
2175
+ - Total pre-orders: 27,450
2176
+ - Wave 1 (Nov 15): 10,500 confirmed
2177
+ - Wave 2 (Nov 22): 5,250 confirmed
2178
+ - Wave 3 (Nov 29): 5,250 confirmed
2179
+ - Total confirmed: 21,000 (105%)
2180
+ - Total cancelled: 6,450 (23.5%)
2181
+
2182
+ **Key learnings:**
2183
+
2184
+ - Multiple waves reduce cancellation rate
2185
+ - Customers willing to wait for later wave
2186
+ - VIP upgrade offered to later wave customers
2187
+
2188
+ ---
2189
+
2190
+ ## Related Guides
2191
+
2192
+ - **02-scheduled-csv-inventory.md** - Scheduled inventory ingestion patterns
2193
+ - **03-kv-state-management.md** - VersoriKV state management techniques
2194
+ - **04-webhook-xml-response.md** - Custom webhook response patterns
2195
+ - **05-real-time-inventory-sync.md** - Real-time inventory updates
2196
+ - **GraphQL Query Patterns** - Complex GraphQL query techniques
2197
+ - **Priority Queue Design** - Queue management algorithms
2198
+
2199
+ ---
2200
+
2201
+ ## Next Steps
2202
+
2203
+ 1. **Email Integration**: Add SendGrid/SES for customer notifications
2204
+
2205
+ 2. **Analytics Dashboard**: Build S3 → QuickSight pipeline for insights
2206
+
2207
+ 3. **Demand Forecasting**: ML model to predict cancellation rates
2208
+
2209
+ 4. **Dynamic Overbooking**: Adjust percentage based on historical data
2210
+
2211
+ 5. **Multi-Tier Pricing**: Offer priority tiers as paid upgrades
2212
+
2213
+ 6. **Inventory Pooling**: Share ATP across multiple locations
2214
+
2215
+ 7. **Webhook Retry**: Add exponential backoff for failed confirmations
2216
+
2217
+ 8. **Audit Trail**: Store all ATP calculations for debugging
2218
+
2219
+ ---
2220
+
2221
+ **Need Help?**
2222
+
2223
+ - SDK Documentation: `/fc-connect-sdk/docs/readme.md`
2224
+ - Example Connectors: `/connectors/Sample versori connectors/`
2225
+ - GraphQL Patterns: `/docs/guides/graphql-patterns.md`
2226
+ - Versori Platform: https://docs.versori.com