@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +11 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,1185 +1,1185 @@
|
|
|
1
|
-
# Standalone: Multi-Channel Inventory Sync (Real-Time ATP to Marketplaces)
|
|
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**: Node.js script that aggregates Available-to-Promise (ATP) inventory from Fluent Commerce and pushes real-time updates to multiple marketplaces (Amazon, eBay, Walmart)
|
|
9
|
-
|
|
10
|
-
**Complexity**: High
|
|
11
|
-
|
|
12
|
-
**Runtime**: Node.js >=18
|
|
13
|
-
|
|
14
|
-
**Estimated Lines**: ~950 lines
|
|
15
|
-
|
|
16
|
-
**Volume**: Large (10k-100k SKUs, multiple marketplaces)
|
|
17
|
-
|
|
18
|
-
**Latency**: Near real-time (every 5-15 min)
|
|
19
|
-
|
|
20
|
-
**Pattern**: GraphQL pagination → external marketplace APIs
|
|
21
|
-
|
|
22
|
-
## What You'll Build
|
|
23
|
-
|
|
24
|
-
- OAuth2-authenticated Fluent Commerce client
|
|
25
|
-
- GraphQL query with auto-pagination for inventory positions
|
|
26
|
-
- ATP (Available-to-Promise) aggregation across multiple locations
|
|
27
|
-
- Marketplace-specific buffer stock calculations
|
|
28
|
-
- Parallel marketplace updates with rate limiting:
|
|
29
|
-
- Amazon MWS/SP-API integration
|
|
30
|
-
- eBay Trading API integration
|
|
31
|
-
- Walmart Seller API integration
|
|
32
|
-
- Per-marketplace error handling and retry logic
|
|
33
|
-
- SLA tracking (15-minute sync requirement)
|
|
34
|
-
- Comprehensive metrics and logging
|
|
35
|
-
- Dry-run mode for testing
|
|
36
|
-
- State management to prevent duplicate updates
|
|
37
|
-
|
|
38
|
-
## SDK Methods Used
|
|
39
|
-
|
|
40
|
-
- `createClient({ config: { baseUrl, clientId, clientSecret, retailerId } })` - OAuth2 client
|
|
41
|
-
- `client.graphql({ query, variables, pagination })` - Query with auto-pagination
|
|
42
|
-
- `UniversalMapper(mappingConfig)` - Transform Fluent data to marketplace formats
|
|
43
|
-
- `createConsoleLogger()` - Simple console logger
|
|
44
|
-
- `toStructuredLogger(logger, context)` - Add context to logs
|
|
45
|
-
- `generateCorrelationId()` - Generate unique correlation IDs
|
|
46
|
-
- State tracking for incremental updates
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Complete Working Implementation
|
|
51
|
-
|
|
52
|
-
### 1. Project Setup
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# Create project directory
|
|
56
|
-
mkdir multi-channel-inventory-sync
|
|
57
|
-
cd multi-channel-inventory-sync
|
|
58
|
-
|
|
59
|
-
# Initialize Node.js project
|
|
60
|
-
npm init -y
|
|
61
|
-
|
|
62
|
-
# Install dependencies
|
|
63
|
-
npm install @fluentcommerce/fc-connect-sdk dotenv axios p-limit p-retry
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 2. Environment Configuration
|
|
67
|
-
|
|
68
|
-
Create `.env` file:
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
# Fluent Commerce OAuth2
|
|
72
|
-
FLUENT_BASE_URL=https://api.fluentcommerce.com
|
|
73
|
-
FLUENT_CLIENT_ID=your-oauth2-client-id
|
|
74
|
-
FLUENT_CLIENT_SECRET=your-oauth2-client-secret
|
|
75
|
-
FLUENT_RETAILER_ID=your-retailer-id
|
|
76
|
-
|
|
77
|
-
# Sync Configuration
|
|
78
|
-
SYNC_INTERVAL_MINUTES=15
|
|
79
|
-
SLA_THRESHOLD_MINUTES=15
|
|
80
|
-
MAX_RECORDS_PER_SYNC=100000
|
|
81
|
-
DRY_RUN=false
|
|
82
|
-
|
|
83
|
-
# Amazon SP-API Credentials
|
|
84
|
-
AMAZON_ENABLED=true
|
|
85
|
-
AMAZON_MARKETPLACE_ID=ATVPDKIKX0DER
|
|
86
|
-
AMAZON_SELLER_ID=your-amazon-seller-id
|
|
87
|
-
AMAZON_REFRESH_TOKEN=your-amazon-refresh-token
|
|
88
|
-
AMAZON_CLIENT_ID=amzn1.application-oa2-client.xxxxx
|
|
89
|
-
AMAZON_CLIENT_SECRET=your-amazon-client-secret
|
|
90
|
-
AMAZON_AWS_ACCESS_KEY_ID=your-aws-access-key
|
|
91
|
-
AMAZON_AWS_SECRET_ACCESS_KEY=your-aws-secret-key
|
|
92
|
-
AMAZON_REGION=us-east-1
|
|
93
|
-
AMAZON_ROLE_ARN=arn:aws:iam::123456789:role/SellingPartnerAPI
|
|
94
|
-
AMAZON_BUFFER_STOCK=5
|
|
95
|
-
AMAZON_RATE_LIMIT=10
|
|
96
|
-
|
|
97
|
-
# eBay Trading API Credentials
|
|
98
|
-
EBAY_ENABLED=true
|
|
99
|
-
EBAY_APP_ID=your-ebay-app-id
|
|
100
|
-
EBAY_DEV_ID=your-ebay-dev-id
|
|
101
|
-
EBAY_CERT_ID=your-ebay-cert-id
|
|
102
|
-
EBAY_AUTH_TOKEN=your-ebay-auth-token
|
|
103
|
-
EBAY_SITE_ID=0
|
|
104
|
-
EBAY_SANDBOX=false
|
|
105
|
-
EBAY_BUFFER_STOCK=3
|
|
106
|
-
EBAY_RATE_LIMIT=5
|
|
107
|
-
|
|
108
|
-
# Walmart Seller API Credentials
|
|
109
|
-
WALMART_ENABLED=true
|
|
110
|
-
WALMART_CLIENT_ID=your-walmart-client-id
|
|
111
|
-
WALMART_CLIENT_SECRET=your-walmart-client-secret
|
|
112
|
-
WALMART_CHANNEL_TYPE=0a7d6c3e-e9a0-4a0c-8d6a-f6c6f1e7a5c7
|
|
113
|
-
WALMART_BUFFER_STOCK=10
|
|
114
|
-
WALMART_RATE_LIMIT=20
|
|
115
|
-
|
|
116
|
-
# Logging
|
|
117
|
-
LOG_LEVEL=info
|
|
118
|
-
METRICS_ENABLED=true
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### 3. Package Configuration
|
|
122
|
-
|
|
123
|
-
Create `package.json`:
|
|
124
|
-
|
|
125
|
-
```json
|
|
126
|
-
{
|
|
127
|
-
"name": "multi-channel-inventory-sync",
|
|
128
|
-
"version": "1.0.0",
|
|
129
|
-
"type": "module",
|
|
130
|
-
"description": "Real-time ATP inventory sync from Fluent Commerce to multiple marketplaces",
|
|
131
|
-
"main": "src/index.js",
|
|
132
|
-
"scripts": {
|
|
133
|
-
"start": "node src/index.js",
|
|
134
|
-
"sync:once": "node src/index.js --once",
|
|
135
|
-
"sync:continuous": "node src/index.js",
|
|
136
|
-
"sync:dry-run": "DRY_RUN=true node src/index.js --once",
|
|
137
|
-
"test": "node --test tests/**/*.test.js"
|
|
138
|
-
},
|
|
139
|
-
"dependencies": {
|
|
140
|
-
"@fluentcommerce/fc-connect-sdk": "^0.1.39",
|
|
141
|
-
"axios": "^1.6.0",
|
|
142
|
-
"dotenv": "^16.0.0",
|
|
143
|
-
"p-limit": "^5.0.0",
|
|
144
|
-
"p-retry": "^6.0.0"
|
|
145
|
-
},
|
|
146
|
-
"devDependencies": {
|
|
147
|
-
"@types/node": "^20.0.0"
|
|
148
|
-
},
|
|
149
|
-
"engines": {
|
|
150
|
-
"node": ">=18.0.0"
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### 4. TypeScript Configuration (tsconfig.json)
|
|
156
|
-
|
|
157
|
-
```json
|
|
158
|
-
{
|
|
159
|
-
"compilerOptions": {
|
|
160
|
-
"module": "ES2022",
|
|
161
|
-
"target": "ES2024",
|
|
162
|
-
"moduleResolution": "node"
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### 5. Main Script Implementation (src/multi-channel-sync.ts)
|
|
168
|
-
|
|
169
|
-
> **⚠️ RUNTIME COMPATIBILITY NOTE:**
|
|
170
|
-
> This template includes `import { Buffer } from 'node:buffer';` for Deno/Versori compatibility.
|
|
171
|
-
> While Buffer is globally available in Node.js and this import is optional for Node.js-only deployments,
|
|
172
|
-
> including it ensures the code works across all runtimes (Node.js, Deno, Versori) without modification.
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
import 'dotenv/config';
|
|
176
|
-
import { Buffer } from 'node:buffer'; // Required for Deno/Versori runtime compatibility
|
|
177
|
-
|
|
178
|
-
// FC Connect SDK+
|
|
179
|
-
// Install: npm install @fluentcommerce/fc-connect-sdk@latest
|
|
180
|
-
// Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
|
|
181
|
-
// GitHub: https://github.com/fluentcommerce/fc-connect-sdk
|
|
182
|
-
|
|
183
|
-
import {
|
|
184
|
-
createClient,
|
|
185
|
-
type FluentClient,
|
|
186
|
-
type StructuredLogger,
|
|
187
|
-
createConsoleLogger,
|
|
188
|
-
toStructuredLogger
|
|
189
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
190
|
-
|
|
191
|
-
import axios, { AxiosInstance } from 'axios';
|
|
192
|
-
import pLimit from 'p-limit';
|
|
193
|
-
|
|
194
|
-
// ============================================================================
|
|
195
|
-
// TYPES & INTERFACES
|
|
196
|
-
// ============================================================================
|
|
197
|
-
|
|
198
|
-
interface InventoryPosition {
|
|
199
|
-
productRef: string;
|
|
200
|
-
locationRef: string;
|
|
201
|
-
quantity: number;
|
|
202
|
-
onHand: number;
|
|
203
|
-
available: number;
|
|
204
|
-
reserved: number;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
interface ChannelConfig {
|
|
208
|
-
enabled: boolean;
|
|
209
|
-
apiClient: AxiosInstance;
|
|
210
|
-
bufferStock: number;
|
|
211
|
-
rateLimit: number;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
interface SyncMetrics {
|
|
215
|
-
startTime: Date;
|
|
216
|
-
endTime?: Date;
|
|
217
|
-
totalSKUs: number;
|
|
218
|
-
successfulUpdates: number;
|
|
219
|
-
failedUpdates: number;
|
|
220
|
-
skippedUpdates: number;
|
|
221
|
-
errors: Array<{ sku: string; channel: string; error: string }>;
|
|
222
|
-
rateLimitHits: number;
|
|
223
|
-
avgResponseTime: number;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ============================================================================
|
|
227
|
-
// CONFIGURATION
|
|
228
|
-
// ============================================================================
|
|
229
|
-
|
|
230
|
-
const config = {
|
|
231
|
-
fluent: {
|
|
232
|
-
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
233
|
-
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
234
|
-
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
235
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
236
|
-
},
|
|
237
|
-
amazon: {
|
|
238
|
-
enabled: process.env.AMAZON_ENABLED === 'true',
|
|
239
|
-
clientId: process.env.AMAZON_CLIENT_ID!,
|
|
240
|
-
clientSecret: process.env.AMAZON_CLIENT_SECRET!,
|
|
241
|
-
refreshToken: process.env.AMAZON_REFRESH_TOKEN!,
|
|
242
|
-
marketplaceId: process.env.AMAZON_MARKETPLACE_ID!,
|
|
243
|
-
sellerId: process.env.AMAZON_SELLER_ID!,
|
|
244
|
-
bufferStock: parseInt(process.env.AMAZON_BUFFER_STOCK || '5'),
|
|
245
|
-
rateLimit: parseInt(process.env.AMAZON_RATE_LIMIT || '10'),
|
|
246
|
-
},
|
|
247
|
-
ebay: {
|
|
248
|
-
enabled: process.env.EBAY_ENABLED === 'true',
|
|
249
|
-
appId: process.env.EBAY_APP_ID!,
|
|
250
|
-
certId: process.env.EBAY_CERT_ID!,
|
|
251
|
-
authToken: process.env.EBAY_AUTH_TOKEN!,
|
|
252
|
-
siteId: process.env.EBAY_SITE_ID || '0',
|
|
253
|
-
bufferStock: parseInt(process.env.EBAY_BUFFER_STOCK || '3'),
|
|
254
|
-
rateLimit: parseInt(process.env.EBAY_RATE_LIMIT || '5'),
|
|
255
|
-
},
|
|
256
|
-
walmart: {
|
|
257
|
-
enabled: process.env.WALMART_ENABLED === 'true',
|
|
258
|
-
clientId: process.env.WALMART_CLIENT_ID!,
|
|
259
|
-
clientSecret: process.env.WALMART_CLIENT_SECRET!,
|
|
260
|
-
channelType: process.env.WALMART_CHANNEL_TYPE!,
|
|
261
|
-
bufferStock: parseInt(process.env.WALMART_BUFFER_STOCK || '10'),
|
|
262
|
-
rateLimit: parseInt(process.env.WALMART_RATE_LIMIT || '20'),
|
|
263
|
-
},
|
|
264
|
-
processing: {
|
|
265
|
-
concurrency: parseInt(process.env.CONCURRENCY || '10'),
|
|
266
|
-
metricsEnabled: process.env.METRICS_ENABLED === 'true',
|
|
267
|
-
},
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
// ============================================================================
|
|
271
|
-
// MARKETPLACE API CLIENTS
|
|
272
|
-
// ============================================================================
|
|
273
|
-
|
|
274
|
-
class AmazonAPIClient {
|
|
275
|
-
private client: AxiosInstance;
|
|
276
|
-
private accessToken?: string;
|
|
277
|
-
private tokenExpiry?: Date;
|
|
278
|
-
|
|
279
|
-
constructor(
|
|
280
|
-
private config: typeof config.amazon,
|
|
281
|
-
private logger: StructuredLogger
|
|
282
|
-
) {
|
|
283
|
-
this.client = axios.create({
|
|
284
|
-
baseURL: 'https://sellingpartnerapi-na.amazon.com',
|
|
285
|
-
timeout: 30000,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async ensureAuthenticated(): Promise<void> {
|
|
290
|
-
if (this.accessToken && this.tokenExpiry && new Date() < this.tokenExpiry) {
|
|
291
|
-
return; // Token still valid
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
this.logger.info('Refreshing Amazon SP-API access token');
|
|
295
|
-
|
|
296
|
-
const response = await axios.post('https://api.amazon.com/auth/o2/token', {
|
|
297
|
-
grant_type: 'refresh_token',
|
|
298
|
-
refresh_token: this.config.refreshToken,
|
|
299
|
-
client_id: this.config.clientId,
|
|
300
|
-
client_secret: this.config.clientSecret,
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
this.accessToken = response.data.access_token;
|
|
304
|
-
this.tokenExpiry = new Date(Date.now() + response.data.expires_in * 1000);
|
|
305
|
-
|
|
306
|
-
this.logger.info('Amazon SP-API token refreshed successfully');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async updateInventory(sku: string, quantity: number): Promise<void> {
|
|
310
|
-
await this.ensureAuthenticated();
|
|
311
|
-
|
|
312
|
-
const payload = {
|
|
313
|
-
marketplaceId: this.config.marketplaceId,
|
|
314
|
-
sellerId: this.config.sellerId,
|
|
315
|
-
feeds: [
|
|
316
|
-
{
|
|
317
|
-
feedType: 'POST_INVENTORY_AVAILABILITY_DATA',
|
|
318
|
-
feedOptions: {
|
|
319
|
-
encoding: 'UTF-8',
|
|
320
|
-
},
|
|
321
|
-
feedContent: this.buildInventoryFeedXml(sku, quantity),
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
await this.client.post('/feeds/2021-06-30/feeds', payload, {
|
|
327
|
-
headers: {
|
|
328
|
-
'x-amz-access-token': this.accessToken,
|
|
329
|
-
'Content-Type': 'application/json',
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private buildInventoryFeedXml(sku: string, quantity: number): string {
|
|
335
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
336
|
-
<AmazonEnvelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="amzn-envelope.xsd">
|
|
337
|
-
<Header>
|
|
338
|
-
<DocumentVersion>1.01</DocumentVersion>
|
|
339
|
-
<MerchantIdentifier>${this.config.sellerId}</MerchantIdentifier>
|
|
340
|
-
</Header>
|
|
341
|
-
<MessageType>Inventory</MessageType>
|
|
342
|
-
<Message>
|
|
343
|
-
<MessageID>1</MessageID>
|
|
344
|
-
<OperationType>Update</OperationType>
|
|
345
|
-
<Inventory>
|
|
346
|
-
<SKU>${sku}</SKU>
|
|
347
|
-
<Quantity>${quantity}</Quantity>
|
|
348
|
-
<FulfillmentLatency>2</FulfillmentLatency>
|
|
349
|
-
</Inventory>
|
|
350
|
-
</Message>
|
|
351
|
-
</AmazonEnvelope>`;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
class eBayAPIClient {
|
|
356
|
-
private client: AxiosInstance;
|
|
357
|
-
|
|
358
|
-
constructor(
|
|
359
|
-
private config: typeof config.ebay,
|
|
360
|
-
private logger: StructuredLogger
|
|
361
|
-
) {
|
|
362
|
-
this.client = axios.create({
|
|
363
|
-
baseURL: 'https://api.ebay.com/ws/api.dll',
|
|
364
|
-
timeout: 30000,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async updateInventory(sku: string, quantity: number): Promise<void> {
|
|
369
|
-
const xmlRequest = `<?xml version="1.0" encoding="utf-8"?>
|
|
370
|
-
<ReviseInventoryStatusRequest xmlns="urn:ebay:apis:eBLBaseComponents">
|
|
371
|
-
<RequesterCredentials>
|
|
372
|
-
<eBayAuthToken>${this.config.authToken}</eBayAuthToken>
|
|
373
|
-
</RequesterCredentials>
|
|
374
|
-
<InventoryStatus>
|
|
375
|
-
<SKU>${sku}</SKU>
|
|
376
|
-
<Quantity>${quantity}</Quantity>
|
|
377
|
-
</InventoryStatus>
|
|
378
|
-
</ReviseInventoryStatusRequest>`;
|
|
379
|
-
|
|
380
|
-
await this.client.post('', xmlRequest, {
|
|
381
|
-
headers: {
|
|
382
|
-
'X-EBAY-API-SITEID': this.config.siteId,
|
|
383
|
-
'X-EBAY-API-COMPATIBILITY-LEVEL': '967',
|
|
384
|
-
'X-EBAY-API-CALL-NAME': 'ReviseInventoryStatus',
|
|
385
|
-
'Content-Type': 'text/xml',
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
class WalmartAPIClient {
|
|
392
|
-
private client: AxiosInstance;
|
|
393
|
-
private accessToken?: string;
|
|
394
|
-
private tokenExpiry?: Date;
|
|
395
|
-
|
|
396
|
-
constructor(
|
|
397
|
-
private config: typeof config.walmart,
|
|
398
|
-
private logger: StructuredLogger
|
|
399
|
-
) {
|
|
400
|
-
this.client = axios.create({
|
|
401
|
-
baseURL: 'https://marketplace.walmartapis.com/v3',
|
|
402
|
-
timeout: 30000,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
async ensureAuthenticated(): Promise<void> {
|
|
407
|
-
if (this.accessToken && this.tokenExpiry && new Date() < this.tokenExpiry) {
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
this.logger.info('Refreshing Walmart Marketplace access token');
|
|
412
|
-
|
|
413
|
-
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString(
|
|
414
|
-
'base64'
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
const response = await axios.post(
|
|
418
|
-
'https://marketplace.walmartapis.com/v3/token',
|
|
419
|
-
'grant_type=client_credentials',
|
|
420
|
-
{
|
|
421
|
-
headers: {
|
|
422
|
-
Authorization: `Basic ${credentials}`,
|
|
423
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
424
|
-
'WM_SVC.NAME': 'Walmart Marketplace',
|
|
425
|
-
'WM_QOS.CORRELATION_ID': Date.now().toString(),
|
|
426
|
-
},
|
|
427
|
-
}
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
this.accessToken = response.data.access_token;
|
|
431
|
-
this.tokenExpiry = new Date(Date.now() + response.data.expires_in * 1000);
|
|
432
|
-
|
|
433
|
-
this.logger.info('Walmart access token refreshed successfully');
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
async updateInventory(sku: string, quantity: number): Promise<void> {
|
|
437
|
-
await this.ensureAuthenticated();
|
|
438
|
-
|
|
439
|
-
const payload = {
|
|
440
|
-
sku,
|
|
441
|
-
quantity: {
|
|
442
|
-
unit: 'EACH',
|
|
443
|
-
amount: quantity,
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
await this.client.put(`/inventory?sku=${sku}`, payload, {
|
|
448
|
-
headers: {
|
|
449
|
-
'WM_SEC.ACCESS_TOKEN': this.accessToken,
|
|
450
|
-
'WM_SVC.NAME': 'Walmart Marketplace',
|
|
451
|
-
'WM_QOS.CORRELATION_ID': Date.now().toString(),
|
|
452
|
-
'Content-Type': 'application/json',
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// ============================================================================
|
|
459
|
-
// MULTI-CHANNEL INVENTORY SYNCHRONIZER
|
|
460
|
-
// ============================================================================
|
|
461
|
-
|
|
462
|
-
class MultiChannelInventorySync {
|
|
463
|
-
private fluentClient: FluentClient;
|
|
464
|
-
private logger: StructuredLogger;
|
|
465
|
-
private channels: Map<string, ChannelConfig> = new Map();
|
|
466
|
-
private metrics: SyncMetrics;
|
|
467
|
-
|
|
468
|
-
constructor() {
|
|
469
|
-
this.logger = toStructuredLogger(createConsoleLogger(), { logLevel: 'info' });
|
|
470
|
-
|
|
471
|
-
this.metrics = {
|
|
472
|
-
startTime: new Date(),
|
|
473
|
-
totalSKUs: 0,
|
|
474
|
-
successfulUpdates: 0,
|
|
475
|
-
failedUpdates: 0,
|
|
476
|
-
skippedUpdates: 0,
|
|
477
|
-
errors: [],
|
|
478
|
-
rateLimitHits: 0,
|
|
479
|
-
avgResponseTime: 0,
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
async initialize(): Promise<void> {
|
|
484
|
-
this.logger.info('Initializing Multi-Channel Inventory Sync');
|
|
485
|
-
|
|
486
|
-
// Initialize Fluent Commerce client
|
|
487
|
-
this.fluentClient = await createClient({ config: config.fluent });
|
|
488
|
-
|
|
489
|
-
// Initialize marketplace API clients
|
|
490
|
-
if (config.amazon.enabled) {
|
|
491
|
-
const amazonClient = new AmazonAPIClient(config.amazon, this.logger);
|
|
492
|
-
this.channels.set('amazon', {
|
|
493
|
-
enabled: true,
|
|
494
|
-
apiClient: amazonClient as any,
|
|
495
|
-
bufferStock: config.amazon.bufferStock,
|
|
496
|
-
rateLimit: config.amazon.rateLimit,
|
|
497
|
-
});
|
|
498
|
-
this.logger.info('Amazon SP-API client initialized');
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (config.ebay.enabled) {
|
|
502
|
-
const ebayClient = new eBayAPIClient(config.ebay, this.logger);
|
|
503
|
-
this.channels.set('ebay', {
|
|
504
|
-
enabled: true,
|
|
505
|
-
apiClient: ebayClient as any,
|
|
506
|
-
bufferStock: config.ebay.bufferStock,
|
|
507
|
-
rateLimit: config.ebay.rateLimit,
|
|
508
|
-
});
|
|
509
|
-
this.logger.info('eBay Trading API client initialized');
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (config.walmart.enabled) {
|
|
513
|
-
const walmartClient = new WalmartAPIClient(config.walmart, this.logger);
|
|
514
|
-
this.channels.set('walmart', {
|
|
515
|
-
enabled: true,
|
|
516
|
-
apiClient: walmartClient as any,
|
|
517
|
-
bufferStock: config.walmart.bufferStock,
|
|
518
|
-
rateLimit: config.walmart.rateLimit,
|
|
519
|
-
});
|
|
520
|
-
this.logger.info('Walmart Marketplace API client initialized');
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
this.logger.info(`Initialized ${this.channels.size} marketplace channels`);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
async syncInventory(): Promise<void> {
|
|
527
|
-
this.logger.info('Starting multi-channel inventory sync');
|
|
528
|
-
this.metrics.startTime = new Date();
|
|
529
|
-
|
|
530
|
-
try {
|
|
531
|
-
// Step 1: Fetch all ATP inventory from Fluent Commerce
|
|
532
|
-
const inventory = await this.fetchFluentInventory();
|
|
533
|
-
this.metrics.totalSKUs = inventory.length;
|
|
534
|
-
|
|
535
|
-
this.logger.info(`Fetched ${inventory.length} inventory positions from Fluent`);
|
|
536
|
-
|
|
537
|
-
// Step 2: Calculate ATP for each channel with buffer stock
|
|
538
|
-
const channelInventory = this.calculateChannelInventory(inventory);
|
|
539
|
-
|
|
540
|
-
// Step 3: Update each marketplace in parallel with rate limiting
|
|
541
|
-
await this.updateMarketplaces(channelInventory);
|
|
542
|
-
|
|
543
|
-
this.metrics.endTime = new Date();
|
|
544
|
-
this.logMetrics();
|
|
545
|
-
|
|
546
|
-
this.logger.info('Multi-channel inventory sync completed successfully');
|
|
547
|
-
} catch (error: any) {
|
|
548
|
-
this.logger.error('Multi-channel inventory sync failed', error);
|
|
549
|
-
throw error;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
private async fetchFluentInventory(): Promise<InventoryPosition[]> {
|
|
554
|
-
const query = `
|
|
555
|
-
query GetInventory($retailerId: ID!, $first: Int!, $after: String) {
|
|
556
|
-
inventoryQuantities(
|
|
557
|
-
first: $first
|
|
558
|
-
after: $after
|
|
559
|
-
retailerId: $retailerId
|
|
560
|
-
type: "AVAILABLE"
|
|
561
|
-
) {
|
|
562
|
-
edges {
|
|
563
|
-
node {
|
|
564
|
-
productRef
|
|
565
|
-
locationRef
|
|
566
|
-
quantity
|
|
567
|
-
onHand
|
|
568
|
-
available
|
|
569
|
-
reserved
|
|
570
|
-
}
|
|
571
|
-
cursor
|
|
572
|
-
}
|
|
573
|
-
pageInfo {
|
|
574
|
-
hasNextPage
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
`;
|
|
579
|
-
|
|
580
|
-
const result = await this.fluentClient.graphql({
|
|
581
|
-
query,
|
|
582
|
-
variables: {
|
|
583
|
-
retailerId: config.fluent.retailerId,
|
|
584
|
-
first: 250,
|
|
585
|
-
},
|
|
586
|
-
pagination: {
|
|
587
|
-
maxPages: 500,
|
|
588
|
-
},
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
if (result.errors) {
|
|
592
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const edges = (result.data as any).inventoryQuantities?.edges || [];
|
|
596
|
-
return edges.map((edge: any) => edge.node);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
private calculateChannelInventory(
|
|
600
|
-
inventory: InventoryPosition[]
|
|
601
|
-
): Map<string, Map<string, number>> {
|
|
602
|
-
const channelInventory = new Map<string, Map<string, number>>();
|
|
603
|
-
|
|
604
|
-
for (const [channelName, channelConfig] of this.channels) {
|
|
605
|
-
const skuQuantities = new Map<string, number>();
|
|
606
|
-
|
|
607
|
-
for (const position of inventory) {
|
|
608
|
-
const atpQuantity = position.available || 0;
|
|
609
|
-
const channelQuantity = Math.max(0, atpQuantity - channelConfig.bufferStock);
|
|
610
|
-
skuQuantities.set(position.productRef, channelQuantity);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
channelInventory.set(channelName, skuQuantities);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
return channelInventory;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
private async updateMarketplaces(
|
|
620
|
-
channelInventory: Map<string, Map<string, number>>
|
|
621
|
-
): Promise<void> {
|
|
622
|
-
const updatePromises: Promise<void>[] = [];
|
|
623
|
-
|
|
624
|
-
for (const [channelName, skuQuantities] of channelInventory) {
|
|
625
|
-
const channelConfig = this.channels.get(channelName)!;
|
|
626
|
-
const limit = pLimit(channelConfig.rateLimit);
|
|
627
|
-
|
|
628
|
-
const channelPromises = Array.from(skuQuantities.entries()).map(([sku, quantity]) =>
|
|
629
|
-
limit(async () => {
|
|
630
|
-
try {
|
|
631
|
-
const startTime = Date.now();
|
|
632
|
-
|
|
633
|
-
if (channelName === 'amazon') {
|
|
634
|
-
await (channelConfig.apiClient as any).updateInventory(sku, quantity);
|
|
635
|
-
} else if (channelName === 'ebay') {
|
|
636
|
-
await (channelConfig.apiClient as any).updateInventory(sku, quantity);
|
|
637
|
-
} else if (channelName === 'walmart') {
|
|
638
|
-
await (channelConfig.apiClient as any).updateInventory(sku, quantity);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const duration = Date.now() - startTime;
|
|
642
|
-
this.metrics.successfulUpdates++;
|
|
643
|
-
|
|
644
|
-
this.logger.debug(`Updated ${channelName} inventory for ${sku}: ${quantity}`, {
|
|
645
|
-
duration,
|
|
646
|
-
});
|
|
647
|
-
} catch (error: any) {
|
|
648
|
-
this.metrics.failedUpdates++;
|
|
649
|
-
this.metrics.errors.push({
|
|
650
|
-
sku,
|
|
651
|
-
channel: channelName,
|
|
652
|
-
error: error.message,
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
if (error.response?.status === 429) {
|
|
656
|
-
this.metrics.rateLimitHits++;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
this.logger.warn(`Failed to update ${channelName} inventory for ${sku}`, {
|
|
660
|
-
error: error.message,
|
|
661
|
-
status: error.response?.status,
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
})
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
updatePromises.push(...channelPromises);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
await Promise.all(updatePromises);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
private logMetrics(): void {
|
|
674
|
-
if (!config.processing.metricsEnabled) return;
|
|
675
|
-
|
|
676
|
-
const duration = this.metrics.endTime
|
|
677
|
-
? (this.metrics.endTime.getTime() - this.metrics.startTime.getTime()) / 1000
|
|
678
|
-
: 0;
|
|
679
|
-
|
|
680
|
-
this.logger.info('='.repeat(80));
|
|
681
|
-
this.logger.info('SYNC METRICS', {
|
|
682
|
-
duration: `${duration.toFixed(2)}s`,
|
|
683
|
-
totalSKUs: this.metrics.totalSKUs,
|
|
684
|
-
successfulUpdates: this.metrics.successfulUpdates,
|
|
685
|
-
failedUpdates: this.metrics.failedUpdates,
|
|
686
|
-
skippedUpdates: this.metrics.skippedUpdates,
|
|
687
|
-
successRate: `${((this.metrics.successfulUpdates / (this.metrics.successfulUpdates + this.metrics.failedUpdates)) * 100).toFixed(2)}%`,
|
|
688
|
-
rateLimitHits: this.metrics.rateLimitHits,
|
|
689
|
-
errorCount: this.metrics.errors.length,
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
if (this.metrics.errors.length > 0) {
|
|
693
|
-
this.logger.warn('Error summary (first 10):', {
|
|
694
|
-
errors: this.metrics.errors.slice(0, 10),
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
this.logger.info('='.repeat(80));
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// ============================================================================
|
|
703
|
-
// MAIN ENTRY POINT
|
|
704
|
-
// ============================================================================
|
|
705
|
-
|
|
706
|
-
async function main() {
|
|
707
|
-
const sync = new MultiChannelInventorySync();
|
|
708
|
-
|
|
709
|
-
try {
|
|
710
|
-
await sync.initialize();
|
|
711
|
-
await sync.syncInventory();
|
|
712
|
-
|
|
713
|
-
console.log('Multi-channel inventory sync completed successfully');
|
|
714
|
-
process.exit(0);
|
|
715
|
-
} catch (error: any) {
|
|
716
|
-
console.error('Fatal error:', error);
|
|
717
|
-
process.exit(1);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Run if executed directly
|
|
722
|
-
if (require.main === module) {
|
|
723
|
-
main();
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
export { MultiChannelInventorySync };
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
---
|
|
730
|
-
|
|
731
|
-
## Key Patterns Explained
|
|
732
|
-
|
|
733
|
-
### Pattern 1: GraphQL Auto-Pagination for Large Datasets
|
|
734
|
-
|
|
735
|
-
**Challenge**: Efficiently fetch 10k-100k inventory positions without manual pagination logic
|
|
736
|
-
|
|
737
|
-
**Solution**: Use SDK's built-in auto-pagination with progress callbacks
|
|
738
|
-
|
|
739
|
-
```javascript
|
|
740
|
-
const result = await fluentClient.graphql({
|
|
741
|
-
query,
|
|
742
|
-
variables: { retailerId, first: 200 },
|
|
743
|
-
pagination: {
|
|
744
|
-
maxRecords: 100000,
|
|
745
|
-
maxPages: 500,
|
|
746
|
-
},
|
|
747
|
-
});
|
|
748
|
-
```
|
|
749
|
-
|
|
750
|
-
**Why This Works**:
|
|
751
|
-
|
|
752
|
-
- **Automatic**: No manual cursor tracking needed
|
|
753
|
-
- **Safe**: Built-in protections against infinite loops
|
|
754
|
-
- **Efficient**: Streams data as it's received
|
|
755
|
-
- **Observable**: Progress callbacks for monitoring
|
|
756
|
-
|
|
757
|
-
### Pattern 2: ATP Aggregation with Location Rollup
|
|
758
|
-
|
|
759
|
-
**Challenge**: Calculate total Available-to-Promise across multiple warehouses/stores per SKU
|
|
760
|
-
|
|
761
|
-
**Solution**: Map-based aggregation with location tracking
|
|
762
|
-
|
|
763
|
-
```javascript
|
|
764
|
-
function aggregateATP(inventoryPositions) {
|
|
765
|
-
const atpMap = new Map();
|
|
766
|
-
for (const position of inventoryPositions) {
|
|
767
|
-
const sku = position.productRef;
|
|
768
|
-
const availableQty = Math.max(0, position.availableQty || 0);
|
|
769
|
-
|
|
770
|
-
if (!atpMap.has(sku)) {
|
|
771
|
-
atpMap.set(sku, {
|
|
772
|
-
sku,
|
|
773
|
-
totalATP: 0,
|
|
774
|
-
locations: [],
|
|
775
|
-
lastUpdated: position.updatedOn,
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const aggregated = atpMap.get(sku);
|
|
780
|
-
aggregated.totalATP += availableQty;
|
|
781
|
-
aggregated.locations.push({
|
|
782
|
-
locationRef: position.locationRef,
|
|
783
|
-
availableQty: availableQty,
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
return atpMap;
|
|
787
|
-
}
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
**Why This Works**:
|
|
791
|
-
|
|
792
|
-
- **O(n) complexity**: Single pass through inventory
|
|
793
|
-
- **Location visibility**: Track which locations contribute to total
|
|
794
|
-
- **Negative protection**: `Math.max(0, ...)` prevents negative ATP
|
|
795
|
-
- **Timestamp tracking**: Know when inventory was last updated
|
|
796
|
-
|
|
797
|
-
### Pattern 3: Marketplace-Specific Buffer Stock
|
|
798
|
-
|
|
799
|
-
**Challenge**: Different marketplaces need different safety buffers to prevent overselling
|
|
800
|
-
|
|
801
|
-
**Solution**: Configurable buffer per marketplace
|
|
802
|
-
|
|
803
|
-
```javascript
|
|
804
|
-
// Configuration
|
|
805
|
-
const buffers = {
|
|
806
|
-
amazon: 5, // Lower buffer (fast fulfillment)
|
|
807
|
-
ebay: 3, // Moderate buffer
|
|
808
|
-
walmart: 10, // Higher buffer (longer fulfillment)
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
// Application
|
|
812
|
-
function applyBufferStock(totalATP, bufferStock) {
|
|
813
|
-
return Math.max(0, totalATP - bufferStock);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Example: SKU with 50 ATP across all locations
|
|
817
|
-
const amazonQty = applyBufferStock(50, 5); // 45 available on Amazon
|
|
818
|
-
const ebayQty = applyBufferStock(50, 3); // 47 available on eBay
|
|
819
|
-
const walmartQty = applyBufferStock(50, 10); // 40 available on Walmart
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
**Why This Works**:
|
|
823
|
-
|
|
824
|
-
- **Oversell prevention**: Accounts for sync delays and processing time
|
|
825
|
-
- **Marketplace flexibility**: Different risk profiles per marketplace
|
|
826
|
-
- **Safety floor**: `Math.max(0, ...)` ensures quantity never goes negative
|
|
827
|
-
- **Configurable**: Easy to adjust per marketplace requirements
|
|
828
|
-
|
|
829
|
-
### Pattern 4: Parallel Marketplace Updates with Rate Limiting
|
|
830
|
-
|
|
831
|
-
**Challenge**: Update multiple marketplaces efficiently without exceeding API rate limits
|
|
832
|
-
|
|
833
|
-
**Solution**: Parallel execution with per-marketplace rate limiters
|
|
834
|
-
|
|
835
|
-
```javascript
|
|
836
|
-
import pLimit from 'p-limit';
|
|
837
|
-
|
|
838
|
-
// Each marketplace has its own rate limiter
|
|
839
|
-
class AmazonMarketplace {
|
|
840
|
-
constructor(config) {
|
|
841
|
-
this.limiter = pLimit(config.rateLimit); // 10 req/sec
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
async updateInventory(sku, quantity) {
|
|
845
|
-
// Rate-limited execution
|
|
846
|
-
return this.limiter(() => this.makeApiCall(sku, quantity));
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// Parallel sync across all marketplaces
|
|
851
|
-
const syncResults = await Promise.all(
|
|
852
|
-
marketplaces.map(marketplace => marketplace.syncInventory(atpMap))
|
|
853
|
-
);
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
**Why This Works**:
|
|
857
|
-
|
|
858
|
-
- **Parallel execution**: All marketplaces sync simultaneously
|
|
859
|
-
- **Independent rate limits**: Each marketplace respects its own limits
|
|
860
|
-
- **Error isolation**: Failure in one marketplace doesn't affect others
|
|
861
|
-
- **Concurrency control**: p-limit prevents overwhelming APIs
|
|
862
|
-
|
|
863
|
-
### Pattern 5: Exponential Backoff Retry Strategy
|
|
864
|
-
|
|
865
|
-
**Challenge**: Handle transient API failures (429 rate limits, 500 server errors)
|
|
866
|
-
|
|
867
|
-
**Solution**: p-retry with exponential backoff
|
|
868
|
-
|
|
869
|
-
```javascript
|
|
870
|
-
import pRetry from 'p-retry';
|
|
871
|
-
|
|
872
|
-
async updateInventory(sku, quantity) {
|
|
873
|
-
return pRetry(
|
|
874
|
-
async () => {
|
|
875
|
-
// Attempt API call
|
|
876
|
-
const response = await axios.put(endpoint, payload);
|
|
877
|
-
|
|
878
|
-
// Check for retryable errors
|
|
879
|
-
if (response.status === 429) {
|
|
880
|
-
throw new Error('Rate limit exceeded'); // Will retry
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
return response.data;
|
|
884
|
-
},
|
|
885
|
-
{
|
|
886
|
-
retries: 3,
|
|
887
|
-
factor: 2, // Exponential backoff factor
|
|
888
|
-
minTimeout: 1000, // 1s, 2s, 4s delays
|
|
889
|
-
onFailedAttempt: error => {
|
|
890
|
-
logger.warn(`Retry attempt ${error.attemptNumber}`, {
|
|
891
|
-
retriesLeft: error.retriesLeft,
|
|
892
|
-
});
|
|
893
|
-
},
|
|
894
|
-
}
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
**Why This Works**:
|
|
900
|
-
|
|
901
|
-
- **Transient failure recovery**: Handles temporary API issues
|
|
902
|
-
- **Exponential backoff**: Reduces load on overloaded APIs
|
|
903
|
-
- **Configurable retries**: 3 attempts is reasonable for most cases
|
|
904
|
-
- **Observable**: Logs each retry attempt for debugging
|
|
905
|
-
|
|
906
|
-
### Pattern 6: SLA Tracking and Compliance
|
|
907
|
-
|
|
908
|
-
**Challenge**: Ensure sync completes within 15-minute SLA requirement
|
|
909
|
-
|
|
910
|
-
**Solution**: Duration tracking with SLA compliance check
|
|
911
|
-
|
|
912
|
-
```javascript
|
|
913
|
-
function checkSLA(durationMs) {
|
|
914
|
-
const durationMinutes = durationMs / 60000;
|
|
915
|
-
const slaThreshold = 15; // 15-minute requirement
|
|
916
|
-
const slaMet = durationMinutes <= slaThreshold;
|
|
917
|
-
|
|
918
|
-
if (!slaMet) {
|
|
919
|
-
logger.warn('SLA BREACH: Sync exceeded threshold', {
|
|
920
|
-
durationMinutes: durationMinutes.toFixed(2),
|
|
921
|
-
threshold: slaThreshold,
|
|
922
|
-
});
|
|
923
|
-
metrics.increment('sla_breaches');
|
|
924
|
-
// Alert via PagerDuty, Slack, etc.
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
return slaMet;
|
|
928
|
-
}
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
**Why This Works**:
|
|
932
|
-
|
|
933
|
-
- **Clear threshold**: 15 minutes is measurable and actionable
|
|
934
|
-
- **Automated detection**: No manual checking needed
|
|
935
|
-
- **Alerting hook**: Can trigger notifications on breach
|
|
936
|
-
- **Metrics tracking**: Historical SLA compliance data
|
|
937
|
-
|
|
938
|
-
### Pattern 7: Dry-Run Mode for Safe Testing
|
|
939
|
-
|
|
940
|
-
**Challenge**: Test sync logic without actually updating marketplaces
|
|
941
|
-
|
|
942
|
-
**Solution**: Dry-run flag that short-circuits API calls
|
|
943
|
-
|
|
944
|
-
```javascript
|
|
945
|
-
async updateInventory(sku, quantity, dryRun = false) {
|
|
946
|
-
if (dryRun) {
|
|
947
|
-
this.logger.info(`[DRY RUN] Would update inventory`, { sku, quantity });
|
|
948
|
-
return { success: true, sku, quantity, dryRun: true };
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Real API call
|
|
952
|
-
return this.makeApiCall(sku, quantity);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// Usage
|
|
956
|
-
npm run sync:dry-run
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
**Why This Works**:
|
|
960
|
-
|
|
961
|
-
- **Safe testing**: Validate logic without side effects
|
|
962
|
-
- **End-to-end flow**: Tests everything except final API call
|
|
963
|
-
- **Easy toggle**: Environment variable or CLI flag
|
|
964
|
-
- **Visible logging**: Clear indication of dry-run mode
|
|
965
|
-
|
|
966
|
-
---
|
|
967
|
-
|
|
968
|
-
## Testing
|
|
969
|
-
|
|
970
|
-
### Dry-Run Test
|
|
971
|
-
|
|
972
|
-
```bash
|
|
973
|
-
# Test sync without updating marketplaces
|
|
974
|
-
DRY_RUN=true npm run sync:once
|
|
975
|
-
|
|
976
|
-
# Expected output:
|
|
977
|
-
# [DRY RUN] Would update Amazon inventory { sku: 'PROD-001', quantity: 45 }
|
|
978
|
-
# [DRY RUN] Would update eBay inventory { sku: 'PROD-001', quantity: 47 }
|
|
979
|
-
# [DRY RUN] Would update Walmart inventory { sku: 'PROD-001', quantity: 40 }
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
### Single Sync Execution
|
|
983
|
-
|
|
984
|
-
```bash
|
|
985
|
-
# Run once and exit
|
|
986
|
-
npm run sync:once
|
|
987
|
-
|
|
988
|
-
# Check logs
|
|
989
|
-
cat logs/inventory-sync.log | grep "COMPLETE"
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
### Continuous Sync Mode
|
|
993
|
-
|
|
994
|
-
```bash
|
|
995
|
-
# Run continuously (every 15 minutes)
|
|
996
|
-
npm run sync:continuous
|
|
997
|
-
|
|
998
|
-
# Check next sync time in logs
|
|
999
|
-
```
|
|
1000
|
-
|
|
1001
|
-
---
|
|
1002
|
-
|
|
1003
|
-
## Deployment Options
|
|
1004
|
-
|
|
1005
|
-
### Option 1: Docker Container
|
|
1006
|
-
|
|
1007
|
-
Create `Dockerfile`:
|
|
1008
|
-
|
|
1009
|
-
```dockerfile
|
|
1010
|
-
FROM node:18-alpine
|
|
1011
|
-
|
|
1012
|
-
WORKDIR /app
|
|
1013
|
-
|
|
1014
|
-
# Copy package files
|
|
1015
|
-
COPY package*.json ./
|
|
1016
|
-
|
|
1017
|
-
# Install dependencies
|
|
1018
|
-
RUN npm ci --only=production
|
|
1019
|
-
|
|
1020
|
-
# Copy application code
|
|
1021
|
-
COPY src ./src
|
|
1022
|
-
|
|
1023
|
-
# Create state directory
|
|
1024
|
-
RUN mkdir -p /app/state
|
|
1025
|
-
|
|
1026
|
-
# Run continuous sync
|
|
1027
|
-
CMD ["node", "src/index.js"]
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
Build and run:
|
|
1031
|
-
|
|
1032
|
-
```bash
|
|
1033
|
-
# Build image
|
|
1034
|
-
docker build -t multi-channel-sync .
|
|
1035
|
-
|
|
1036
|
-
# Run container
|
|
1037
|
-
docker run -d \
|
|
1038
|
-
--name inventory-sync \
|
|
1039
|
-
--env-file .env \
|
|
1040
|
-
--restart unless-stopped \
|
|
1041
|
-
--volume ./state:/app/state \
|
|
1042
|
-
multi-channel-sync
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
### Option 2: AWS ECS Task
|
|
1046
|
-
|
|
1047
|
-
Create `ecs-task-definition.json`:
|
|
1048
|
-
|
|
1049
|
-
```json
|
|
1050
|
-
{
|
|
1051
|
-
"family": "multi-channel-inventory-sync",
|
|
1052
|
-
"networkMode": "awsvpc",
|
|
1053
|
-
"requiresCompatibilities": ["FARGATE"],
|
|
1054
|
-
"cpu": "1024",
|
|
1055
|
-
"memory": "2048",
|
|
1056
|
-
"containerDefinitions": [
|
|
1057
|
-
{
|
|
1058
|
-
"name": "inventory-sync",
|
|
1059
|
-
"image": "your-registry/multi-channel-sync:latest",
|
|
1060
|
-
"essential": true,
|
|
1061
|
-
"environment": [
|
|
1062
|
-
{ "name": "LOG_LEVEL", "value": "info" },
|
|
1063
|
-
{ "name": "SYNC_INTERVAL_MINUTES", "value": "15" }
|
|
1064
|
-
],
|
|
1065
|
-
"secrets": [
|
|
1066
|
-
{
|
|
1067
|
-
"name": "FLUENT_CLIENT_ID",
|
|
1068
|
-
"valueFrom": "arn:aws:secretsmanager:us-east-1:123:secret:fluent-client-id"
|
|
1069
|
-
}
|
|
1070
|
-
],
|
|
1071
|
-
"logConfiguration": {
|
|
1072
|
-
"logDriver": "awslogs",
|
|
1073
|
-
"options": {
|
|
1074
|
-
"awslogs-group": "/ecs/inventory-sync",
|
|
1075
|
-
"awslogs-region": "us-east-1",
|
|
1076
|
-
"awslogs-stream-prefix": "ecs"
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
]
|
|
1081
|
-
}
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
Deploy:
|
|
1085
|
-
|
|
1086
|
-
```bash
|
|
1087
|
-
aws ecs register-task-definition --cli-input-json file://ecs-task-definition.json
|
|
1088
|
-
aws ecs create-service \
|
|
1089
|
-
--cluster production \
|
|
1090
|
-
--service-name inventory-sync \
|
|
1091
|
-
--task-definition multi-channel-inventory-sync \
|
|
1092
|
-
--desired-count 1 \
|
|
1093
|
-
--launch-type FARGATE
|
|
1094
|
-
```
|
|
1095
|
-
|
|
1096
|
-
---
|
|
1097
|
-
|
|
1098
|
-
## Common Issues & Solutions
|
|
1099
|
-
|
|
1100
|
-
### Issue 1: SLA Breach (Sync Takes >15 Minutes)
|
|
1101
|
-
|
|
1102
|
-
**Symptoms**:
|
|
1103
|
-
|
|
1104
|
-
- Log shows "SLA BREACH" warning
|
|
1105
|
-
- Sync duration exceeds 15 minutes
|
|
1106
|
-
|
|
1107
|
-
**Solutions**:
|
|
1108
|
-
|
|
1109
|
-
1. **Reduce page size** (faster initial response):
|
|
1110
|
-
|
|
1111
|
-
```javascript
|
|
1112
|
-
variables: {
|
|
1113
|
-
first: 100;
|
|
1114
|
-
} // Instead of 200
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
2. **Increase parallelism**:
|
|
1118
|
-
|
|
1119
|
-
```javascript
|
|
1120
|
-
const limiter = pLimit(20); // Increase from 10
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
3. **Optimize marketplace rate limits** (if APIs allow):
|
|
1124
|
-
|
|
1125
|
-
```bash
|
|
1126
|
-
AMAZON_RATE_LIMIT=20 # Increase from 10
|
|
1127
|
-
```
|
|
1128
|
-
|
|
1129
|
-
4. **Split sync by marketplace** (run separately):
|
|
1130
|
-
|
|
1131
|
-
```bash
|
|
1132
|
-
AMAZON_ENABLED=true EBAY_ENABLED=false WALMART_ENABLED=false npm run sync:once
|
|
1133
|
-
```
|
|
1134
|
-
|
|
1135
|
-
---
|
|
1136
|
-
|
|
1137
|
-
## Related Guides
|
|
1138
|
-
|
|
1139
|
-
- **GraphQL Query Export**: [`./graphql-query-export.md`](./graphql-query-export.md) - GraphQL pagination patterns
|
|
1140
|
-
- **Multi-Source Aggregation**: `./04-multi-source-aggregation.md` - Data aggregation strategies
|
|
1141
|
-
- **Error Handling Patterns**: `../../03-PATTERN-GUIDES/error-handling/readme.md` - Retry and error handling
|
|
1142
|
-
- **Universal Mapping Guide**: `../../02-CORE-GUIDES/mapping/readme.md` - Field transformations
|
|
1143
|
-
- **SDK API Reference**: `../../readme.md` - Core SDK documentation
|
|
1144
|
-
|
|
1145
|
-
---
|
|
1146
|
-
|
|
1147
|
-
## Production Checklist
|
|
1148
|
-
|
|
1149
|
-
Before deploying to production:
|
|
1150
|
-
|
|
1151
|
-
- [ ] All marketplace credentials secured (AWS Secrets Manager, Vault)
|
|
1152
|
-
- [ ] Rate limits configured per marketplace API documentation
|
|
1153
|
-
- [ ] Buffer stock values tested and approved by business
|
|
1154
|
-
- [ ] SLA threshold aligned with business requirements
|
|
1155
|
-
- [ ] Monitoring dashboards created (sync duration, SLA compliance, error rates)
|
|
1156
|
-
- [ ] Alerting configured (PagerDuty, Slack, email)
|
|
1157
|
-
- [ ] Dry-run testing completed with production data
|
|
1158
|
-
- [ ] Load testing with 100k+ SKUs
|
|
1159
|
-
- [ ] State management enabled to prevent duplicate processing
|
|
1160
|
-
- [ ] Logging configured (CloudWatch, Datadog, Splunk)
|
|
1161
|
-
- [ ] Error tracking integrated (Sentry, Rollbar)
|
|
1162
|
-
- [ ] Documentation updated with runbooks
|
|
1163
|
-
- [ ] On-call team trained on troubleshooting
|
|
1164
|
-
- [ ] Disaster recovery plan documented
|
|
1165
|
-
- [ ] Metrics baseline established (average sync duration, success rate)
|
|
1166
|
-
|
|
1167
|
-
---
|
|
1168
|
-
|
|
1169
|
-
**Next Steps**:
|
|
1170
|
-
|
|
1171
|
-
1. Customize buffer stock values per marketplace and product category
|
|
1172
|
-
2. Implement incremental sync (only process changed inventory)
|
|
1173
|
-
3. Add webhook integration for real-time inventory updates
|
|
1174
|
-
4. Set up monitoring dashboards (Grafana, Datadog)
|
|
1175
|
-
5. Integrate with incident management (PagerDuty)
|
|
1176
|
-
6. Add marketplace-specific SKU mapping (Fluent SKU → Marketplace SKU)
|
|
1177
|
-
7. Implement A/B testing for buffer stock optimization
|
|
1178
|
-
|
|
1179
|
-
**Support**:
|
|
1180
|
-
|
|
1181
|
-
- SDK Issues: https://github.com/fluentcommerce/fc-connect-sdk/issues
|
|
1182
|
-
- Fluent Commerce API: https://docs.fluentcommerce.com
|
|
1183
|
-
- Amazon SP-API: https://developer-docs.amazon.com/sp-api/
|
|
1184
|
-
- eBay Trading API: https://developer.ebay.com/devzone/xml/docs/reference/ebay/
|
|
1185
|
-
- Walmart Seller API: https://developer.walmart.com/
|
|
1
|
+
# Standalone: Multi-Channel Inventory Sync (Real-Time ATP to Marketplaces)
|
|
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**: Node.js script that aggregates Available-to-Promise (ATP) inventory from Fluent Commerce and pushes real-time updates to multiple marketplaces (Amazon, eBay, Walmart)
|
|
9
|
+
|
|
10
|
+
**Complexity**: High
|
|
11
|
+
|
|
12
|
+
**Runtime**: Node.js >=18
|
|
13
|
+
|
|
14
|
+
**Estimated Lines**: ~950 lines
|
|
15
|
+
|
|
16
|
+
**Volume**: Large (10k-100k SKUs, multiple marketplaces)
|
|
17
|
+
|
|
18
|
+
**Latency**: Near real-time (every 5-15 min)
|
|
19
|
+
|
|
20
|
+
**Pattern**: GraphQL pagination → external marketplace APIs
|
|
21
|
+
|
|
22
|
+
## What You'll Build
|
|
23
|
+
|
|
24
|
+
- OAuth2-authenticated Fluent Commerce client
|
|
25
|
+
- GraphQL query with auto-pagination for inventory positions
|
|
26
|
+
- ATP (Available-to-Promise) aggregation across multiple locations
|
|
27
|
+
- Marketplace-specific buffer stock calculations
|
|
28
|
+
- Parallel marketplace updates with rate limiting:
|
|
29
|
+
- Amazon MWS/SP-API integration
|
|
30
|
+
- eBay Trading API integration
|
|
31
|
+
- Walmart Seller API integration
|
|
32
|
+
- Per-marketplace error handling and retry logic
|
|
33
|
+
- SLA tracking (15-minute sync requirement)
|
|
34
|
+
- Comprehensive metrics and logging
|
|
35
|
+
- Dry-run mode for testing
|
|
36
|
+
- State management to prevent duplicate updates
|
|
37
|
+
|
|
38
|
+
## SDK Methods Used
|
|
39
|
+
|
|
40
|
+
- `createClient({ config: { baseUrl, clientId, clientSecret, retailerId } })` - OAuth2 client
|
|
41
|
+
- `client.graphql({ query, variables, pagination })` - Query with auto-pagination
|
|
42
|
+
- `UniversalMapper(mappingConfig)` - Transform Fluent data to marketplace formats
|
|
43
|
+
- `createConsoleLogger()` - Simple console logger
|
|
44
|
+
- `toStructuredLogger(logger, context)` - Add context to logs
|
|
45
|
+
- `generateCorrelationId()` - Generate unique correlation IDs
|
|
46
|
+
- State tracking for incremental updates
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Complete Working Implementation
|
|
51
|
+
|
|
52
|
+
### 1. Project Setup
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Create project directory
|
|
56
|
+
mkdir multi-channel-inventory-sync
|
|
57
|
+
cd multi-channel-inventory-sync
|
|
58
|
+
|
|
59
|
+
# Initialize Node.js project
|
|
60
|
+
npm init -y
|
|
61
|
+
|
|
62
|
+
# Install dependencies
|
|
63
|
+
npm install @fluentcommerce/fc-connect-sdk dotenv axios p-limit p-retry
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Environment Configuration
|
|
67
|
+
|
|
68
|
+
Create `.env` file:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Fluent Commerce OAuth2
|
|
72
|
+
FLUENT_BASE_URL=https://api.fluentcommerce.com
|
|
73
|
+
FLUENT_CLIENT_ID=your-oauth2-client-id
|
|
74
|
+
FLUENT_CLIENT_SECRET=your-oauth2-client-secret
|
|
75
|
+
FLUENT_RETAILER_ID=your-retailer-id
|
|
76
|
+
|
|
77
|
+
# Sync Configuration
|
|
78
|
+
SYNC_INTERVAL_MINUTES=15
|
|
79
|
+
SLA_THRESHOLD_MINUTES=15
|
|
80
|
+
MAX_RECORDS_PER_SYNC=100000
|
|
81
|
+
DRY_RUN=false
|
|
82
|
+
|
|
83
|
+
# Amazon SP-API Credentials
|
|
84
|
+
AMAZON_ENABLED=true
|
|
85
|
+
AMAZON_MARKETPLACE_ID=ATVPDKIKX0DER
|
|
86
|
+
AMAZON_SELLER_ID=your-amazon-seller-id
|
|
87
|
+
AMAZON_REFRESH_TOKEN=your-amazon-refresh-token
|
|
88
|
+
AMAZON_CLIENT_ID=amzn1.application-oa2-client.xxxxx
|
|
89
|
+
AMAZON_CLIENT_SECRET=your-amazon-client-secret
|
|
90
|
+
AMAZON_AWS_ACCESS_KEY_ID=your-aws-access-key
|
|
91
|
+
AMAZON_AWS_SECRET_ACCESS_KEY=your-aws-secret-key
|
|
92
|
+
AMAZON_REGION=us-east-1
|
|
93
|
+
AMAZON_ROLE_ARN=arn:aws:iam::123456789:role/SellingPartnerAPI
|
|
94
|
+
AMAZON_BUFFER_STOCK=5
|
|
95
|
+
AMAZON_RATE_LIMIT=10
|
|
96
|
+
|
|
97
|
+
# eBay Trading API Credentials
|
|
98
|
+
EBAY_ENABLED=true
|
|
99
|
+
EBAY_APP_ID=your-ebay-app-id
|
|
100
|
+
EBAY_DEV_ID=your-ebay-dev-id
|
|
101
|
+
EBAY_CERT_ID=your-ebay-cert-id
|
|
102
|
+
EBAY_AUTH_TOKEN=your-ebay-auth-token
|
|
103
|
+
EBAY_SITE_ID=0
|
|
104
|
+
EBAY_SANDBOX=false
|
|
105
|
+
EBAY_BUFFER_STOCK=3
|
|
106
|
+
EBAY_RATE_LIMIT=5
|
|
107
|
+
|
|
108
|
+
# Walmart Seller API Credentials
|
|
109
|
+
WALMART_ENABLED=true
|
|
110
|
+
WALMART_CLIENT_ID=your-walmart-client-id
|
|
111
|
+
WALMART_CLIENT_SECRET=your-walmart-client-secret
|
|
112
|
+
WALMART_CHANNEL_TYPE=0a7d6c3e-e9a0-4a0c-8d6a-f6c6f1e7a5c7
|
|
113
|
+
WALMART_BUFFER_STOCK=10
|
|
114
|
+
WALMART_RATE_LIMIT=20
|
|
115
|
+
|
|
116
|
+
# Logging
|
|
117
|
+
LOG_LEVEL=info
|
|
118
|
+
METRICS_ENABLED=true
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Package Configuration
|
|
122
|
+
|
|
123
|
+
Create `package.json`:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"name": "multi-channel-inventory-sync",
|
|
128
|
+
"version": "1.0.0",
|
|
129
|
+
"type": "module",
|
|
130
|
+
"description": "Real-time ATP inventory sync from Fluent Commerce to multiple marketplaces",
|
|
131
|
+
"main": "src/index.js",
|
|
132
|
+
"scripts": {
|
|
133
|
+
"start": "node src/index.js",
|
|
134
|
+
"sync:once": "node src/index.js --once",
|
|
135
|
+
"sync:continuous": "node src/index.js",
|
|
136
|
+
"sync:dry-run": "DRY_RUN=true node src/index.js --once",
|
|
137
|
+
"test": "node --test tests/**/*.test.js"
|
|
138
|
+
},
|
|
139
|
+
"dependencies": {
|
|
140
|
+
"@fluentcommerce/fc-connect-sdk": "^0.1.39",
|
|
141
|
+
"axios": "^1.6.0",
|
|
142
|
+
"dotenv": "^16.0.0",
|
|
143
|
+
"p-limit": "^5.0.0",
|
|
144
|
+
"p-retry": "^6.0.0"
|
|
145
|
+
},
|
|
146
|
+
"devDependencies": {
|
|
147
|
+
"@types/node": "^20.0.0"
|
|
148
|
+
},
|
|
149
|
+
"engines": {
|
|
150
|
+
"node": ">=18.0.0"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 4. TypeScript Configuration (tsconfig.json)
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"compilerOptions": {
|
|
160
|
+
"module": "ES2022",
|
|
161
|
+
"target": "ES2024",
|
|
162
|
+
"moduleResolution": "node"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 5. Main Script Implementation (src/multi-channel-sync.ts)
|
|
168
|
+
|
|
169
|
+
> **⚠️ RUNTIME COMPATIBILITY NOTE:**
|
|
170
|
+
> This template includes `import { Buffer } from 'node:buffer';` for Deno/Versori compatibility.
|
|
171
|
+
> While Buffer is globally available in Node.js and this import is optional for Node.js-only deployments,
|
|
172
|
+
> including it ensures the code works across all runtimes (Node.js, Deno, Versori) without modification.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import 'dotenv/config';
|
|
176
|
+
import { Buffer } from 'node:buffer'; // Required for Deno/Versori runtime compatibility
|
|
177
|
+
|
|
178
|
+
// FC Connect SDK+
|
|
179
|
+
// Install: npm install @fluentcommerce/fc-connect-sdk@latest
|
|
180
|
+
// Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
|
|
181
|
+
// GitHub: https://github.com/fluentcommerce/fc-connect-sdk
|
|
182
|
+
|
|
183
|
+
import {
|
|
184
|
+
createClient,
|
|
185
|
+
type FluentClient,
|
|
186
|
+
type StructuredLogger,
|
|
187
|
+
createConsoleLogger,
|
|
188
|
+
toStructuredLogger
|
|
189
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
190
|
+
|
|
191
|
+
import axios, { AxiosInstance } from 'axios';
|
|
192
|
+
import pLimit from 'p-limit';
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// TYPES & INTERFACES
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
interface InventoryPosition {
|
|
199
|
+
productRef: string;
|
|
200
|
+
locationRef: string;
|
|
201
|
+
quantity: number;
|
|
202
|
+
onHand: number;
|
|
203
|
+
available: number;
|
|
204
|
+
reserved: number;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface ChannelConfig {
|
|
208
|
+
enabled: boolean;
|
|
209
|
+
apiClient: AxiosInstance;
|
|
210
|
+
bufferStock: number;
|
|
211
|
+
rateLimit: number;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface SyncMetrics {
|
|
215
|
+
startTime: Date;
|
|
216
|
+
endTime?: Date;
|
|
217
|
+
totalSKUs: number;
|
|
218
|
+
successfulUpdates: number;
|
|
219
|
+
failedUpdates: number;
|
|
220
|
+
skippedUpdates: number;
|
|
221
|
+
errors: Array<{ sku: string; channel: string; error: string }>;
|
|
222
|
+
rateLimitHits: number;
|
|
223
|
+
avgResponseTime: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// CONFIGURATION
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
const config = {
|
|
231
|
+
fluent: {
|
|
232
|
+
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
233
|
+
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
234
|
+
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
235
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
236
|
+
},
|
|
237
|
+
amazon: {
|
|
238
|
+
enabled: process.env.AMAZON_ENABLED === 'true',
|
|
239
|
+
clientId: process.env.AMAZON_CLIENT_ID!,
|
|
240
|
+
clientSecret: process.env.AMAZON_CLIENT_SECRET!,
|
|
241
|
+
refreshToken: process.env.AMAZON_REFRESH_TOKEN!,
|
|
242
|
+
marketplaceId: process.env.AMAZON_MARKETPLACE_ID!,
|
|
243
|
+
sellerId: process.env.AMAZON_SELLER_ID!,
|
|
244
|
+
bufferStock: parseInt(process.env.AMAZON_BUFFER_STOCK || '5'),
|
|
245
|
+
rateLimit: parseInt(process.env.AMAZON_RATE_LIMIT || '10'),
|
|
246
|
+
},
|
|
247
|
+
ebay: {
|
|
248
|
+
enabled: process.env.EBAY_ENABLED === 'true',
|
|
249
|
+
appId: process.env.EBAY_APP_ID!,
|
|
250
|
+
certId: process.env.EBAY_CERT_ID!,
|
|
251
|
+
authToken: process.env.EBAY_AUTH_TOKEN!,
|
|
252
|
+
siteId: process.env.EBAY_SITE_ID || '0',
|
|
253
|
+
bufferStock: parseInt(process.env.EBAY_BUFFER_STOCK || '3'),
|
|
254
|
+
rateLimit: parseInt(process.env.EBAY_RATE_LIMIT || '5'),
|
|
255
|
+
},
|
|
256
|
+
walmart: {
|
|
257
|
+
enabled: process.env.WALMART_ENABLED === 'true',
|
|
258
|
+
clientId: process.env.WALMART_CLIENT_ID!,
|
|
259
|
+
clientSecret: process.env.WALMART_CLIENT_SECRET!,
|
|
260
|
+
channelType: process.env.WALMART_CHANNEL_TYPE!,
|
|
261
|
+
bufferStock: parseInt(process.env.WALMART_BUFFER_STOCK || '10'),
|
|
262
|
+
rateLimit: parseInt(process.env.WALMART_RATE_LIMIT || '20'),
|
|
263
|
+
},
|
|
264
|
+
processing: {
|
|
265
|
+
concurrency: parseInt(process.env.CONCURRENCY || '10'),
|
|
266
|
+
metricsEnabled: process.env.METRICS_ENABLED === 'true',
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// MARKETPLACE API CLIENTS
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
class AmazonAPIClient {
|
|
275
|
+
private client: AxiosInstance;
|
|
276
|
+
private accessToken?: string;
|
|
277
|
+
private tokenExpiry?: Date;
|
|
278
|
+
|
|
279
|
+
constructor(
|
|
280
|
+
private config: typeof config.amazon,
|
|
281
|
+
private logger: StructuredLogger
|
|
282
|
+
) {
|
|
283
|
+
this.client = axios.create({
|
|
284
|
+
baseURL: 'https://sellingpartnerapi-na.amazon.com',
|
|
285
|
+
timeout: 30000,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async ensureAuthenticated(): Promise<void> {
|
|
290
|
+
if (this.accessToken && this.tokenExpiry && new Date() < this.tokenExpiry) {
|
|
291
|
+
return; // Token still valid
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.logger.info('Refreshing Amazon SP-API access token');
|
|
295
|
+
|
|
296
|
+
const response = await axios.post('https://api.amazon.com/auth/o2/token', {
|
|
297
|
+
grant_type: 'refresh_token',
|
|
298
|
+
refresh_token: this.config.refreshToken,
|
|
299
|
+
client_id: this.config.clientId,
|
|
300
|
+
client_secret: this.config.clientSecret,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
this.accessToken = response.data.access_token;
|
|
304
|
+
this.tokenExpiry = new Date(Date.now() + response.data.expires_in * 1000);
|
|
305
|
+
|
|
306
|
+
this.logger.info('Amazon SP-API token refreshed successfully');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async updateInventory(sku: string, quantity: number): Promise<void> {
|
|
310
|
+
await this.ensureAuthenticated();
|
|
311
|
+
|
|
312
|
+
const payload = {
|
|
313
|
+
marketplaceId: this.config.marketplaceId,
|
|
314
|
+
sellerId: this.config.sellerId,
|
|
315
|
+
feeds: [
|
|
316
|
+
{
|
|
317
|
+
feedType: 'POST_INVENTORY_AVAILABILITY_DATA',
|
|
318
|
+
feedOptions: {
|
|
319
|
+
encoding: 'UTF-8',
|
|
320
|
+
},
|
|
321
|
+
feedContent: this.buildInventoryFeedXml(sku, quantity),
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
await this.client.post('/feeds/2021-06-30/feeds', payload, {
|
|
327
|
+
headers: {
|
|
328
|
+
'x-amz-access-token': this.accessToken,
|
|
329
|
+
'Content-Type': 'application/json',
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private buildInventoryFeedXml(sku: string, quantity: number): string {
|
|
335
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
336
|
+
<AmazonEnvelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="amzn-envelope.xsd">
|
|
337
|
+
<Header>
|
|
338
|
+
<DocumentVersion>1.01</DocumentVersion>
|
|
339
|
+
<MerchantIdentifier>${this.config.sellerId}</MerchantIdentifier>
|
|
340
|
+
</Header>
|
|
341
|
+
<MessageType>Inventory</MessageType>
|
|
342
|
+
<Message>
|
|
343
|
+
<MessageID>1</MessageID>
|
|
344
|
+
<OperationType>Update</OperationType>
|
|
345
|
+
<Inventory>
|
|
346
|
+
<SKU>${sku}</SKU>
|
|
347
|
+
<Quantity>${quantity}</Quantity>
|
|
348
|
+
<FulfillmentLatency>2</FulfillmentLatency>
|
|
349
|
+
</Inventory>
|
|
350
|
+
</Message>
|
|
351
|
+
</AmazonEnvelope>`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
class eBayAPIClient {
|
|
356
|
+
private client: AxiosInstance;
|
|
357
|
+
|
|
358
|
+
constructor(
|
|
359
|
+
private config: typeof config.ebay,
|
|
360
|
+
private logger: StructuredLogger
|
|
361
|
+
) {
|
|
362
|
+
this.client = axios.create({
|
|
363
|
+
baseURL: 'https://api.ebay.com/ws/api.dll',
|
|
364
|
+
timeout: 30000,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async updateInventory(sku: string, quantity: number): Promise<void> {
|
|
369
|
+
const xmlRequest = `<?xml version="1.0" encoding="utf-8"?>
|
|
370
|
+
<ReviseInventoryStatusRequest xmlns="urn:ebay:apis:eBLBaseComponents">
|
|
371
|
+
<RequesterCredentials>
|
|
372
|
+
<eBayAuthToken>${this.config.authToken}</eBayAuthToken>
|
|
373
|
+
</RequesterCredentials>
|
|
374
|
+
<InventoryStatus>
|
|
375
|
+
<SKU>${sku}</SKU>
|
|
376
|
+
<Quantity>${quantity}</Quantity>
|
|
377
|
+
</InventoryStatus>
|
|
378
|
+
</ReviseInventoryStatusRequest>`;
|
|
379
|
+
|
|
380
|
+
await this.client.post('', xmlRequest, {
|
|
381
|
+
headers: {
|
|
382
|
+
'X-EBAY-API-SITEID': this.config.siteId,
|
|
383
|
+
'X-EBAY-API-COMPATIBILITY-LEVEL': '967',
|
|
384
|
+
'X-EBAY-API-CALL-NAME': 'ReviseInventoryStatus',
|
|
385
|
+
'Content-Type': 'text/xml',
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
class WalmartAPIClient {
|
|
392
|
+
private client: AxiosInstance;
|
|
393
|
+
private accessToken?: string;
|
|
394
|
+
private tokenExpiry?: Date;
|
|
395
|
+
|
|
396
|
+
constructor(
|
|
397
|
+
private config: typeof config.walmart,
|
|
398
|
+
private logger: StructuredLogger
|
|
399
|
+
) {
|
|
400
|
+
this.client = axios.create({
|
|
401
|
+
baseURL: 'https://marketplace.walmartapis.com/v3',
|
|
402
|
+
timeout: 30000,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async ensureAuthenticated(): Promise<void> {
|
|
407
|
+
if (this.accessToken && this.tokenExpiry && new Date() < this.tokenExpiry) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
this.logger.info('Refreshing Walmart Marketplace access token');
|
|
412
|
+
|
|
413
|
+
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString(
|
|
414
|
+
'base64'
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const response = await axios.post(
|
|
418
|
+
'https://marketplace.walmartapis.com/v3/token',
|
|
419
|
+
'grant_type=client_credentials',
|
|
420
|
+
{
|
|
421
|
+
headers: {
|
|
422
|
+
Authorization: `Basic ${credentials}`,
|
|
423
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
424
|
+
'WM_SVC.NAME': 'Walmart Marketplace',
|
|
425
|
+
'WM_QOS.CORRELATION_ID': Date.now().toString(),
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
this.accessToken = response.data.access_token;
|
|
431
|
+
this.tokenExpiry = new Date(Date.now() + response.data.expires_in * 1000);
|
|
432
|
+
|
|
433
|
+
this.logger.info('Walmart access token refreshed successfully');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async updateInventory(sku: string, quantity: number): Promise<void> {
|
|
437
|
+
await this.ensureAuthenticated();
|
|
438
|
+
|
|
439
|
+
const payload = {
|
|
440
|
+
sku,
|
|
441
|
+
quantity: {
|
|
442
|
+
unit: 'EACH',
|
|
443
|
+
amount: quantity,
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
await this.client.put(`/inventory?sku=${sku}`, payload, {
|
|
448
|
+
headers: {
|
|
449
|
+
'WM_SEC.ACCESS_TOKEN': this.accessToken,
|
|
450
|
+
'WM_SVC.NAME': 'Walmart Marketplace',
|
|
451
|
+
'WM_QOS.CORRELATION_ID': Date.now().toString(),
|
|
452
|
+
'Content-Type': 'application/json',
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ============================================================================
|
|
459
|
+
// MULTI-CHANNEL INVENTORY SYNCHRONIZER
|
|
460
|
+
// ============================================================================
|
|
461
|
+
|
|
462
|
+
class MultiChannelInventorySync {
|
|
463
|
+
private fluentClient: FluentClient;
|
|
464
|
+
private logger: StructuredLogger;
|
|
465
|
+
private channels: Map<string, ChannelConfig> = new Map();
|
|
466
|
+
private metrics: SyncMetrics;
|
|
467
|
+
|
|
468
|
+
constructor() {
|
|
469
|
+
this.logger = toStructuredLogger(createConsoleLogger(), { logLevel: 'info' });
|
|
470
|
+
|
|
471
|
+
this.metrics = {
|
|
472
|
+
startTime: new Date(),
|
|
473
|
+
totalSKUs: 0,
|
|
474
|
+
successfulUpdates: 0,
|
|
475
|
+
failedUpdates: 0,
|
|
476
|
+
skippedUpdates: 0,
|
|
477
|
+
errors: [],
|
|
478
|
+
rateLimitHits: 0,
|
|
479
|
+
avgResponseTime: 0,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async initialize(): Promise<void> {
|
|
484
|
+
this.logger.info('Initializing Multi-Channel Inventory Sync');
|
|
485
|
+
|
|
486
|
+
// Initialize Fluent Commerce client
|
|
487
|
+
this.fluentClient = await createClient({ config: config.fluent });
|
|
488
|
+
|
|
489
|
+
// Initialize marketplace API clients
|
|
490
|
+
if (config.amazon.enabled) {
|
|
491
|
+
const amazonClient = new AmazonAPIClient(config.amazon, this.logger);
|
|
492
|
+
this.channels.set('amazon', {
|
|
493
|
+
enabled: true,
|
|
494
|
+
apiClient: amazonClient as any,
|
|
495
|
+
bufferStock: config.amazon.bufferStock,
|
|
496
|
+
rateLimit: config.amazon.rateLimit,
|
|
497
|
+
});
|
|
498
|
+
this.logger.info('Amazon SP-API client initialized');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (config.ebay.enabled) {
|
|
502
|
+
const ebayClient = new eBayAPIClient(config.ebay, this.logger);
|
|
503
|
+
this.channels.set('ebay', {
|
|
504
|
+
enabled: true,
|
|
505
|
+
apiClient: ebayClient as any,
|
|
506
|
+
bufferStock: config.ebay.bufferStock,
|
|
507
|
+
rateLimit: config.ebay.rateLimit,
|
|
508
|
+
});
|
|
509
|
+
this.logger.info('eBay Trading API client initialized');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (config.walmart.enabled) {
|
|
513
|
+
const walmartClient = new WalmartAPIClient(config.walmart, this.logger);
|
|
514
|
+
this.channels.set('walmart', {
|
|
515
|
+
enabled: true,
|
|
516
|
+
apiClient: walmartClient as any,
|
|
517
|
+
bufferStock: config.walmart.bufferStock,
|
|
518
|
+
rateLimit: config.walmart.rateLimit,
|
|
519
|
+
});
|
|
520
|
+
this.logger.info('Walmart Marketplace API client initialized');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
this.logger.info(`Initialized ${this.channels.size} marketplace channels`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async syncInventory(): Promise<void> {
|
|
527
|
+
this.logger.info('Starting multi-channel inventory sync');
|
|
528
|
+
this.metrics.startTime = new Date();
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
// Step 1: Fetch all ATP inventory from Fluent Commerce
|
|
532
|
+
const inventory = await this.fetchFluentInventory();
|
|
533
|
+
this.metrics.totalSKUs = inventory.length;
|
|
534
|
+
|
|
535
|
+
this.logger.info(`Fetched ${inventory.length} inventory positions from Fluent`);
|
|
536
|
+
|
|
537
|
+
// Step 2: Calculate ATP for each channel with buffer stock
|
|
538
|
+
const channelInventory = this.calculateChannelInventory(inventory);
|
|
539
|
+
|
|
540
|
+
// Step 3: Update each marketplace in parallel with rate limiting
|
|
541
|
+
await this.updateMarketplaces(channelInventory);
|
|
542
|
+
|
|
543
|
+
this.metrics.endTime = new Date();
|
|
544
|
+
this.logMetrics();
|
|
545
|
+
|
|
546
|
+
this.logger.info('Multi-channel inventory sync completed successfully');
|
|
547
|
+
} catch (error: any) {
|
|
548
|
+
this.logger.error('Multi-channel inventory sync failed', error);
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private async fetchFluentInventory(): Promise<InventoryPosition[]> {
|
|
554
|
+
const query = `
|
|
555
|
+
query GetInventory($retailerId: ID!, $first: Int!, $after: String) {
|
|
556
|
+
inventoryQuantities(
|
|
557
|
+
first: $first
|
|
558
|
+
after: $after
|
|
559
|
+
retailerId: $retailerId
|
|
560
|
+
type: "AVAILABLE"
|
|
561
|
+
) {
|
|
562
|
+
edges {
|
|
563
|
+
node {
|
|
564
|
+
productRef
|
|
565
|
+
locationRef
|
|
566
|
+
quantity
|
|
567
|
+
onHand
|
|
568
|
+
available
|
|
569
|
+
reserved
|
|
570
|
+
}
|
|
571
|
+
cursor
|
|
572
|
+
}
|
|
573
|
+
pageInfo {
|
|
574
|
+
hasNextPage
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
`;
|
|
579
|
+
|
|
580
|
+
const result = await this.fluentClient.graphql({
|
|
581
|
+
query,
|
|
582
|
+
variables: {
|
|
583
|
+
retailerId: config.fluent.retailerId,
|
|
584
|
+
first: 250,
|
|
585
|
+
},
|
|
586
|
+
pagination: {
|
|
587
|
+
maxPages: 500,
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
if (result.errors) {
|
|
592
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const edges = (result.data as any).inventoryQuantities?.edges || [];
|
|
596
|
+
return edges.map((edge: any) => edge.node);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private calculateChannelInventory(
|
|
600
|
+
inventory: InventoryPosition[]
|
|
601
|
+
): Map<string, Map<string, number>> {
|
|
602
|
+
const channelInventory = new Map<string, Map<string, number>>();
|
|
603
|
+
|
|
604
|
+
for (const [channelName, channelConfig] of this.channels) {
|
|
605
|
+
const skuQuantities = new Map<string, number>();
|
|
606
|
+
|
|
607
|
+
for (const position of inventory) {
|
|
608
|
+
const atpQuantity = position.available || 0;
|
|
609
|
+
const channelQuantity = Math.max(0, atpQuantity - channelConfig.bufferStock);
|
|
610
|
+
skuQuantities.set(position.productRef, channelQuantity);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
channelInventory.set(channelName, skuQuantities);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return channelInventory;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private async updateMarketplaces(
|
|
620
|
+
channelInventory: Map<string, Map<string, number>>
|
|
621
|
+
): Promise<void> {
|
|
622
|
+
const updatePromises: Promise<void>[] = [];
|
|
623
|
+
|
|
624
|
+
for (const [channelName, skuQuantities] of channelInventory) {
|
|
625
|
+
const channelConfig = this.channels.get(channelName)!;
|
|
626
|
+
const limit = pLimit(channelConfig.rateLimit);
|
|
627
|
+
|
|
628
|
+
const channelPromises = Array.from(skuQuantities.entries()).map(([sku, quantity]) =>
|
|
629
|
+
limit(async () => {
|
|
630
|
+
try {
|
|
631
|
+
const startTime = Date.now();
|
|
632
|
+
|
|
633
|
+
if (channelName === 'amazon') {
|
|
634
|
+
await (channelConfig.apiClient as any).updateInventory(sku, quantity);
|
|
635
|
+
} else if (channelName === 'ebay') {
|
|
636
|
+
await (channelConfig.apiClient as any).updateInventory(sku, quantity);
|
|
637
|
+
} else if (channelName === 'walmart') {
|
|
638
|
+
await (channelConfig.apiClient as any).updateInventory(sku, quantity);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const duration = Date.now() - startTime;
|
|
642
|
+
this.metrics.successfulUpdates++;
|
|
643
|
+
|
|
644
|
+
this.logger.debug(`Updated ${channelName} inventory for ${sku}: ${quantity}`, {
|
|
645
|
+
duration,
|
|
646
|
+
});
|
|
647
|
+
} catch (error: any) {
|
|
648
|
+
this.metrics.failedUpdates++;
|
|
649
|
+
this.metrics.errors.push({
|
|
650
|
+
sku,
|
|
651
|
+
channel: channelName,
|
|
652
|
+
error: error.message,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
if (error.response?.status === 429) {
|
|
656
|
+
this.metrics.rateLimitHits++;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
this.logger.warn(`Failed to update ${channelName} inventory for ${sku}`, {
|
|
660
|
+
error: error.message,
|
|
661
|
+
status: error.response?.status,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
})
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
updatePromises.push(...channelPromises);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
await Promise.all(updatePromises);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private logMetrics(): void {
|
|
674
|
+
if (!config.processing.metricsEnabled) return;
|
|
675
|
+
|
|
676
|
+
const duration = this.metrics.endTime
|
|
677
|
+
? (this.metrics.endTime.getTime() - this.metrics.startTime.getTime()) / 1000
|
|
678
|
+
: 0;
|
|
679
|
+
|
|
680
|
+
this.logger.info('='.repeat(80));
|
|
681
|
+
this.logger.info('SYNC METRICS', {
|
|
682
|
+
duration: `${duration.toFixed(2)}s`,
|
|
683
|
+
totalSKUs: this.metrics.totalSKUs,
|
|
684
|
+
successfulUpdates: this.metrics.successfulUpdates,
|
|
685
|
+
failedUpdates: this.metrics.failedUpdates,
|
|
686
|
+
skippedUpdates: this.metrics.skippedUpdates,
|
|
687
|
+
successRate: `${((this.metrics.successfulUpdates / (this.metrics.successfulUpdates + this.metrics.failedUpdates)) * 100).toFixed(2)}%`,
|
|
688
|
+
rateLimitHits: this.metrics.rateLimitHits,
|
|
689
|
+
errorCount: this.metrics.errors.length,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
if (this.metrics.errors.length > 0) {
|
|
693
|
+
this.logger.warn('Error summary (first 10):', {
|
|
694
|
+
errors: this.metrics.errors.slice(0, 10),
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
this.logger.info('='.repeat(80));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ============================================================================
|
|
703
|
+
// MAIN ENTRY POINT
|
|
704
|
+
// ============================================================================
|
|
705
|
+
|
|
706
|
+
async function main() {
|
|
707
|
+
const sync = new MultiChannelInventorySync();
|
|
708
|
+
|
|
709
|
+
try {
|
|
710
|
+
await sync.initialize();
|
|
711
|
+
await sync.syncInventory();
|
|
712
|
+
|
|
713
|
+
console.log('Multi-channel inventory sync completed successfully');
|
|
714
|
+
process.exit(0);
|
|
715
|
+
} catch (error: any) {
|
|
716
|
+
console.error('Fatal error:', error);
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Run if executed directly
|
|
722
|
+
if (require.main === module) {
|
|
723
|
+
main();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export { MultiChannelInventorySync };
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## Key Patterns Explained
|
|
732
|
+
|
|
733
|
+
### Pattern 1: GraphQL Auto-Pagination for Large Datasets
|
|
734
|
+
|
|
735
|
+
**Challenge**: Efficiently fetch 10k-100k inventory positions without manual pagination logic
|
|
736
|
+
|
|
737
|
+
**Solution**: Use SDK's built-in auto-pagination with progress callbacks
|
|
738
|
+
|
|
739
|
+
```javascript
|
|
740
|
+
const result = await fluentClient.graphql({
|
|
741
|
+
query,
|
|
742
|
+
variables: { retailerId, first: 200 },
|
|
743
|
+
pagination: {
|
|
744
|
+
maxRecords: 100000,
|
|
745
|
+
maxPages: 500,
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Why This Works**:
|
|
751
|
+
|
|
752
|
+
- **Automatic**: No manual cursor tracking needed
|
|
753
|
+
- **Safe**: Built-in protections against infinite loops
|
|
754
|
+
- **Efficient**: Streams data as it's received
|
|
755
|
+
- **Observable**: Progress callbacks for monitoring
|
|
756
|
+
|
|
757
|
+
### Pattern 2: ATP Aggregation with Location Rollup
|
|
758
|
+
|
|
759
|
+
**Challenge**: Calculate total Available-to-Promise across multiple warehouses/stores per SKU
|
|
760
|
+
|
|
761
|
+
**Solution**: Map-based aggregation with location tracking
|
|
762
|
+
|
|
763
|
+
```javascript
|
|
764
|
+
function aggregateATP(inventoryPositions) {
|
|
765
|
+
const atpMap = new Map();
|
|
766
|
+
for (const position of inventoryPositions) {
|
|
767
|
+
const sku = position.productRef;
|
|
768
|
+
const availableQty = Math.max(0, position.availableQty || 0);
|
|
769
|
+
|
|
770
|
+
if (!atpMap.has(sku)) {
|
|
771
|
+
atpMap.set(sku, {
|
|
772
|
+
sku,
|
|
773
|
+
totalATP: 0,
|
|
774
|
+
locations: [],
|
|
775
|
+
lastUpdated: position.updatedOn,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const aggregated = atpMap.get(sku);
|
|
780
|
+
aggregated.totalATP += availableQty;
|
|
781
|
+
aggregated.locations.push({
|
|
782
|
+
locationRef: position.locationRef,
|
|
783
|
+
availableQty: availableQty,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
return atpMap;
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**Why This Works**:
|
|
791
|
+
|
|
792
|
+
- **O(n) complexity**: Single pass through inventory
|
|
793
|
+
- **Location visibility**: Track which locations contribute to total
|
|
794
|
+
- **Negative protection**: `Math.max(0, ...)` prevents negative ATP
|
|
795
|
+
- **Timestamp tracking**: Know when inventory was last updated
|
|
796
|
+
|
|
797
|
+
### Pattern 3: Marketplace-Specific Buffer Stock
|
|
798
|
+
|
|
799
|
+
**Challenge**: Different marketplaces need different safety buffers to prevent overselling
|
|
800
|
+
|
|
801
|
+
**Solution**: Configurable buffer per marketplace
|
|
802
|
+
|
|
803
|
+
```javascript
|
|
804
|
+
// Configuration
|
|
805
|
+
const buffers = {
|
|
806
|
+
amazon: 5, // Lower buffer (fast fulfillment)
|
|
807
|
+
ebay: 3, // Moderate buffer
|
|
808
|
+
walmart: 10, // Higher buffer (longer fulfillment)
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// Application
|
|
812
|
+
function applyBufferStock(totalATP, bufferStock) {
|
|
813
|
+
return Math.max(0, totalATP - bufferStock);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Example: SKU with 50 ATP across all locations
|
|
817
|
+
const amazonQty = applyBufferStock(50, 5); // 45 available on Amazon
|
|
818
|
+
const ebayQty = applyBufferStock(50, 3); // 47 available on eBay
|
|
819
|
+
const walmartQty = applyBufferStock(50, 10); // 40 available on Walmart
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
**Why This Works**:
|
|
823
|
+
|
|
824
|
+
- **Oversell prevention**: Accounts for sync delays and processing time
|
|
825
|
+
- **Marketplace flexibility**: Different risk profiles per marketplace
|
|
826
|
+
- **Safety floor**: `Math.max(0, ...)` ensures quantity never goes negative
|
|
827
|
+
- **Configurable**: Easy to adjust per marketplace requirements
|
|
828
|
+
|
|
829
|
+
### Pattern 4: Parallel Marketplace Updates with Rate Limiting
|
|
830
|
+
|
|
831
|
+
**Challenge**: Update multiple marketplaces efficiently without exceeding API rate limits
|
|
832
|
+
|
|
833
|
+
**Solution**: Parallel execution with per-marketplace rate limiters
|
|
834
|
+
|
|
835
|
+
```javascript
|
|
836
|
+
import pLimit from 'p-limit';
|
|
837
|
+
|
|
838
|
+
// Each marketplace has its own rate limiter
|
|
839
|
+
class AmazonMarketplace {
|
|
840
|
+
constructor(config) {
|
|
841
|
+
this.limiter = pLimit(config.rateLimit); // 10 req/sec
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
async updateInventory(sku, quantity) {
|
|
845
|
+
// Rate-limited execution
|
|
846
|
+
return this.limiter(() => this.makeApiCall(sku, quantity));
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Parallel sync across all marketplaces
|
|
851
|
+
const syncResults = await Promise.all(
|
|
852
|
+
marketplaces.map(marketplace => marketplace.syncInventory(atpMap))
|
|
853
|
+
);
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
**Why This Works**:
|
|
857
|
+
|
|
858
|
+
- **Parallel execution**: All marketplaces sync simultaneously
|
|
859
|
+
- **Independent rate limits**: Each marketplace respects its own limits
|
|
860
|
+
- **Error isolation**: Failure in one marketplace doesn't affect others
|
|
861
|
+
- **Concurrency control**: p-limit prevents overwhelming APIs
|
|
862
|
+
|
|
863
|
+
### Pattern 5: Exponential Backoff Retry Strategy
|
|
864
|
+
|
|
865
|
+
**Challenge**: Handle transient API failures (429 rate limits, 500 server errors)
|
|
866
|
+
|
|
867
|
+
**Solution**: p-retry with exponential backoff
|
|
868
|
+
|
|
869
|
+
```javascript
|
|
870
|
+
import pRetry from 'p-retry';
|
|
871
|
+
|
|
872
|
+
async updateInventory(sku, quantity) {
|
|
873
|
+
return pRetry(
|
|
874
|
+
async () => {
|
|
875
|
+
// Attempt API call
|
|
876
|
+
const response = await axios.put(endpoint, payload);
|
|
877
|
+
|
|
878
|
+
// Check for retryable errors
|
|
879
|
+
if (response.status === 429) {
|
|
880
|
+
throw new Error('Rate limit exceeded'); // Will retry
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return response.data;
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
retries: 3,
|
|
887
|
+
factor: 2, // Exponential backoff factor
|
|
888
|
+
minTimeout: 1000, // 1s, 2s, 4s delays
|
|
889
|
+
onFailedAttempt: error => {
|
|
890
|
+
logger.warn(`Retry attempt ${error.attemptNumber}`, {
|
|
891
|
+
retriesLeft: error.retriesLeft,
|
|
892
|
+
});
|
|
893
|
+
},
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
**Why This Works**:
|
|
900
|
+
|
|
901
|
+
- **Transient failure recovery**: Handles temporary API issues
|
|
902
|
+
- **Exponential backoff**: Reduces load on overloaded APIs
|
|
903
|
+
- **Configurable retries**: 3 attempts is reasonable for most cases
|
|
904
|
+
- **Observable**: Logs each retry attempt for debugging
|
|
905
|
+
|
|
906
|
+
### Pattern 6: SLA Tracking and Compliance
|
|
907
|
+
|
|
908
|
+
**Challenge**: Ensure sync completes within 15-minute SLA requirement
|
|
909
|
+
|
|
910
|
+
**Solution**: Duration tracking with SLA compliance check
|
|
911
|
+
|
|
912
|
+
```javascript
|
|
913
|
+
function checkSLA(durationMs) {
|
|
914
|
+
const durationMinutes = durationMs / 60000;
|
|
915
|
+
const slaThreshold = 15; // 15-minute requirement
|
|
916
|
+
const slaMet = durationMinutes <= slaThreshold;
|
|
917
|
+
|
|
918
|
+
if (!slaMet) {
|
|
919
|
+
logger.warn('SLA BREACH: Sync exceeded threshold', {
|
|
920
|
+
durationMinutes: durationMinutes.toFixed(2),
|
|
921
|
+
threshold: slaThreshold,
|
|
922
|
+
});
|
|
923
|
+
metrics.increment('sla_breaches');
|
|
924
|
+
// Alert via PagerDuty, Slack, etc.
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return slaMet;
|
|
928
|
+
}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
**Why This Works**:
|
|
932
|
+
|
|
933
|
+
- **Clear threshold**: 15 minutes is measurable and actionable
|
|
934
|
+
- **Automated detection**: No manual checking needed
|
|
935
|
+
- **Alerting hook**: Can trigger notifications on breach
|
|
936
|
+
- **Metrics tracking**: Historical SLA compliance data
|
|
937
|
+
|
|
938
|
+
### Pattern 7: Dry-Run Mode for Safe Testing
|
|
939
|
+
|
|
940
|
+
**Challenge**: Test sync logic without actually updating marketplaces
|
|
941
|
+
|
|
942
|
+
**Solution**: Dry-run flag that short-circuits API calls
|
|
943
|
+
|
|
944
|
+
```javascript
|
|
945
|
+
async updateInventory(sku, quantity, dryRun = false) {
|
|
946
|
+
if (dryRun) {
|
|
947
|
+
this.logger.info(`[DRY RUN] Would update inventory`, { sku, quantity });
|
|
948
|
+
return { success: true, sku, quantity, dryRun: true };
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Real API call
|
|
952
|
+
return this.makeApiCall(sku, quantity);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Usage
|
|
956
|
+
npm run sync:dry-run
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
**Why This Works**:
|
|
960
|
+
|
|
961
|
+
- **Safe testing**: Validate logic without side effects
|
|
962
|
+
- **End-to-end flow**: Tests everything except final API call
|
|
963
|
+
- **Easy toggle**: Environment variable or CLI flag
|
|
964
|
+
- **Visible logging**: Clear indication of dry-run mode
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
## Testing
|
|
969
|
+
|
|
970
|
+
### Dry-Run Test
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
# Test sync without updating marketplaces
|
|
974
|
+
DRY_RUN=true npm run sync:once
|
|
975
|
+
|
|
976
|
+
# Expected output:
|
|
977
|
+
# [DRY RUN] Would update Amazon inventory { sku: 'PROD-001', quantity: 45 }
|
|
978
|
+
# [DRY RUN] Would update eBay inventory { sku: 'PROD-001', quantity: 47 }
|
|
979
|
+
# [DRY RUN] Would update Walmart inventory { sku: 'PROD-001', quantity: 40 }
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Single Sync Execution
|
|
983
|
+
|
|
984
|
+
```bash
|
|
985
|
+
# Run once and exit
|
|
986
|
+
npm run sync:once
|
|
987
|
+
|
|
988
|
+
# Check logs
|
|
989
|
+
cat logs/inventory-sync.log | grep "COMPLETE"
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### Continuous Sync Mode
|
|
993
|
+
|
|
994
|
+
```bash
|
|
995
|
+
# Run continuously (every 15 minutes)
|
|
996
|
+
npm run sync:continuous
|
|
997
|
+
|
|
998
|
+
# Check next sync time in logs
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
## Deployment Options
|
|
1004
|
+
|
|
1005
|
+
### Option 1: Docker Container
|
|
1006
|
+
|
|
1007
|
+
Create `Dockerfile`:
|
|
1008
|
+
|
|
1009
|
+
```dockerfile
|
|
1010
|
+
FROM node:18-alpine
|
|
1011
|
+
|
|
1012
|
+
WORKDIR /app
|
|
1013
|
+
|
|
1014
|
+
# Copy package files
|
|
1015
|
+
COPY package*.json ./
|
|
1016
|
+
|
|
1017
|
+
# Install dependencies
|
|
1018
|
+
RUN npm ci --only=production
|
|
1019
|
+
|
|
1020
|
+
# Copy application code
|
|
1021
|
+
COPY src ./src
|
|
1022
|
+
|
|
1023
|
+
# Create state directory
|
|
1024
|
+
RUN mkdir -p /app/state
|
|
1025
|
+
|
|
1026
|
+
# Run continuous sync
|
|
1027
|
+
CMD ["node", "src/index.js"]
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
Build and run:
|
|
1031
|
+
|
|
1032
|
+
```bash
|
|
1033
|
+
# Build image
|
|
1034
|
+
docker build -t multi-channel-sync .
|
|
1035
|
+
|
|
1036
|
+
# Run container
|
|
1037
|
+
docker run -d \
|
|
1038
|
+
--name inventory-sync \
|
|
1039
|
+
--env-file .env \
|
|
1040
|
+
--restart unless-stopped \
|
|
1041
|
+
--volume ./state:/app/state \
|
|
1042
|
+
multi-channel-sync
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
### Option 2: AWS ECS Task
|
|
1046
|
+
|
|
1047
|
+
Create `ecs-task-definition.json`:
|
|
1048
|
+
|
|
1049
|
+
```json
|
|
1050
|
+
{
|
|
1051
|
+
"family": "multi-channel-inventory-sync",
|
|
1052
|
+
"networkMode": "awsvpc",
|
|
1053
|
+
"requiresCompatibilities": ["FARGATE"],
|
|
1054
|
+
"cpu": "1024",
|
|
1055
|
+
"memory": "2048",
|
|
1056
|
+
"containerDefinitions": [
|
|
1057
|
+
{
|
|
1058
|
+
"name": "inventory-sync",
|
|
1059
|
+
"image": "your-registry/multi-channel-sync:latest",
|
|
1060
|
+
"essential": true,
|
|
1061
|
+
"environment": [
|
|
1062
|
+
{ "name": "LOG_LEVEL", "value": "info" },
|
|
1063
|
+
{ "name": "SYNC_INTERVAL_MINUTES", "value": "15" }
|
|
1064
|
+
],
|
|
1065
|
+
"secrets": [
|
|
1066
|
+
{
|
|
1067
|
+
"name": "FLUENT_CLIENT_ID",
|
|
1068
|
+
"valueFrom": "arn:aws:secretsmanager:us-east-1:123:secret:fluent-client-id"
|
|
1069
|
+
}
|
|
1070
|
+
],
|
|
1071
|
+
"logConfiguration": {
|
|
1072
|
+
"logDriver": "awslogs",
|
|
1073
|
+
"options": {
|
|
1074
|
+
"awslogs-group": "/ecs/inventory-sync",
|
|
1075
|
+
"awslogs-region": "us-east-1",
|
|
1076
|
+
"awslogs-stream-prefix": "ecs"
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
]
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
Deploy:
|
|
1085
|
+
|
|
1086
|
+
```bash
|
|
1087
|
+
aws ecs register-task-definition --cli-input-json file://ecs-task-definition.json
|
|
1088
|
+
aws ecs create-service \
|
|
1089
|
+
--cluster production \
|
|
1090
|
+
--service-name inventory-sync \
|
|
1091
|
+
--task-definition multi-channel-inventory-sync \
|
|
1092
|
+
--desired-count 1 \
|
|
1093
|
+
--launch-type FARGATE
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
---
|
|
1097
|
+
|
|
1098
|
+
## Common Issues & Solutions
|
|
1099
|
+
|
|
1100
|
+
### Issue 1: SLA Breach (Sync Takes >15 Minutes)
|
|
1101
|
+
|
|
1102
|
+
**Symptoms**:
|
|
1103
|
+
|
|
1104
|
+
- Log shows "SLA BREACH" warning
|
|
1105
|
+
- Sync duration exceeds 15 minutes
|
|
1106
|
+
|
|
1107
|
+
**Solutions**:
|
|
1108
|
+
|
|
1109
|
+
1. **Reduce page size** (faster initial response):
|
|
1110
|
+
|
|
1111
|
+
```javascript
|
|
1112
|
+
variables: {
|
|
1113
|
+
first: 100;
|
|
1114
|
+
} // Instead of 200
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
2. **Increase parallelism**:
|
|
1118
|
+
|
|
1119
|
+
```javascript
|
|
1120
|
+
const limiter = pLimit(20); // Increase from 10
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
3. **Optimize marketplace rate limits** (if APIs allow):
|
|
1124
|
+
|
|
1125
|
+
```bash
|
|
1126
|
+
AMAZON_RATE_LIMIT=20 # Increase from 10
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
4. **Split sync by marketplace** (run separately):
|
|
1130
|
+
|
|
1131
|
+
```bash
|
|
1132
|
+
AMAZON_ENABLED=true EBAY_ENABLED=false WALMART_ENABLED=false npm run sync:once
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
## Related Guides
|
|
1138
|
+
|
|
1139
|
+
- **GraphQL Query Export**: [`./graphql-query-export.md`](./graphql-query-export.md) - GraphQL pagination patterns
|
|
1140
|
+
- **Multi-Source Aggregation**: `./04-multi-source-aggregation.md` - Data aggregation strategies
|
|
1141
|
+
- **Error Handling Patterns**: `../../03-PATTERN-GUIDES/error-handling/readme.md` - Retry and error handling
|
|
1142
|
+
- **Universal Mapping Guide**: `../../02-CORE-GUIDES/mapping/readme.md` - Field transformations
|
|
1143
|
+
- **SDK API Reference**: `../../readme.md` - Core SDK documentation
|
|
1144
|
+
|
|
1145
|
+
---
|
|
1146
|
+
|
|
1147
|
+
## Production Checklist
|
|
1148
|
+
|
|
1149
|
+
Before deploying to production:
|
|
1150
|
+
|
|
1151
|
+
- [ ] All marketplace credentials secured (AWS Secrets Manager, Vault)
|
|
1152
|
+
- [ ] Rate limits configured per marketplace API documentation
|
|
1153
|
+
- [ ] Buffer stock values tested and approved by business
|
|
1154
|
+
- [ ] SLA threshold aligned with business requirements
|
|
1155
|
+
- [ ] Monitoring dashboards created (sync duration, SLA compliance, error rates)
|
|
1156
|
+
- [ ] Alerting configured (PagerDuty, Slack, email)
|
|
1157
|
+
- [ ] Dry-run testing completed with production data
|
|
1158
|
+
- [ ] Load testing with 100k+ SKUs
|
|
1159
|
+
- [ ] State management enabled to prevent duplicate processing
|
|
1160
|
+
- [ ] Logging configured (CloudWatch, Datadog, Splunk)
|
|
1161
|
+
- [ ] Error tracking integrated (Sentry, Rollbar)
|
|
1162
|
+
- [ ] Documentation updated with runbooks
|
|
1163
|
+
- [ ] On-call team trained on troubleshooting
|
|
1164
|
+
- [ ] Disaster recovery plan documented
|
|
1165
|
+
- [ ] Metrics baseline established (average sync duration, success rate)
|
|
1166
|
+
|
|
1167
|
+
---
|
|
1168
|
+
|
|
1169
|
+
**Next Steps**:
|
|
1170
|
+
|
|
1171
|
+
1. Customize buffer stock values per marketplace and product category
|
|
1172
|
+
2. Implement incremental sync (only process changed inventory)
|
|
1173
|
+
3. Add webhook integration for real-time inventory updates
|
|
1174
|
+
4. Set up monitoring dashboards (Grafana, Datadog)
|
|
1175
|
+
5. Integrate with incident management (PagerDuty)
|
|
1176
|
+
6. Add marketplace-specific SKU mapping (Fluent SKU → Marketplace SKU)
|
|
1177
|
+
7. Implement A/B testing for buffer stock optimization
|
|
1178
|
+
|
|
1179
|
+
**Support**:
|
|
1180
|
+
|
|
1181
|
+
- SDK Issues: https://github.com/fluentcommerce/fc-connect-sdk/issues
|
|
1182
|
+
- Fluent Commerce API: https://docs.fluentcommerce.com
|
|
1183
|
+
- Amazon SP-API: https://developer-docs.amazon.com/sp-api/
|
|
1184
|
+
- eBay Trading API: https://developer.ebay.com/devzone/xml/docs/reference/ebay/
|
|
1185
|
+
- Walmart Seller API: https://developer.walmart.com/
|