@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55

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