@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.56

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