@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -2
- package/README.md +39 -0
- package/dist/cjs/auth/index.d.ts +3 -0
- package/dist/cjs/auth/index.js +13 -0
- package/dist/cjs/auth/profile-loader.d.ts +18 -0
- package/dist/cjs/auth/profile-loader.js +208 -0
- package/dist/cjs/client-factory.d.ts +4 -0
- package/dist/cjs/client-factory.js +10 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/auth/index.d.ts +3 -0
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/profile-loader.d.ts +18 -0
- package/dist/esm/auth/profile-loader.js +169 -0
- package/dist/esm/client-factory.d.ts +4 -0
- package/dist/esm/client-factory.js +9 -0
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -1
- 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/dist/types/auth/index.d.ts +3 -0
- package/dist/types/auth/profile-loader.d.ts +18 -0
- package/dist/types/client-factory.d.ts +4 -0
- package/dist/types/index.d.ts +3 -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 -482
- 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
|
@@ -1,1295 +1,1295 @@
|
|
|
1
|
-
# Module 6: Batch API Deep Dive
|
|
2
|
-
|
|
3
|
-
[← Back to Ingestion Guide](../ingestion-readme.md)
|
|
4
|
-
|
|
5
|
-
**Module 6 of 9** | **Level**: Intermediate | **Time**: 30 minutes
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
This module provides complete coverage of the Fluent Commerce Batch API, including job lifecycle, batch operations, status monitoring, and optimization strategies.
|
|
12
|
-
|
|
13
|
-
## Learning Objectives
|
|
14
|
-
|
|
15
|
-
By the end of this module, you will:
|
|
16
|
-
|
|
17
|
-
- ✅ Understand the complete Batch API schema
|
|
18
|
-
- ✅ Master the job creation and management lifecycle
|
|
19
|
-
- ✅ Implement batch size optimization strategies
|
|
20
|
-
- ✅ Monitor job and batch status effectively
|
|
21
|
-
- ✅ Understand job strategies (DAILY vs PER_FILE)
|
|
22
|
-
- ✅ Handle job expiration and recovery
|
|
23
|
-
- ✅ Implement production-ready batch processing
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Batch API Schema
|
|
28
|
-
|
|
29
|
-
### Complete GraphQL Schema
|
|
30
|
-
|
|
31
|
-
The Batch API consists of two main mutations:
|
|
32
|
-
|
|
33
|
-
#### 1. `createJob` Mutation
|
|
34
|
-
|
|
35
|
-
```graphql
|
|
36
|
-
mutation CreateJob($input: CreateJobInput!) {
|
|
37
|
-
createJob(input: $input) {
|
|
38
|
-
id # Job ID (String!)
|
|
39
|
-
name # Job name (String)
|
|
40
|
-
status # Job status (JobStatus enum)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Input Schema** (`CreateJobInput`):
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
{
|
|
49
|
-
"name": "string", // ✅ Job name (String! required)
|
|
50
|
-
"retailerId": "string", // ✅ Retailer ID (ID! required)
|
|
51
|
-
"metadata": { // ✅ JSON (optional) - Custom metadata
|
|
52
|
-
"source": "S3",
|
|
53
|
-
"file": "inventory-2025-01-19.csv"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
#### 2. `sendBatch` Mutation
|
|
59
|
-
|
|
60
|
-
```graphql
|
|
61
|
-
mutation SendBatch($input: BatchInput!) {
|
|
62
|
-
sendBatch(input: $input) {
|
|
63
|
-
id # Batch ID (String!)
|
|
64
|
-
status # Batch status (BatchStatus enum)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Input Schema** (`BatchInput`):
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
{
|
|
73
|
-
"action": "UPSERT", // ✅ String (UPSERT only - required)
|
|
74
|
-
"entityType": "INVENTORY", // ✅ String (INVENTORY only - required)
|
|
75
|
-
"source": "S3_CSV_IMPORT", // ✅ String (required) - Source system identifier
|
|
76
|
-
"event": "INVENTORY_UPDATE", // ✅ String (required) - Event type identifier
|
|
77
|
-
"retailerId": "1", // ✅ ID! (required)
|
|
78
|
-
"entities": [ // ✅ [InventoryPositionInput]! (required array)
|
|
79
|
-
{
|
|
80
|
-
"ref": "SKU-WM-001", // ✅ String! (required)
|
|
81
|
-
"productRef": "SKU-WM-001", // ✅ String! (required)
|
|
82
|
-
"locationRef": "WH-001", // ✅ String! (required)
|
|
83
|
-
"qty": 500, // ✅ Int! (required)
|
|
84
|
-
"status": "AVAILABLE" // ✅ String (optional)
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### `InventoryPositionInput` Complete Schema
|
|
91
|
-
|
|
92
|
-
From `docs/schema/fluent-commerce-schema.json`:
|
|
93
|
-
|
|
94
|
-
```json
|
|
95
|
-
{
|
|
96
|
-
"InventoryPositionInput": {
|
|
97
|
-
"kind": "INPUT_OBJECT",
|
|
98
|
-
"description": "Input for inventory position in batch",
|
|
99
|
-
"fields": {
|
|
100
|
-
"ref": {
|
|
101
|
-
"type": "String!",
|
|
102
|
-
"description": "Position reference (Required)",
|
|
103
|
-
"businessContext": "Unique identifier for this inventory position, typically SKU-LOCATION combination",
|
|
104
|
-
"example": "SKU-WM-001"
|
|
105
|
-
},
|
|
106
|
-
"productRef": {
|
|
107
|
-
"type": "String!",
|
|
108
|
-
"description": "Product reference (Required)",
|
|
109
|
-
"businessContext": "Product SKU that this inventory position represents",
|
|
110
|
-
"example": "SKU-WM-001"
|
|
111
|
-
},
|
|
112
|
-
"locationRef": {
|
|
113
|
-
"type": "String!",
|
|
114
|
-
"description": "Location reference (Required)",
|
|
115
|
-
"businessContext": "Warehouse or location code where inventory is held",
|
|
116
|
-
"example": "WH-001"
|
|
117
|
-
},
|
|
118
|
-
"qty": {
|
|
119
|
-
"type": "Int!",
|
|
120
|
-
"description": "Quantity (Required)",
|
|
121
|
-
"businessContext": "Available quantity at this location",
|
|
122
|
-
"example": 500,
|
|
123
|
-
"validationRules": ["qty >= 0"]
|
|
124
|
-
},
|
|
125
|
-
"status": {
|
|
126
|
-
"type": "String",
|
|
127
|
-
"description": "Status (Optional)",
|
|
128
|
-
"businessContext": "Inventory position status",
|
|
129
|
-
"example": "AVAILABLE",
|
|
130
|
-
"validValues": ["AVAILABLE", "RESERVED", "ON_HOLD", "DISCONTINUED"]
|
|
131
|
-
},
|
|
132
|
-
"type": {
|
|
133
|
-
"type": "String",
|
|
134
|
-
"description": "Position type (Optional)",
|
|
135
|
-
"businessContext": "Type of inventory adjustment",
|
|
136
|
-
"example": "ADJUSTMENT"
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## Complete Job Lifecycle
|
|
146
|
-
|
|
147
|
-
### Lifecycle Diagram
|
|
148
|
-
|
|
149
|
-
```mermaid
|
|
150
|
-
graph TD
|
|
151
|
-
A[Create Job] --> B{Job Created?}
|
|
152
|
-
B -->|Yes| C[Job PENDING]
|
|
153
|
-
B -->|No| Z[ERROR]
|
|
154
|
-
|
|
155
|
-
C --> D[Send Batch 1]
|
|
156
|
-
D --> E[Batch PROCESSING]
|
|
157
|
-
|
|
158
|
-
C --> F[Send Batch 2]
|
|
159
|
-
F --> G[Batch PROCESSING]
|
|
160
|
-
|
|
161
|
-
E --> H{Batch Complete?}
|
|
162
|
-
G --> I{Batch Complete?}
|
|
163
|
-
|
|
164
|
-
H -->|Success| J[Batch COMPLETED]
|
|
165
|
-
H -->|Failure| K[Batch FAILED]
|
|
166
|
-
|
|
167
|
-
I -->|Success| L[Batch COMPLETED]
|
|
168
|
-
I -->|Failure| M[Batch FAILED]
|
|
169
|
-
|
|
170
|
-
J --> N{More Batches?}
|
|
171
|
-
L --> N
|
|
172
|
-
|
|
173
|
-
N -->|Yes| O[Send Next Batch]
|
|
174
|
-
N -->|No| P[All Batches Sent]
|
|
175
|
-
|
|
176
|
-
O --> E
|
|
177
|
-
|
|
178
|
-
P --> Q{Job Expired?}
|
|
179
|
-
Q -->|No| R[Job COMPLETED]
|
|
180
|
-
Q -->|Yes| S[Job EXPIRED - Create New Job]
|
|
181
|
-
|
|
182
|
-
K --> T[Handle Failed Batch]
|
|
183
|
-
M --> T
|
|
184
|
-
T --> U[Retry or Log Error]
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Step 1: Create Job
|
|
188
|
-
|
|
189
|
-
**Simple job creation:**
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
193
|
-
|
|
194
|
-
const client = await createClient({ config });
|
|
195
|
-
|
|
196
|
-
const job = await client.createJob({
|
|
197
|
-
name: 'Daily Inventory Sync',
|
|
198
|
-
retailerId: '1',
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
console.log(`Job created: ${job.id}`);
|
|
202
|
-
// Output: Job created: JOB-12345
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
**Job creation with metadata (for tracking):**
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
const job = await client.createJob({
|
|
209
|
-
name: `Inventory Import - ${new Date().toISOString()}`,
|
|
210
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
211
|
-
metadata: {
|
|
212
|
-
source: 'S3',
|
|
213
|
-
bucket: 'inventory-bucket',
|
|
214
|
-
file: 'data/inventory-2025-01-19.csv',
|
|
215
|
-
uploadedBy: 'integration-service',
|
|
216
|
-
triggeredBy: 'scheduled-workflow',
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Step 2: Send Batches
|
|
222
|
-
|
|
223
|
-
**Single batch:**
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
const batch = await client.sendBatch(job.id, {
|
|
227
|
-
action: 'UPSERT', // String, not enum
|
|
228
|
-
entityType: 'INVENTORY', // String, not enum
|
|
229
|
-
source: 'S3_CSV_IMPORT', // REQUIRED - Source system identifier
|
|
230
|
-
event: 'INVENTORY_UPDATE', // REQUIRED - Event type identifier
|
|
231
|
-
entities: [
|
|
232
|
-
{
|
|
233
|
-
ref: 'SKU-WM-001',
|
|
234
|
-
productRef: 'SKU-WM-001',
|
|
235
|
-
locationRef: 'WH-001',
|
|
236
|
-
qty: 500,
|
|
237
|
-
status: 'AVAILABLE',
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
console.log(`Batch sent: ${batch.id}`);
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
**Multiple batches (chunked):**
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
function chunkArray<T>(array: T[], chunkSize: number): T[][] {
|
|
249
|
-
const chunks: T[][] = [];
|
|
250
|
-
for (let i = 0; i < array.length; i += chunkSize) {
|
|
251
|
-
chunks.push(array.slice(i, i + chunkSize));
|
|
252
|
-
}
|
|
253
|
-
return chunks;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Split large dataset into batches
|
|
257
|
-
const batchSize = 1000;
|
|
258
|
-
const batches = chunkArray(inventoryData, batchSize);
|
|
259
|
-
|
|
260
|
-
const batchResults = [];
|
|
261
|
-
|
|
262
|
-
for (const [index, batchData] of batches.entries()) {
|
|
263
|
-
console.log(`Sending batch ${index + 1}/${batches.length}...`);
|
|
264
|
-
|
|
265
|
-
const batch = await client.sendBatch(job.id, {
|
|
266
|
-
action: 'UPSERT',
|
|
267
|
-
entityType: 'INVENTORY',
|
|
268
|
-
source: 'S3_CSV_IMPORT',
|
|
269
|
-
event: 'INVENTORY_UPDATE',
|
|
270
|
-
entities: batchData,
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
batchResults.push({
|
|
274
|
-
index,
|
|
275
|
-
batchId: batch.id,
|
|
276
|
-
records: batchData.length,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
console.log(`Sent ${batchResults.length} batches with ${inventoryData.length} total records`);
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### Step 3: Monitor Status
|
|
284
|
-
|
|
285
|
-
**Poll batch status:**
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
async function waitForBatchCompletion(
|
|
289
|
-
client: FluentClient,
|
|
290
|
-
jobId: string,
|
|
291
|
-
batchId: string,
|
|
292
|
-
pollInterval: number = 5000,
|
|
293
|
-
timeout: number = 300000
|
|
294
|
-
): Promise<BatchStatus> {
|
|
295
|
-
const startTime = Date.now();
|
|
296
|
-
|
|
297
|
-
while (Date.now() - startTime < timeout) {
|
|
298
|
-
const status = await client.getBatchStatus(jobId, batchId);
|
|
299
|
-
|
|
300
|
-
console.log(`Batch ${batchId}: ${status.status}`);
|
|
301
|
-
|
|
302
|
-
if (status.status === 'COMPLETED') {
|
|
303
|
-
return status;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (status.status === 'FAILED') {
|
|
307
|
-
throw new Error(`Batch failed: ${JSON.stringify(status.errors)}`);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Wait before next poll
|
|
311
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
throw new Error(`Batch monitoring timeout after ${timeout}ms`);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Usage
|
|
318
|
-
const status = await waitForBatchCompletion(client, job.id, batch.id);
|
|
319
|
-
console.log(`✅ Batch completed successfully`);
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Monitor job status (all batches):**
|
|
323
|
-
|
|
324
|
-
```typescript
|
|
325
|
-
async function monitorJobProgress(
|
|
326
|
-
client: FluentClient,
|
|
327
|
-
jobId: string,
|
|
328
|
-
onProgress?: (status: JobStatus) => void
|
|
329
|
-
): Promise<JobStatus> {
|
|
330
|
-
const pollInterval = 5000; // 5 seconds
|
|
331
|
-
const timeout = 600000; // 10 minutes
|
|
332
|
-
|
|
333
|
-
const startTime = Date.now();
|
|
334
|
-
|
|
335
|
-
while (Date.now() - startTime < timeout) {
|
|
336
|
-
const status = await client.getJobStatus(jobId);
|
|
337
|
-
|
|
338
|
-
if (onProgress) {
|
|
339
|
-
onProgress(status);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (status.status === 'COMPLETED' || status.status === 'FAILED') {
|
|
343
|
-
return status;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
throw new Error(`Job monitoring timeout: ${jobId}`);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Usage with progress callback
|
|
353
|
-
const jobStatus = await monitorJobProgress(client, job.id, status => {
|
|
354
|
-
console.log(`Job ${job.id}: ${status.status} - ${status.progress || 0}% complete`);
|
|
355
|
-
});
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
---
|
|
359
|
-
|
|
360
|
-
## Batch Pre-Processing (BPP) - Change Detection
|
|
361
|
-
|
|
362
|
-
### Overview
|
|
363
|
-
|
|
364
|
-
**Batch Pre-Processing (BPP)** is Fluent Commerce's built-in change detection system that **filters out unchanged inventory records** before they reach the workflow engine, significantly improving performance when sending full inventory snapshots.
|
|
365
|
-
|
|
366
|
-
### How BPP Works
|
|
367
|
-
|
|
368
|
-
BPP introduces two internal jobs that run **before** your inventory updates hit the workflow:
|
|
369
|
-
|
|
370
|
-
```
|
|
371
|
-
┌─────────────────────────────────────────────────────┐
|
|
372
|
-
│ WITHOUT BPP (meta.preprocessing: "skip") │
|
|
373
|
-
│ ──────────────────────────────────────────────── │
|
|
374
|
-
│ Batch API → ALL records → Inventory Queue → Rubix │
|
|
375
|
-
│ 100K records → 100K workflow events │
|
|
376
|
-
└─────────────────────────────────────────────────────┘
|
|
377
|
-
|
|
378
|
-
┌──────────────────────────────────────────────────────────┐
|
|
379
|
-
│ WITH BPP (default) │
|
|
380
|
-
│ ───────────────────────────────────────────────────── │
|
|
381
|
-
│ Batch API → Loader Job → Comparison Job → │
|
|
382
|
-
│ → ONLY CHANGED records → Inventory Queue → Rubix │
|
|
383
|
-
│ 100K records → 5K workflow events (95% filtered) │
|
|
384
|
-
└──────────────────────────────────────────────────────────┘
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
#### BPP Components
|
|
388
|
-
|
|
389
|
-
**1. Loader Job**
|
|
390
|
-
|
|
391
|
-
- Loads existing inventory data from database
|
|
392
|
-
- Includes current quantities, statuses, and transient inventory
|
|
393
|
-
|
|
394
|
-
**2. Comparison Job**
|
|
395
|
-
|
|
396
|
-
- Compares batch records vs existing data
|
|
397
|
-
- Checks for changes in:
|
|
398
|
-
- **Quantity**: Has qty changed?
|
|
399
|
-
- **Status**: Has status changed?
|
|
400
|
-
- **Transient Inventory**: Are there active transient quantities?
|
|
401
|
-
|
|
402
|
-
**3. Result**
|
|
403
|
-
|
|
404
|
-
- Only records marked as "changed" proceed to workflow
|
|
405
|
-
- Unchanged records are filtered out (not processed)
|
|
406
|
-
|
|
407
|
-
### When to Use BPP
|
|
408
|
-
|
|
409
|
-
#### ✅ Use BPP (Default) When:
|
|
410
|
-
|
|
411
|
-
**Scenario 1: Full Inventory Snapshots**
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
// Sending complete inventory file daily
|
|
415
|
-
const job = await client.createJob({
|
|
416
|
-
name: 'Daily Full Inventory Sync',
|
|
417
|
-
retailerId: '1',
|
|
418
|
-
// BPP enabled by default (no meta.preprocessing)
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// 100K records sent, only ~5K changed overnight
|
|
422
|
-
// Result: 5K workflow events instead of 100K
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
**Scenario 2: Unchanged Records in Data Feed**
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
// WMS sends all SKU-location combinations daily
|
|
429
|
-
// Many haven't changed since yesterday
|
|
430
|
-
const job = await client.createJob({
|
|
431
|
-
name: 'WMS Daily Extract',
|
|
432
|
-
retailerId: '1',
|
|
433
|
-
meta: {
|
|
434
|
-
preprocessing: 'enabled', // Explicit BPP enable
|
|
435
|
-
source: 'WMS',
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
**Benefits:**
|
|
441
|
-
|
|
442
|
-
- ✅ Reduces workflow engine load by 80-95%
|
|
443
|
-
- ✅ Faster processing (only changed records)
|
|
444
|
-
- ✅ Automatic duplicate detection
|
|
445
|
-
- ✅ Lower infrastructure costs
|
|
446
|
-
|
|
447
|
-
#### ❌ Skip BPP When:
|
|
448
|
-
|
|
449
|
-
**Scenario 1: Delta/Change-Only Feeds**
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
// Your application already filtered changed records
|
|
453
|
-
// Sending ONLY records that changed
|
|
454
|
-
const job = await client.createJob({
|
|
455
|
-
name: 'Real-Time Inventory Delta',
|
|
456
|
-
retailerId: '1',
|
|
457
|
-
meta: {
|
|
458
|
-
preprocessing: 'skip', // ← Skip BPP (every record is already changed)
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// 100 records sent, all 100 are changes
|
|
463
|
-
// Result: 100 workflow events (no filtering needed)
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
**Scenario 2: Real-Time Updates**
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
// High-frequency updates where every record matters
|
|
470
|
-
// e.g., Point-of-sale inventory decrements
|
|
471
|
-
const job = await client.createJob({
|
|
472
|
-
name: 'POS Inventory Update',
|
|
473
|
-
retailerId: '1',
|
|
474
|
-
meta: {
|
|
475
|
-
preprocessing: 'skip',
|
|
476
|
-
source: 'POS',
|
|
477
|
-
realtime: true,
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
**Scenario 3: Performance-Critical Imports**
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
// Need absolute fastest processing
|
|
486
|
-
// Data is guaranteed to be different
|
|
487
|
-
const job = await client.createJob({
|
|
488
|
-
name: 'Critical Inventory Adjustment',
|
|
489
|
-
retailerId: '1',
|
|
490
|
-
meta: {
|
|
491
|
-
preprocessing: 'skip',
|
|
492
|
-
priority: 'high',
|
|
493
|
-
},
|
|
494
|
-
});
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
**Benefits:**
|
|
498
|
-
|
|
499
|
-
- ✅ Faster (no comparison overhead)
|
|
500
|
-
- ✅ No need to load existing data
|
|
501
|
-
- ✅ Simpler processing flow
|
|
502
|
-
|
|
503
|
-
### Account-Level BPP Configuration
|
|
504
|
-
|
|
505
|
-
BPP behavior is controlled by **two settings**:
|
|
506
|
-
|
|
507
|
-
#### 1. Account Setting: `fc.enable.batch.preprocessing`
|
|
508
|
-
|
|
509
|
-
This is configured at the **Fluent Commerce account level** (contact Fluent support to check/modify):
|
|
510
|
-
|
|
511
|
-
```typescript
|
|
512
|
-
// Pseudo-code showing the logic
|
|
513
|
-
if (account.settings.fc_enable_batch_preprocessing === false) {
|
|
514
|
-
// BPP is NEVER used, regardless of job meta
|
|
515
|
-
processingMode = 'DIRECT';
|
|
516
|
-
} else {
|
|
517
|
-
// BPP is available, check job-level meta
|
|
518
|
-
if (job.meta.preprocessing === 'skip') {
|
|
519
|
-
processingMode = 'DIRECT';
|
|
520
|
-
} else {
|
|
521
|
-
processingMode = 'BPP';
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
#### 2. Job-Level Setting: `meta.preprocessing`
|
|
527
|
-
|
|
528
|
-
Set per job when creating:
|
|
529
|
-
|
|
530
|
-
```typescript
|
|
531
|
-
interface FluentJobMetadata {
|
|
532
|
-
/**
|
|
533
|
-
* Controls Batch Pre-Processing (BPP) change detection
|
|
534
|
-
*
|
|
535
|
-
* @remarks
|
|
536
|
-
* - `"skip"`: Bypass BPP (all records processed)
|
|
537
|
-
* - `"enabled"` or any other value: Use BPP (default)
|
|
538
|
-
* - Omitted/null: Use BPP (default)
|
|
539
|
-
*
|
|
540
|
-
* @important
|
|
541
|
-
* Only works if account setting `fc.enable.batch.preprocessing` is `true`
|
|
542
|
-
* If account setting is `false`, BPP is never used regardless of this value
|
|
543
|
-
*/
|
|
544
|
-
preprocessing?: 'skip' | 'enabled' | string;
|
|
545
|
-
|
|
546
|
-
// Other metadata fields
|
|
547
|
-
source?: string;
|
|
548
|
-
file?: string;
|
|
549
|
-
[key: string]: any;
|
|
550
|
-
}
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### BPP Behavior Matrix
|
|
554
|
-
|
|
555
|
-
| Account Setting (`fc.enable.batch.preprocessing`) | Job Meta (`preprocessing`) | Result |
|
|
556
|
-
| ------------------------------------------------- | -------------------------- | --------------------------------- |
|
|
557
|
-
| `true` | `"skip"` | ❌ **No BPP** (Direct processing) |
|
|
558
|
-
| `true` | `"enabled"` | ✅ **Use BPP** (Change detection) |
|
|
559
|
-
| `true` | `null` / omitted | ✅ **Use BPP** (Default) |
|
|
560
|
-
| `false` | ANY value | ❌ **No BPP** (Account override) |
|
|
561
|
-
|
|
562
|
-
### Complete Code Examples
|
|
563
|
-
|
|
564
|
-
#### Example 1: Daily Full Snapshot (Use BPP)
|
|
565
|
-
|
|
566
|
-
```typescript
|
|
567
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
568
|
-
|
|
569
|
-
const client = await createClient(ctx);
|
|
570
|
-
|
|
571
|
-
// Daily full inventory file from WMS
|
|
572
|
-
const job = await client.createJob({
|
|
573
|
-
name: 'Daily WMS Full Snapshot',
|
|
574
|
-
retailerId: '1',
|
|
575
|
-
// No meta.preprocessing = BPP enabled by default
|
|
576
|
-
meta: {
|
|
577
|
-
source: 'WMS',
|
|
578
|
-
feedType: 'FULL_SNAPSHOT',
|
|
579
|
-
date: new Date().toISOString(),
|
|
580
|
-
},
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
// Send 100K records (many unchanged)
|
|
584
|
-
await client.sendBatch(job.id, {
|
|
585
|
-
action: 'UPSERT',
|
|
586
|
-
entityType: 'INVENTORY',
|
|
587
|
-
source: 'WMS_FULL_SNAPSHOT',
|
|
588
|
-
event: 'INVENTORY_SYNC',
|
|
589
|
-
entities: allInventoryRecords, // All 100K records
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// Result: Only ~5K changed records hit workflow
|
|
593
|
-
console.log('BPP filtered out ~95K unchanged records');
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
#### Example 2: Real-Time Deltas (Skip BPP)
|
|
597
|
-
|
|
598
|
-
```typescript
|
|
599
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
600
|
-
|
|
601
|
-
const client = await createClient(ctx);
|
|
602
|
-
|
|
603
|
-
// Only sending records that changed in last hour
|
|
604
|
-
const job = await client.createJob({
|
|
605
|
-
name: 'Hourly Inventory Deltas',
|
|
606
|
-
retailerId: '1',
|
|
607
|
-
meta: {
|
|
608
|
-
preprocessing: 'skip', // ← Skip BPP (already filtered)
|
|
609
|
-
source: 'CDC', // Change Data Capture
|
|
610
|
-
feedType: 'DELTA',
|
|
611
|
-
},
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
// Send only changed records (already filtered by your system)
|
|
615
|
-
await client.sendBatch(job.id, {
|
|
616
|
-
action: 'UPSERT',
|
|
617
|
-
entityType: 'INVENTORY',
|
|
618
|
-
source: 'CDC_DELTA_FEED',
|
|
619
|
-
event: 'INVENTORY_CHANGE',
|
|
620
|
-
entities: changedRecordsOnly, // Only 500 changed records
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// Result: All 500 records hit workflow immediately
|
|
624
|
-
console.log('All records processed (no BPP filtering)');
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
#### Example 3: Hybrid Approach
|
|
628
|
-
|
|
629
|
-
```typescript
|
|
630
|
-
// Different strategies for different times of day
|
|
631
|
-
async function adaptiveInventorySync(client, records, isFullSync) {
|
|
632
|
-
const job = await client.createJob({
|
|
633
|
-
name: isFullSync ? 'Full Sync' : 'Delta Sync',
|
|
634
|
-
retailerId: '1',
|
|
635
|
-
meta: {
|
|
636
|
-
// Use BPP for full syncs, skip for deltas
|
|
637
|
-
preprocessing: isFullSync ? 'enabled' : 'skip',
|
|
638
|
-
source: 'Hybrid',
|
|
639
|
-
syncType: isFullSync ? 'FULL' : 'DELTA',
|
|
640
|
-
},
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
await client.sendBatch(job.id, {
|
|
644
|
-
action: 'UPSERT',
|
|
645
|
-
entityType: 'INVENTORY',
|
|
646
|
-
source: isFullSync ? 'HYBRID_FULL_SYNC' : 'HYBRID_DELTA_SYNC',
|
|
647
|
-
event: isFullSync ? 'INVENTORY_FULL_SYNC' : 'INVENTORY_DELTA_SYNC',
|
|
648
|
-
entities: records,
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
if (isFullSync) {
|
|
652
|
-
console.log('BPP filtering applied for full sync');
|
|
653
|
-
} else {
|
|
654
|
-
console.log('Direct processing for delta sync');
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Daily full sync at 2 AM
|
|
659
|
-
await adaptiveInventorySync(client, allRecords, true);
|
|
660
|
-
|
|
661
|
-
// Hourly deltas throughout the day
|
|
662
|
-
await adaptiveInventorySync(client, changedRecords, false);
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
### Decision Guide: BPP or Skip?
|
|
666
|
-
|
|
667
|
-
```
|
|
668
|
-
START: What type of data are you sending?
|
|
669
|
-
│
|
|
670
|
-
├─ Full inventory snapshot (all SKUs/locations)
|
|
671
|
-
│ └─ **Use BPP** (default - don't set meta.preprocessing)
|
|
672
|
-
│ → Many unchanged records will be filtered
|
|
673
|
-
│
|
|
674
|
-
├─ Delta feed (only changed records)
|
|
675
|
-
│ └─ **Skip BPP** (set meta.preprocessing: "skip")
|
|
676
|
-
│ → All records are already changes
|
|
677
|
-
│
|
|
678
|
-
├─ Mixed/Unknown
|
|
679
|
-
│ ├─ High percentage unchanged (>50%)
|
|
680
|
-
│ │ └─ **Use BPP** → Better performance
|
|
681
|
-
│ │
|
|
682
|
-
│ └─ Most records changed (>80%)
|
|
683
|
-
│ └─ **Skip BPP** → Faster (no comparison overhead)
|
|
684
|
-
│
|
|
685
|
-
└─ Real-time/urgent updates
|
|
686
|
-
└─ **Skip BPP** → Lowest latency
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
### Performance Comparison
|
|
690
|
-
|
|
691
|
-
#### Scenario: 100K Inventory Records Sent Daily
|
|
692
|
-
|
|
693
|
-
| Metric | With BPP (Default) | BPP Skip (`preprocessing: "skip"`) |
|
|
694
|
-
| ------------------------- | ------------------ | ---------------------------------- |
|
|
695
|
-
| Records sent to API | 100K | 100K |
|
|
696
|
-
| Records actually changed | 5K (5%) | 100K (100% assumed) |
|
|
697
|
-
| Workflow events generated | 5K | 100K |
|
|
698
|
-
| Processing time | ~8 seconds | ~45 seconds |
|
|
699
|
-
| Workflow engine load | Low | High |
|
|
700
|
-
| **Recommended for** | Full snapshots | Delta feeds only |
|
|
701
|
-
|
|
702
|
-
### Common Mistakes
|
|
703
|
-
|
|
704
|
-
#### ❌ Mistake 1: Using BPP for Delta Feeds
|
|
705
|
-
|
|
706
|
-
```typescript
|
|
707
|
-
// WRONG: Wasting resources on comparison
|
|
708
|
-
const job = await client.createJob({
|
|
709
|
-
name: 'Delta Feed',
|
|
710
|
-
retailerId: '1',
|
|
711
|
-
// BPP enabled by default - unnecessary for delta!
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
await client.sendBatch(job.id, {
|
|
715
|
-
action: 'UPSERT',
|
|
716
|
-
entityType: 'INVENTORY',
|
|
717
|
-
source: 'DELTA_FEED',
|
|
718
|
-
event: 'INVENTORY_DELTA',
|
|
719
|
-
entities: onlyChangedRecords, // Already filtered!
|
|
720
|
-
});
|
|
721
|
-
// Result: BPP compares all records unnecessarily
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
**✅ Fix:**
|
|
725
|
-
|
|
726
|
-
```typescript
|
|
727
|
-
const job = await client.createJob({
|
|
728
|
-
name: 'Delta Feed',
|
|
729
|
-
retailerId: '1',
|
|
730
|
-
meta: {
|
|
731
|
-
preprocessing: 'skip', // ← Skip BPP for delta feeds
|
|
732
|
-
},
|
|
733
|
-
});
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
#### ❌ Mistake 2: Skipping BPP for Full Snapshots
|
|
737
|
-
|
|
738
|
-
```typescript
|
|
739
|
-
// WRONG: Overwhelming workflow with unchanged records
|
|
740
|
-
const job = await client.createJob({
|
|
741
|
-
name: 'Full Inventory',
|
|
742
|
-
retailerId: '1',
|
|
743
|
-
meta: {
|
|
744
|
-
preprocessing: 'skip', // ← Bad for full snapshots!
|
|
745
|
-
},
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
await client.sendBatch(job.id, {
|
|
749
|
-
action: 'UPSERT',
|
|
750
|
-
entityType: 'INVENTORY',
|
|
751
|
-
source: 'FULL_INVENTORY_SYNC',
|
|
752
|
-
event: 'INVENTORY_SNAPSHOT',
|
|
753
|
-
entities: allInventory, // 100K records, 95K unchanged
|
|
754
|
-
});
|
|
755
|
-
// Result: 100K workflow events (95K unnecessary)
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
**✅ Fix:**
|
|
759
|
-
|
|
760
|
-
```typescript
|
|
761
|
-
const job = await client.createJob({
|
|
762
|
-
name: 'Full Inventory',
|
|
763
|
-
retailerId: '1',
|
|
764
|
-
// BPP enabled by default - perfect!
|
|
765
|
-
});
|
|
766
|
-
// Result: Only 5K workflow events (95K filtered by BPP)
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
### Key Takeaways
|
|
770
|
-
|
|
771
|
-
- 🎯 **BPP = Change Detection**, not data transformation
|
|
772
|
-
- 🎯 **Use BPP** for full inventory snapshots (most common)
|
|
773
|
-
- 🎯 **Skip BPP** for delta feeds (already filtered)
|
|
774
|
-
- 🎯 **Account setting** takes precedence over job setting
|
|
775
|
-
- 🎯 **Default is BPP enabled** (safest for most cases)
|
|
776
|
-
|
|
777
|
-
---
|
|
778
|
-
|
|
779
|
-
## Batch Size Optimization
|
|
780
|
-
|
|
781
|
-
### Calculate Optimal Batch Size
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
interface BatchSizeConfig {
|
|
785
|
-
maxBatchSize: number; // Maximum records per batch (default: 10000)
|
|
786
|
-
maxPayloadSize: number; // Maximum payload size in bytes (default: 10MB)
|
|
787
|
-
avgRecordSize?: number; // Average record size (auto-calculated if not provided)
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function calculateOptimalBatchSize(
|
|
791
|
-
records: any[],
|
|
792
|
-
config: BatchSizeConfig = {
|
|
793
|
-
maxBatchSize: 10000,
|
|
794
|
-
maxPayloadSize: 10 * 1024 * 1024, // 10MB
|
|
795
|
-
}
|
|
796
|
-
): number {
|
|
797
|
-
// Calculate average record size if not provided
|
|
798
|
-
const avgRecordSize = config.avgRecordSize || JSON.stringify(records.slice(0, 100)).length / 100;
|
|
799
|
-
|
|
800
|
-
// Calculate records per max payload
|
|
801
|
-
const recordsPerPayload = Math.floor(config.maxPayloadSize / avgRecordSize);
|
|
802
|
-
|
|
803
|
-
// Return minimum of calculated size and max batch size
|
|
804
|
-
const optimalSize = Math.min(recordsPerPayload, config.maxBatchSize);
|
|
805
|
-
|
|
806
|
-
console.log(`Optimal batch size: ${optimalSize} (based on ${avgRecordSize} bytes/record)`);
|
|
807
|
-
|
|
808
|
-
return optimalSize;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// Usage
|
|
812
|
-
const batchSize = calculateOptimalBatchSize(inventoryData);
|
|
813
|
-
const batches = chunkArray(inventoryData, batchSize);
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
### Adaptive Batch Sizing
|
|
817
|
-
|
|
818
|
-
```typescript
|
|
819
|
-
class AdaptiveBatchProcessor {
|
|
820
|
-
private currentBatchSize: number;
|
|
821
|
-
private readonly minBatchSize = 100;
|
|
822
|
-
private readonly maxBatchSize = 10000;
|
|
823
|
-
|
|
824
|
-
constructor(initialBatchSize: number = 1000) {
|
|
825
|
-
this.currentBatchSize = initialBatchSize;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
async processBatch(client: FluentClient, jobId: string, data: any[]): Promise<void> {
|
|
829
|
-
const batches = chunkArray(data, this.currentBatchSize);
|
|
830
|
-
|
|
831
|
-
for (const batchData of batches) {
|
|
832
|
-
const startTime = Date.now();
|
|
833
|
-
|
|
834
|
-
try {
|
|
835
|
-
const batch = await client.sendBatch(jobId, {
|
|
836
|
-
action: 'UPSERT',
|
|
837
|
-
entityType: 'INVENTORY',
|
|
838
|
-
source: 'ADAPTIVE_BATCH',
|
|
839
|
-
event: 'INVENTORY_BATCH_IMPORT',
|
|
840
|
-
entities: batchData,
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
const processingTime = Date.now() - startTime;
|
|
844
|
-
|
|
845
|
-
// Adjust batch size based on performance
|
|
846
|
-
this.adjustBatchSize(processingTime, batchData.length);
|
|
847
|
-
} catch (error) {
|
|
848
|
-
// Reduce batch size on error
|
|
849
|
-
this.currentBatchSize = Math.max(this.minBatchSize, Math.floor(this.currentBatchSize / 2));
|
|
850
|
-
throw error;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
private adjustBatchSize(processingTime: number, recordCount: number): void {
|
|
856
|
-
const avgTimePerRecord = processingTime / recordCount;
|
|
857
|
-
|
|
858
|
-
// If processing is fast, increase batch size
|
|
859
|
-
if (avgTimePerRecord < 10 && this.currentBatchSize < this.maxBatchSize) {
|
|
860
|
-
this.currentBatchSize = Math.min(this.maxBatchSize, Math.floor(this.currentBatchSize * 1.2));
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// If processing is slow, decrease batch size
|
|
864
|
-
if (avgTimePerRecord > 50 && this.currentBatchSize > this.minBatchSize) {
|
|
865
|
-
this.currentBatchSize = Math.max(this.minBatchSize, Math.floor(this.currentBatchSize * 0.8));
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
```
|
|
870
|
-
|
|
871
|
-
---
|
|
872
|
-
|
|
873
|
-
## Job Strategies
|
|
874
|
-
|
|
875
|
-
### Strategy 1: DAILY (Recommended for frequent updates)
|
|
876
|
-
|
|
877
|
-
**Use when:**
|
|
878
|
-
|
|
879
|
-
- Multiple files processed per day
|
|
880
|
-
- Want to group all updates under one job
|
|
881
|
-
- Need centralized tracking
|
|
882
|
-
|
|
883
|
-
**Pattern:**
|
|
884
|
-
|
|
885
|
-
```typescript
|
|
886
|
-
import { FluentClient, StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
887
|
-
|
|
888
|
-
async function dailyJobStrategy(
|
|
889
|
-
client: FluentClient,
|
|
890
|
-
files: string[],
|
|
891
|
-
kvAdapter: VersoriKVAdapter,
|
|
892
|
-
state: StateService
|
|
893
|
-
): Promise<void> {
|
|
894
|
-
const workflowId = 'daily-inventory-sync';
|
|
895
|
-
|
|
896
|
-
// Get or create today's job using StateService
|
|
897
|
-
let job = await state.getDailyJob(kvAdapter, workflowId);
|
|
898
|
-
|
|
899
|
-
if (!job) {
|
|
900
|
-
const dateKey = new Date().toISOString().split('T')[0];
|
|
901
|
-
const newJob = await client.createJob({
|
|
902
|
-
name: `Daily Inventory Sync - ${dateKey}`,
|
|
903
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
904
|
-
meta: { date: dateKey, type: 'daily' },
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
// Store job reference with 24-hour expiration
|
|
908
|
-
await state.setDailyJob(kvAdapter, workflowId, newJob.id, 24);
|
|
909
|
-
job = { jobId: newJob.id };
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// Process all files with same job
|
|
913
|
-
for (const file of files) {
|
|
914
|
-
const data = await processFile(file);
|
|
915
|
-
|
|
916
|
-
await client.sendBatch(job.jobId, {
|
|
917
|
-
action: 'UPSERT',
|
|
918
|
-
entityType: 'INVENTORY',
|
|
919
|
-
source: 'DAILY_JOB_STRATEGY',
|
|
920
|
-
event: 'INVENTORY_DAILY_SYNC',
|
|
921
|
-
entities: data,
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
```
|
|
926
|
-
|
|
927
|
-
**Benefits:**
|
|
928
|
-
|
|
929
|
-
- ✅ Single job for all daily updates
|
|
930
|
-
- ✅ Easier monitoring and reporting
|
|
931
|
-
- ✅ Reduced job creation overhead
|
|
932
|
-
|
|
933
|
-
**Drawbacks:**
|
|
934
|
-
|
|
935
|
-
- ❌ Job expiration risk if processing takes too long
|
|
936
|
-
- ❌ All batches fail if job fails
|
|
937
|
-
|
|
938
|
-
### Strategy 2: PER_FILE (Recommended for large datasets)
|
|
939
|
-
|
|
940
|
-
**Use when:**
|
|
941
|
-
|
|
942
|
-
- Processing large files (50k+ records)
|
|
943
|
-
- Want isolated failure handling
|
|
944
|
-
- Need per-file tracking
|
|
945
|
-
|
|
946
|
-
**Pattern:**
|
|
947
|
-
|
|
948
|
-
```typescript
|
|
949
|
-
async function perFileJobStrategy(client: FluentClient, files: string[]): Promise<void> {
|
|
950
|
-
for (const file of files) {
|
|
951
|
-
// Create new job for each file
|
|
952
|
-
const job = await client.createJob({
|
|
953
|
-
name: `Import - ${file}`,
|
|
954
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
955
|
-
metadata: { file, type: 'per-file' },
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
const data = await processFile(file);
|
|
959
|
-
const batches = chunkArray(data, 1000);
|
|
960
|
-
|
|
961
|
-
for (const batchData of batches) {
|
|
962
|
-
await client.sendBatch(job.id, {
|
|
963
|
-
action: 'UPSERT',
|
|
964
|
-
entityType: 'INVENTORY',
|
|
965
|
-
source: 'PER_FILE_STRATEGY',
|
|
966
|
-
event: 'INVENTORY_FILE_IMPORT',
|
|
967
|
-
entities: batchData,
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
```
|
|
973
|
-
|
|
974
|
-
**Benefits:**
|
|
975
|
-
|
|
976
|
-
- ✅ Isolated failure handling
|
|
977
|
-
- ✅ No job expiration risk
|
|
978
|
-
- ✅ Clear per-file tracking
|
|
979
|
-
|
|
980
|
-
**Drawbacks:**
|
|
981
|
-
|
|
982
|
-
- ❌ More jobs created (overhead)
|
|
983
|
-
- ❌ Harder to aggregate metrics
|
|
984
|
-
|
|
985
|
-
### Strategy 3: BATCHES_PER_JOB (Advanced)
|
|
986
|
-
|
|
987
|
-
**Use when:**
|
|
988
|
-
|
|
989
|
-
- Need balance between DAILY and PER_FILE
|
|
990
|
-
- Want to limit batches per job
|
|
991
|
-
|
|
992
|
-
**Pattern:**
|
|
993
|
-
|
|
994
|
-
```typescript
|
|
995
|
-
async function batchesPerJobStrategy(
|
|
996
|
-
client: FluentClient,
|
|
997
|
-
data: any[],
|
|
998
|
-
maxBatchesPerJob: number = 10
|
|
999
|
-
): Promise<void> {
|
|
1000
|
-
const batchSize = 1000;
|
|
1001
|
-
const batches = chunkArray(data, batchSize);
|
|
1002
|
-
|
|
1003
|
-
let currentJob: any = null;
|
|
1004
|
-
let batchCount = 0;
|
|
1005
|
-
|
|
1006
|
-
for (const batchData of batches) {
|
|
1007
|
-
// Create new job if needed
|
|
1008
|
-
if (!currentJob || batchCount >= maxBatchesPerJob) {
|
|
1009
|
-
currentJob = await client.createJob({
|
|
1010
|
-
name: `Inventory Import - ${Date.now()}`,
|
|
1011
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
1012
|
-
});
|
|
1013
|
-
batchCount = 0;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// Send batch
|
|
1017
|
-
await client.sendBatch(currentJob.id, {
|
|
1018
|
-
action: 'UPSERT',
|
|
1019
|
-
entityType: 'INVENTORY',
|
|
1020
|
-
source: 'BATCHES_PER_JOB',
|
|
1021
|
-
event: 'INVENTORY_BATCH_CHUNK',
|
|
1022
|
-
entities: batchData,
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
batchCount++;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
---
|
|
1031
|
-
|
|
1032
|
-
## Job Expiration Handling
|
|
1033
|
-
|
|
1034
|
-
Jobs expire after a certain time (typically 24 hours). Handle expiration gracefully:
|
|
1035
|
-
|
|
1036
|
-
```typescript
|
|
1037
|
-
async function sendBatchWithExpirationHandling(
|
|
1038
|
-
client: FluentClient,
|
|
1039
|
-
jobId: string,
|
|
1040
|
-
batchData: any[]
|
|
1041
|
-
): Promise<string> {
|
|
1042
|
-
try {
|
|
1043
|
-
const batch = await client.sendBatch(jobId, {
|
|
1044
|
-
action: 'UPSERT',
|
|
1045
|
-
entityType: 'INVENTORY',
|
|
1046
|
-
source: 'EXPIRATION_HANDLING',
|
|
1047
|
-
event: 'INVENTORY_BATCH',
|
|
1048
|
-
entities: batchData,
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
return batch.id;
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
if (error.code === 'JOB_EXPIRED' || error.message.includes('expired')) {
|
|
1054
|
-
console.log('Job expired, creating new job...');
|
|
1055
|
-
|
|
1056
|
-
// Create new job
|
|
1057
|
-
const newJob = await client.createJob({
|
|
1058
|
-
name: `Recovery Job - ${Date.now()}`,
|
|
1059
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
1060
|
-
metadata: {
|
|
1061
|
-
originalJobId: jobId,
|
|
1062
|
-
recoveryReason: 'JOB_EXPIRED',
|
|
1063
|
-
},
|
|
1064
|
-
});
|
|
1065
|
-
|
|
1066
|
-
// Retry with new job
|
|
1067
|
-
const batch = await client.sendBatch(newJob.id, {
|
|
1068
|
-
action: 'UPSERT',
|
|
1069
|
-
entityType: 'INVENTORY',
|
|
1070
|
-
source: 'EXPIRATION_RECOVERY',
|
|
1071
|
-
event: 'INVENTORY_BATCH_RECOVERY',
|
|
1072
|
-
entities: batchData,
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
return batch.id;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
throw error; // Re-throw if not expiration error
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
```
|
|
1082
|
-
|
|
1083
|
-
---
|
|
1084
|
-
|
|
1085
|
-
## Partial Batch Recovery (NEW)
|
|
1086
|
-
|
|
1087
|
-
Use PartialBatchRecovery to automatically analyze batch failures, separate retryable vs non-retryable records, and resend only what can succeed.
|
|
1088
|
-
|
|
1089
|
-
### Capabilities
|
|
1090
|
-
|
|
1091
|
-
- Analyze batch error responses
|
|
1092
|
-
- Identify retryable vs non-retryable records
|
|
1093
|
-
- Generate retry payloads and rejection reports
|
|
1094
|
-
- Support new-job or append-to-existing strategies
|
|
1095
|
-
|
|
1096
|
-
### Example
|
|
1097
|
-
|
|
1098
|
-
```typescript
|
|
1099
|
-
import { PartialBatchRecovery } from '@fluentcommerce/fc-connect-sdk';
|
|
1100
|
-
|
|
1101
|
-
// After a batch completes with failures
|
|
1102
|
-
const status = await client.getBatchStatus(job.id, batch.id);
|
|
1103
|
-
|
|
1104
|
-
if (status.status === 'FAILED' && Array.isArray(status.errors) && status.errors.length > 0) {
|
|
1105
|
-
const recovery = new PartialBatchRecovery(logger);
|
|
1106
|
-
|
|
1107
|
-
// 'originalEntities' is the array you sent in this batch
|
|
1108
|
-
const analysis = recovery.analyze(status.errors, originalEntities);
|
|
1109
|
-
|
|
1110
|
-
// Retry only retryable records
|
|
1111
|
-
if (analysis.retryable.length > 0) {
|
|
1112
|
-
const recoveryJob = await client.createJob({
|
|
1113
|
-
name: `Recovery - ${job.id}`,
|
|
1114
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
1115
|
-
meta: { originalJobId: job.id, recoveryOfBatchId: batch.id },
|
|
1116
|
-
});
|
|
1117
|
-
|
|
1118
|
-
await client.sendBatch(recoveryJob.id, {
|
|
1119
|
-
action: 'UPSERT',
|
|
1120
|
-
entityType: 'INVENTORY',
|
|
1121
|
-
source: 'PARTIAL_BATCH_RECOVERY',
|
|
1122
|
-
event: 'INVENTORY_RECOVERY',
|
|
1123
|
-
entities: analysis.retryable,
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// Persist non-retryable errors for investigation
|
|
1128
|
-
if (analysis.nonRetryable.length > 0) {
|
|
1129
|
-
await saveRejectionReport(job.id, batch.id, analysis.nonRetryable);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
```
|
|
1133
|
-
|
|
1134
|
-
Notes:
|
|
1135
|
-
|
|
1136
|
-
- Typical retryable categories: transient network errors, rate limits, temporary validation states
|
|
1137
|
-
- Typical non-retryable categories: schema violations, permanently invalid data
|
|
1138
|
-
- Always store a rejection report for auditing and manual remediation
|
|
1139
|
-
|
|
1140
|
-
---
|
|
1141
|
-
|
|
1142
|
-
## Comprehensive Error Handling Patterns
|
|
1143
|
-
|
|
1144
|
-
### Pattern 1: Batch Processing with Rate Limiting
|
|
1145
|
-
|
|
1146
|
-
```typescript
|
|
1147
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
1148
|
-
|
|
1149
|
-
async function processBatchWithRateLimiting(
|
|
1150
|
-
client: any,
|
|
1151
|
-
jobId: string,
|
|
1152
|
-
entities: any[],
|
|
1153
|
-
logger: any
|
|
1154
|
-
) {
|
|
1155
|
-
try {
|
|
1156
|
-
const batch = await client.sendBatch(jobId, {
|
|
1157
|
-
action: 'UPSERT',
|
|
1158
|
-
entityType: 'INVENTORY',
|
|
1159
|
-
source: 'BATCH_PROCESSING',
|
|
1160
|
-
event: 'INVENTORY_BATCH',
|
|
1161
|
-
entities
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
logger.info('Batch sent successfully', {
|
|
1165
|
-
batchId: batch.id,
|
|
1166
|
-
recordCount: entities.length
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1169
|
-
return batch.id;
|
|
1170
|
-
} catch (error) {
|
|
1171
|
-
logger.error('Batch submission failed', {
|
|
1172
|
-
jobId,
|
|
1173
|
-
recordCount: entities.length,
|
|
1174
|
-
error: error.message,
|
|
1175
|
-
errorCode: error.code
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
if (error.code === 'RATE_LIMIT_ERROR' || error.message.includes('rate limit')) {
|
|
1179
|
-
logger.warn('Rate limit hit, implementing backoff', { jobId });
|
|
1180
|
-
|
|
1181
|
-
// Exponential backoff retry
|
|
1182
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1183
|
-
|
|
1184
|
-
try {
|
|
1185
|
-
const retryBatch = await client.sendBatch(jobId, {
|
|
1186
|
-
action: 'UPSERT',
|
|
1187
|
-
entityType: 'INVENTORY',
|
|
1188
|
-
source: 'BATCH_RETRY',
|
|
1189
|
-
event: 'INVENTORY_BATCH_RETRY',
|
|
1190
|
-
entities
|
|
1191
|
-
});
|
|
1192
|
-
|
|
1193
|
-
logger.info('Batch sent successfully after retry', {
|
|
1194
|
-
batchId: retryBatch.id
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
return retryBatch.id;
|
|
1198
|
-
} catch (retryError) {
|
|
1199
|
-
logger.error('Batch failed after retry', {
|
|
1200
|
-
jobId,
|
|
1201
|
-
error: retryError.message
|
|
1202
|
-
});
|
|
1203
|
-
throw retryError;
|
|
1204
|
-
}
|
|
1205
|
-
} else if (error.code === 'VALIDATION_ERROR') {
|
|
1206
|
-
logger.error('Batch validation failed - data does not match schema', {
|
|
1207
|
-
jobId,
|
|
1208
|
-
error: error.message
|
|
1209
|
-
});
|
|
1210
|
-
throw new Error('Validation error: Check data schema compatibility');
|
|
1211
|
-
} else {
|
|
1212
|
-
throw error;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
```
|
|
1217
|
-
|
|
1218
|
-
### Pattern 2: Network Error Recovery
|
|
1219
|
-
|
|
1220
|
-
```typescript
|
|
1221
|
-
async function executeWithNetworkRetry<T>(
|
|
1222
|
-
operation: () => Promise<T>,
|
|
1223
|
-
operationName: string,
|
|
1224
|
-
logger: any,
|
|
1225
|
-
maxRetries: number = 3
|
|
1226
|
-
): Promise<T> {
|
|
1227
|
-
let lastError: Error;
|
|
1228
|
-
|
|
1229
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1230
|
-
try {
|
|
1231
|
-
return await operation();
|
|
1232
|
-
} catch (error) {
|
|
1233
|
-
lastError = error;
|
|
1234
|
-
|
|
1235
|
-
logger.warn(`${operationName} failed`, {
|
|
1236
|
-
attempt: attempt + 1,
|
|
1237
|
-
maxRetries,
|
|
1238
|
-
error: error.message
|
|
1239
|
-
});
|
|
1240
|
-
|
|
1241
|
-
// Check if error is retryable
|
|
1242
|
-
const isRetryable =
|
|
1243
|
-
error.code === 'ECONNRESET' ||
|
|
1244
|
-
error.code === 'ETIMEDOUT' ||
|
|
1245
|
-
error.message.includes('network') ||
|
|
1246
|
-
error.message.includes('timeout');
|
|
1247
|
-
|
|
1248
|
-
if (!isRetryable || attempt === maxRetries - 1) {
|
|
1249
|
-
throw lastError;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// Exponential backoff
|
|
1253
|
-
const delay = Math.pow(2, attempt) * 1000;
|
|
1254
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
throw lastError!;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
// Usage
|
|
1262
|
-
const batch = await executeWithNetworkRetry(
|
|
1263
|
-
() => client.sendBatch(jobId, payload),
|
|
1264
|
-
'sendBatch',
|
|
1265
|
-
logger
|
|
1266
|
-
);
|
|
1267
|
-
```
|
|
1268
|
-
|
|
1269
|
-
---
|
|
1270
|
-
|
|
1271
|
-
## Key Takeaways
|
|
1272
|
-
|
|
1273
|
-
- 🎯 **Batch API is INVENTORY only** - No orders, products, or locations
|
|
1274
|
-
- 🎯 **Choose right job strategy** - DAILY for frequent updates, PER_FILE for large datasets
|
|
1275
|
-
- 🎯 **Optimize batch sizes** - Balance payload size and API limits
|
|
1276
|
-
- 🎯 **Handle job expiration** - Implement recovery for long-running processes
|
|
1277
|
-
- 🎯 **Monitor status properly** - Poll batch and job status with timeouts
|
|
1278
|
-
- 🎯 **Implement error recovery** - Retry transient errors, log permanent failures
|
|
1279
|
-
- 🎯 **Use structured logging** - Include context in all error logs
|
|
1280
|
-
|
|
1281
|
-
---
|
|
1282
|
-
|
|
1283
|
-
## Next Steps
|
|
1284
|
-
|
|
1285
|
-
Continue to [Module 7: State Management](./02-core-guides-ingestion-07-state-management.md) to learn how to prevent duplicate processing with state tracking.
|
|
1286
|
-
|
|
1287
|
-
---
|
|
1288
|
-
|
|
1289
|
-
[← Previous: Field Mapping](02-core-guides-ingestion-04-field-mapping.md) | [Back to Guide](../ingestion-readme.md) | [Next: State Management →](./02-core-guides-ingestion-07-state-management.md)
|
|
1290
|
-
|
|
1291
|
-
## Related Documentation
|
|
1292
|
-
|
|
1293
|
-
- [Batch API Reference](../../api-reference/modules/api-reference-01-client-api.md#job-batch-operations) - Complete API documentation
|
|
1294
|
-
- [Job Strategies](02-core-guides-ingestion-08-performance-optimization.md#job-strategy-comparison) - Detailed strategy comparison
|
|
1295
|
-
- [Schema Validation](../../mapping/mapping-readme.md#04-REFERENCE/schema-validation) - Validate mappings
|
|
1
|
+
# Module 6: Batch API Deep Dive
|
|
2
|
+
|
|
3
|
+
[← Back to Ingestion Guide](../ingestion-readme.md)
|
|
4
|
+
|
|
5
|
+
**Module 6 of 9** | **Level**: Intermediate | **Time**: 30 minutes
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This module provides complete coverage of the Fluent Commerce Batch API, including job lifecycle, batch operations, status monitoring, and optimization strategies.
|
|
12
|
+
|
|
13
|
+
## Learning Objectives
|
|
14
|
+
|
|
15
|
+
By the end of this module, you will:
|
|
16
|
+
|
|
17
|
+
- ✅ Understand the complete Batch API schema
|
|
18
|
+
- ✅ Master the job creation and management lifecycle
|
|
19
|
+
- ✅ Implement batch size optimization strategies
|
|
20
|
+
- ✅ Monitor job and batch status effectively
|
|
21
|
+
- ✅ Understand job strategies (DAILY vs PER_FILE)
|
|
22
|
+
- ✅ Handle job expiration and recovery
|
|
23
|
+
- ✅ Implement production-ready batch processing
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Batch API Schema
|
|
28
|
+
|
|
29
|
+
### Complete GraphQL Schema
|
|
30
|
+
|
|
31
|
+
The Batch API consists of two main mutations:
|
|
32
|
+
|
|
33
|
+
#### 1. `createJob` Mutation
|
|
34
|
+
|
|
35
|
+
```graphql
|
|
36
|
+
mutation CreateJob($input: CreateJobInput!) {
|
|
37
|
+
createJob(input: $input) {
|
|
38
|
+
id # Job ID (String!)
|
|
39
|
+
name # Job name (String)
|
|
40
|
+
status # Job status (JobStatus enum)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Input Schema** (`CreateJobInput`):
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
{
|
|
49
|
+
"name": "string", // ✅ Job name (String! required)
|
|
50
|
+
"retailerId": "string", // ✅ Retailer ID (ID! required)
|
|
51
|
+
"metadata": { // ✅ JSON (optional) - Custom metadata
|
|
52
|
+
"source": "S3",
|
|
53
|
+
"file": "inventory-2025-01-19.csv"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### 2. `sendBatch` Mutation
|
|
59
|
+
|
|
60
|
+
```graphql
|
|
61
|
+
mutation SendBatch($input: BatchInput!) {
|
|
62
|
+
sendBatch(input: $input) {
|
|
63
|
+
id # Batch ID (String!)
|
|
64
|
+
status # Batch status (BatchStatus enum)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Input Schema** (`BatchInput`):
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
{
|
|
73
|
+
"action": "UPSERT", // ✅ String (UPSERT only - required)
|
|
74
|
+
"entityType": "INVENTORY", // ✅ String (INVENTORY only - required)
|
|
75
|
+
"source": "S3_CSV_IMPORT", // ✅ String (required) - Source system identifier
|
|
76
|
+
"event": "INVENTORY_UPDATE", // ✅ String (required) - Event type identifier
|
|
77
|
+
"retailerId": "1", // ✅ ID! (required)
|
|
78
|
+
"entities": [ // ✅ [InventoryPositionInput]! (required array)
|
|
79
|
+
{
|
|
80
|
+
"ref": "SKU-WM-001", // ✅ String! (required)
|
|
81
|
+
"productRef": "SKU-WM-001", // ✅ String! (required)
|
|
82
|
+
"locationRef": "WH-001", // ✅ String! (required)
|
|
83
|
+
"qty": 500, // ✅ Int! (required)
|
|
84
|
+
"status": "AVAILABLE" // ✅ String (optional)
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `InventoryPositionInput` Complete Schema
|
|
91
|
+
|
|
92
|
+
From `docs/schema/fluent-commerce-schema.json`:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"InventoryPositionInput": {
|
|
97
|
+
"kind": "INPUT_OBJECT",
|
|
98
|
+
"description": "Input for inventory position in batch",
|
|
99
|
+
"fields": {
|
|
100
|
+
"ref": {
|
|
101
|
+
"type": "String!",
|
|
102
|
+
"description": "Position reference (Required)",
|
|
103
|
+
"businessContext": "Unique identifier for this inventory position, typically SKU-LOCATION combination",
|
|
104
|
+
"example": "SKU-WM-001"
|
|
105
|
+
},
|
|
106
|
+
"productRef": {
|
|
107
|
+
"type": "String!",
|
|
108
|
+
"description": "Product reference (Required)",
|
|
109
|
+
"businessContext": "Product SKU that this inventory position represents",
|
|
110
|
+
"example": "SKU-WM-001"
|
|
111
|
+
},
|
|
112
|
+
"locationRef": {
|
|
113
|
+
"type": "String!",
|
|
114
|
+
"description": "Location reference (Required)",
|
|
115
|
+
"businessContext": "Warehouse or location code where inventory is held",
|
|
116
|
+
"example": "WH-001"
|
|
117
|
+
},
|
|
118
|
+
"qty": {
|
|
119
|
+
"type": "Int!",
|
|
120
|
+
"description": "Quantity (Required)",
|
|
121
|
+
"businessContext": "Available quantity at this location",
|
|
122
|
+
"example": 500,
|
|
123
|
+
"validationRules": ["qty >= 0"]
|
|
124
|
+
},
|
|
125
|
+
"status": {
|
|
126
|
+
"type": "String",
|
|
127
|
+
"description": "Status (Optional)",
|
|
128
|
+
"businessContext": "Inventory position status",
|
|
129
|
+
"example": "AVAILABLE",
|
|
130
|
+
"validValues": ["AVAILABLE", "RESERVED", "ON_HOLD", "DISCONTINUED"]
|
|
131
|
+
},
|
|
132
|
+
"type": {
|
|
133
|
+
"type": "String",
|
|
134
|
+
"description": "Position type (Optional)",
|
|
135
|
+
"businessContext": "Type of inventory adjustment",
|
|
136
|
+
"example": "ADJUSTMENT"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Complete Job Lifecycle
|
|
146
|
+
|
|
147
|
+
### Lifecycle Diagram
|
|
148
|
+
|
|
149
|
+
```mermaid
|
|
150
|
+
graph TD
|
|
151
|
+
A[Create Job] --> B{Job Created?}
|
|
152
|
+
B -->|Yes| C[Job PENDING]
|
|
153
|
+
B -->|No| Z[ERROR]
|
|
154
|
+
|
|
155
|
+
C --> D[Send Batch 1]
|
|
156
|
+
D --> E[Batch PROCESSING]
|
|
157
|
+
|
|
158
|
+
C --> F[Send Batch 2]
|
|
159
|
+
F --> G[Batch PROCESSING]
|
|
160
|
+
|
|
161
|
+
E --> H{Batch Complete?}
|
|
162
|
+
G --> I{Batch Complete?}
|
|
163
|
+
|
|
164
|
+
H -->|Success| J[Batch COMPLETED]
|
|
165
|
+
H -->|Failure| K[Batch FAILED]
|
|
166
|
+
|
|
167
|
+
I -->|Success| L[Batch COMPLETED]
|
|
168
|
+
I -->|Failure| M[Batch FAILED]
|
|
169
|
+
|
|
170
|
+
J --> N{More Batches?}
|
|
171
|
+
L --> N
|
|
172
|
+
|
|
173
|
+
N -->|Yes| O[Send Next Batch]
|
|
174
|
+
N -->|No| P[All Batches Sent]
|
|
175
|
+
|
|
176
|
+
O --> E
|
|
177
|
+
|
|
178
|
+
P --> Q{Job Expired?}
|
|
179
|
+
Q -->|No| R[Job COMPLETED]
|
|
180
|
+
Q -->|Yes| S[Job EXPIRED - Create New Job]
|
|
181
|
+
|
|
182
|
+
K --> T[Handle Failed Batch]
|
|
183
|
+
M --> T
|
|
184
|
+
T --> U[Retry or Log Error]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Step 1: Create Job
|
|
188
|
+
|
|
189
|
+
**Simple job creation:**
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
193
|
+
|
|
194
|
+
const client = await createClient({ config });
|
|
195
|
+
|
|
196
|
+
const job = await client.createJob({
|
|
197
|
+
name: 'Daily Inventory Sync',
|
|
198
|
+
retailerId: '1',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
console.log(`Job created: ${job.id}`);
|
|
202
|
+
// Output: Job created: JOB-12345
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Job creation with metadata (for tracking):**
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const job = await client.createJob({
|
|
209
|
+
name: `Inventory Import - ${new Date().toISOString()}`,
|
|
210
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
211
|
+
metadata: {
|
|
212
|
+
source: 'S3',
|
|
213
|
+
bucket: 'inventory-bucket',
|
|
214
|
+
file: 'data/inventory-2025-01-19.csv',
|
|
215
|
+
uploadedBy: 'integration-service',
|
|
216
|
+
triggeredBy: 'scheduled-workflow',
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Step 2: Send Batches
|
|
222
|
+
|
|
223
|
+
**Single batch:**
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const batch = await client.sendBatch(job.id, {
|
|
227
|
+
action: 'UPSERT', // String, not enum
|
|
228
|
+
entityType: 'INVENTORY', // String, not enum
|
|
229
|
+
source: 'S3_CSV_IMPORT', // REQUIRED - Source system identifier
|
|
230
|
+
event: 'INVENTORY_UPDATE', // REQUIRED - Event type identifier
|
|
231
|
+
entities: [
|
|
232
|
+
{
|
|
233
|
+
ref: 'SKU-WM-001',
|
|
234
|
+
productRef: 'SKU-WM-001',
|
|
235
|
+
locationRef: 'WH-001',
|
|
236
|
+
qty: 500,
|
|
237
|
+
status: 'AVAILABLE',
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log(`Batch sent: ${batch.id}`);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Multiple batches (chunked):**
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
function chunkArray<T>(array: T[], chunkSize: number): T[][] {
|
|
249
|
+
const chunks: T[][] = [];
|
|
250
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
251
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
252
|
+
}
|
|
253
|
+
return chunks;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Split large dataset into batches
|
|
257
|
+
const batchSize = 1000;
|
|
258
|
+
const batches = chunkArray(inventoryData, batchSize);
|
|
259
|
+
|
|
260
|
+
const batchResults = [];
|
|
261
|
+
|
|
262
|
+
for (const [index, batchData] of batches.entries()) {
|
|
263
|
+
console.log(`Sending batch ${index + 1}/${batches.length}...`);
|
|
264
|
+
|
|
265
|
+
const batch = await client.sendBatch(job.id, {
|
|
266
|
+
action: 'UPSERT',
|
|
267
|
+
entityType: 'INVENTORY',
|
|
268
|
+
source: 'S3_CSV_IMPORT',
|
|
269
|
+
event: 'INVENTORY_UPDATE',
|
|
270
|
+
entities: batchData,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
batchResults.push({
|
|
274
|
+
index,
|
|
275
|
+
batchId: batch.id,
|
|
276
|
+
records: batchData.length,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log(`Sent ${batchResults.length} batches with ${inventoryData.length} total records`);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Step 3: Monitor Status
|
|
284
|
+
|
|
285
|
+
**Poll batch status:**
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
async function waitForBatchCompletion(
|
|
289
|
+
client: FluentClient,
|
|
290
|
+
jobId: string,
|
|
291
|
+
batchId: string,
|
|
292
|
+
pollInterval: number = 5000,
|
|
293
|
+
timeout: number = 300000
|
|
294
|
+
): Promise<BatchStatus> {
|
|
295
|
+
const startTime = Date.now();
|
|
296
|
+
|
|
297
|
+
while (Date.now() - startTime < timeout) {
|
|
298
|
+
const status = await client.getBatchStatus(jobId, batchId);
|
|
299
|
+
|
|
300
|
+
console.log(`Batch ${batchId}: ${status.status}`);
|
|
301
|
+
|
|
302
|
+
if (status.status === 'COMPLETED') {
|
|
303
|
+
return status;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (status.status === 'FAILED') {
|
|
307
|
+
throw new Error(`Batch failed: ${JSON.stringify(status.errors)}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Wait before next poll
|
|
311
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
throw new Error(`Batch monitoring timeout after ${timeout}ms`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Usage
|
|
318
|
+
const status = await waitForBatchCompletion(client, job.id, batch.id);
|
|
319
|
+
console.log(`✅ Batch completed successfully`);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Monitor job status (all batches):**
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
async function monitorJobProgress(
|
|
326
|
+
client: FluentClient,
|
|
327
|
+
jobId: string,
|
|
328
|
+
onProgress?: (status: JobStatus) => void
|
|
329
|
+
): Promise<JobStatus> {
|
|
330
|
+
const pollInterval = 5000; // 5 seconds
|
|
331
|
+
const timeout = 600000; // 10 minutes
|
|
332
|
+
|
|
333
|
+
const startTime = Date.now();
|
|
334
|
+
|
|
335
|
+
while (Date.now() - startTime < timeout) {
|
|
336
|
+
const status = await client.getJobStatus(jobId);
|
|
337
|
+
|
|
338
|
+
if (onProgress) {
|
|
339
|
+
onProgress(status);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (status.status === 'COMPLETED' || status.status === 'FAILED') {
|
|
343
|
+
return status;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
throw new Error(`Job monitoring timeout: ${jobId}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Usage with progress callback
|
|
353
|
+
const jobStatus = await monitorJobProgress(client, job.id, status => {
|
|
354
|
+
console.log(`Job ${job.id}: ${status.status} - ${status.progress || 0}% complete`);
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Batch Pre-Processing (BPP) - Change Detection
|
|
361
|
+
|
|
362
|
+
### Overview
|
|
363
|
+
|
|
364
|
+
**Batch Pre-Processing (BPP)** is Fluent Commerce's built-in change detection system that **filters out unchanged inventory records** before they reach the workflow engine, significantly improving performance when sending full inventory snapshots.
|
|
365
|
+
|
|
366
|
+
### How BPP Works
|
|
367
|
+
|
|
368
|
+
BPP introduces two internal jobs that run **before** your inventory updates hit the workflow:
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
┌─────────────────────────────────────────────────────┐
|
|
372
|
+
│ WITHOUT BPP (meta.preprocessing: "skip") │
|
|
373
|
+
│ ──────────────────────────────────────────────── │
|
|
374
|
+
│ Batch API → ALL records → Inventory Queue → Rubix │
|
|
375
|
+
│ 100K records → 100K workflow events │
|
|
376
|
+
└─────────────────────────────────────────────────────┘
|
|
377
|
+
|
|
378
|
+
┌──────────────────────────────────────────────────────────┐
|
|
379
|
+
│ WITH BPP (default) │
|
|
380
|
+
│ ───────────────────────────────────────────────────── │
|
|
381
|
+
│ Batch API → Loader Job → Comparison Job → │
|
|
382
|
+
│ → ONLY CHANGED records → Inventory Queue → Rubix │
|
|
383
|
+
│ 100K records → 5K workflow events (95% filtered) │
|
|
384
|
+
└──────────────────────────────────────────────────────────┘
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### BPP Components
|
|
388
|
+
|
|
389
|
+
**1. Loader Job**
|
|
390
|
+
|
|
391
|
+
- Loads existing inventory data from database
|
|
392
|
+
- Includes current quantities, statuses, and transient inventory
|
|
393
|
+
|
|
394
|
+
**2. Comparison Job**
|
|
395
|
+
|
|
396
|
+
- Compares batch records vs existing data
|
|
397
|
+
- Checks for changes in:
|
|
398
|
+
- **Quantity**: Has qty changed?
|
|
399
|
+
- **Status**: Has status changed?
|
|
400
|
+
- **Transient Inventory**: Are there active transient quantities?
|
|
401
|
+
|
|
402
|
+
**3. Result**
|
|
403
|
+
|
|
404
|
+
- Only records marked as "changed" proceed to workflow
|
|
405
|
+
- Unchanged records are filtered out (not processed)
|
|
406
|
+
|
|
407
|
+
### When to Use BPP
|
|
408
|
+
|
|
409
|
+
#### ✅ Use BPP (Default) When:
|
|
410
|
+
|
|
411
|
+
**Scenario 1: Full Inventory Snapshots**
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// Sending complete inventory file daily
|
|
415
|
+
const job = await client.createJob({
|
|
416
|
+
name: 'Daily Full Inventory Sync',
|
|
417
|
+
retailerId: '1',
|
|
418
|
+
// BPP enabled by default (no meta.preprocessing)
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// 100K records sent, only ~5K changed overnight
|
|
422
|
+
// Result: 5K workflow events instead of 100K
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Scenario 2: Unchanged Records in Data Feed**
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// WMS sends all SKU-location combinations daily
|
|
429
|
+
// Many haven't changed since yesterday
|
|
430
|
+
const job = await client.createJob({
|
|
431
|
+
name: 'WMS Daily Extract',
|
|
432
|
+
retailerId: '1',
|
|
433
|
+
meta: {
|
|
434
|
+
preprocessing: 'enabled', // Explicit BPP enable
|
|
435
|
+
source: 'WMS',
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Benefits:**
|
|
441
|
+
|
|
442
|
+
- ✅ Reduces workflow engine load by 80-95%
|
|
443
|
+
- ✅ Faster processing (only changed records)
|
|
444
|
+
- ✅ Automatic duplicate detection
|
|
445
|
+
- ✅ Lower infrastructure costs
|
|
446
|
+
|
|
447
|
+
#### ❌ Skip BPP When:
|
|
448
|
+
|
|
449
|
+
**Scenario 1: Delta/Change-Only Feeds**
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// Your application already filtered changed records
|
|
453
|
+
// Sending ONLY records that changed
|
|
454
|
+
const job = await client.createJob({
|
|
455
|
+
name: 'Real-Time Inventory Delta',
|
|
456
|
+
retailerId: '1',
|
|
457
|
+
meta: {
|
|
458
|
+
preprocessing: 'skip', // ← Skip BPP (every record is already changed)
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// 100 records sent, all 100 are changes
|
|
463
|
+
// Result: 100 workflow events (no filtering needed)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Scenario 2: Real-Time Updates**
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
// High-frequency updates where every record matters
|
|
470
|
+
// e.g., Point-of-sale inventory decrements
|
|
471
|
+
const job = await client.createJob({
|
|
472
|
+
name: 'POS Inventory Update',
|
|
473
|
+
retailerId: '1',
|
|
474
|
+
meta: {
|
|
475
|
+
preprocessing: 'skip',
|
|
476
|
+
source: 'POS',
|
|
477
|
+
realtime: true,
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Scenario 3: Performance-Critical Imports**
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
// Need absolute fastest processing
|
|
486
|
+
// Data is guaranteed to be different
|
|
487
|
+
const job = await client.createJob({
|
|
488
|
+
name: 'Critical Inventory Adjustment',
|
|
489
|
+
retailerId: '1',
|
|
490
|
+
meta: {
|
|
491
|
+
preprocessing: 'skip',
|
|
492
|
+
priority: 'high',
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Benefits:**
|
|
498
|
+
|
|
499
|
+
- ✅ Faster (no comparison overhead)
|
|
500
|
+
- ✅ No need to load existing data
|
|
501
|
+
- ✅ Simpler processing flow
|
|
502
|
+
|
|
503
|
+
### Account-Level BPP Configuration
|
|
504
|
+
|
|
505
|
+
BPP behavior is controlled by **two settings**:
|
|
506
|
+
|
|
507
|
+
#### 1. Account Setting: `fc.enable.batch.preprocessing`
|
|
508
|
+
|
|
509
|
+
This is configured at the **Fluent Commerce account level** (contact Fluent support to check/modify):
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
// Pseudo-code showing the logic
|
|
513
|
+
if (account.settings.fc_enable_batch_preprocessing === false) {
|
|
514
|
+
// BPP is NEVER used, regardless of job meta
|
|
515
|
+
processingMode = 'DIRECT';
|
|
516
|
+
} else {
|
|
517
|
+
// BPP is available, check job-level meta
|
|
518
|
+
if (job.meta.preprocessing === 'skip') {
|
|
519
|
+
processingMode = 'DIRECT';
|
|
520
|
+
} else {
|
|
521
|
+
processingMode = 'BPP';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### 2. Job-Level Setting: `meta.preprocessing`
|
|
527
|
+
|
|
528
|
+
Set per job when creating:
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
interface FluentJobMetadata {
|
|
532
|
+
/**
|
|
533
|
+
* Controls Batch Pre-Processing (BPP) change detection
|
|
534
|
+
*
|
|
535
|
+
* @remarks
|
|
536
|
+
* - `"skip"`: Bypass BPP (all records processed)
|
|
537
|
+
* - `"enabled"` or any other value: Use BPP (default)
|
|
538
|
+
* - Omitted/null: Use BPP (default)
|
|
539
|
+
*
|
|
540
|
+
* @important
|
|
541
|
+
* Only works if account setting `fc.enable.batch.preprocessing` is `true`
|
|
542
|
+
* If account setting is `false`, BPP is never used regardless of this value
|
|
543
|
+
*/
|
|
544
|
+
preprocessing?: 'skip' | 'enabled' | string;
|
|
545
|
+
|
|
546
|
+
// Other metadata fields
|
|
547
|
+
source?: string;
|
|
548
|
+
file?: string;
|
|
549
|
+
[key: string]: any;
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### BPP Behavior Matrix
|
|
554
|
+
|
|
555
|
+
| Account Setting (`fc.enable.batch.preprocessing`) | Job Meta (`preprocessing`) | Result |
|
|
556
|
+
| ------------------------------------------------- | -------------------------- | --------------------------------- |
|
|
557
|
+
| `true` | `"skip"` | ❌ **No BPP** (Direct processing) |
|
|
558
|
+
| `true` | `"enabled"` | ✅ **Use BPP** (Change detection) |
|
|
559
|
+
| `true` | `null` / omitted | ✅ **Use BPP** (Default) |
|
|
560
|
+
| `false` | ANY value | ❌ **No BPP** (Account override) |
|
|
561
|
+
|
|
562
|
+
### Complete Code Examples
|
|
563
|
+
|
|
564
|
+
#### Example 1: Daily Full Snapshot (Use BPP)
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
568
|
+
|
|
569
|
+
const client = await createClient(ctx);
|
|
570
|
+
|
|
571
|
+
// Daily full inventory file from WMS
|
|
572
|
+
const job = await client.createJob({
|
|
573
|
+
name: 'Daily WMS Full Snapshot',
|
|
574
|
+
retailerId: '1',
|
|
575
|
+
// No meta.preprocessing = BPP enabled by default
|
|
576
|
+
meta: {
|
|
577
|
+
source: 'WMS',
|
|
578
|
+
feedType: 'FULL_SNAPSHOT',
|
|
579
|
+
date: new Date().toISOString(),
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Send 100K records (many unchanged)
|
|
584
|
+
await client.sendBatch(job.id, {
|
|
585
|
+
action: 'UPSERT',
|
|
586
|
+
entityType: 'INVENTORY',
|
|
587
|
+
source: 'WMS_FULL_SNAPSHOT',
|
|
588
|
+
event: 'INVENTORY_SYNC',
|
|
589
|
+
entities: allInventoryRecords, // All 100K records
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Result: Only ~5K changed records hit workflow
|
|
593
|
+
console.log('BPP filtered out ~95K unchanged records');
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### Example 2: Real-Time Deltas (Skip BPP)
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
600
|
+
|
|
601
|
+
const client = await createClient(ctx);
|
|
602
|
+
|
|
603
|
+
// Only sending records that changed in last hour
|
|
604
|
+
const job = await client.createJob({
|
|
605
|
+
name: 'Hourly Inventory Deltas',
|
|
606
|
+
retailerId: '1',
|
|
607
|
+
meta: {
|
|
608
|
+
preprocessing: 'skip', // ← Skip BPP (already filtered)
|
|
609
|
+
source: 'CDC', // Change Data Capture
|
|
610
|
+
feedType: 'DELTA',
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Send only changed records (already filtered by your system)
|
|
615
|
+
await client.sendBatch(job.id, {
|
|
616
|
+
action: 'UPSERT',
|
|
617
|
+
entityType: 'INVENTORY',
|
|
618
|
+
source: 'CDC_DELTA_FEED',
|
|
619
|
+
event: 'INVENTORY_CHANGE',
|
|
620
|
+
entities: changedRecordsOnly, // Only 500 changed records
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// Result: All 500 records hit workflow immediately
|
|
624
|
+
console.log('All records processed (no BPP filtering)');
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### Example 3: Hybrid Approach
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
// Different strategies for different times of day
|
|
631
|
+
async function adaptiveInventorySync(client, records, isFullSync) {
|
|
632
|
+
const job = await client.createJob({
|
|
633
|
+
name: isFullSync ? 'Full Sync' : 'Delta Sync',
|
|
634
|
+
retailerId: '1',
|
|
635
|
+
meta: {
|
|
636
|
+
// Use BPP for full syncs, skip for deltas
|
|
637
|
+
preprocessing: isFullSync ? 'enabled' : 'skip',
|
|
638
|
+
source: 'Hybrid',
|
|
639
|
+
syncType: isFullSync ? 'FULL' : 'DELTA',
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
await client.sendBatch(job.id, {
|
|
644
|
+
action: 'UPSERT',
|
|
645
|
+
entityType: 'INVENTORY',
|
|
646
|
+
source: isFullSync ? 'HYBRID_FULL_SYNC' : 'HYBRID_DELTA_SYNC',
|
|
647
|
+
event: isFullSync ? 'INVENTORY_FULL_SYNC' : 'INVENTORY_DELTA_SYNC',
|
|
648
|
+
entities: records,
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
if (isFullSync) {
|
|
652
|
+
console.log('BPP filtering applied for full sync');
|
|
653
|
+
} else {
|
|
654
|
+
console.log('Direct processing for delta sync');
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Daily full sync at 2 AM
|
|
659
|
+
await adaptiveInventorySync(client, allRecords, true);
|
|
660
|
+
|
|
661
|
+
// Hourly deltas throughout the day
|
|
662
|
+
await adaptiveInventorySync(client, changedRecords, false);
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Decision Guide: BPP or Skip?
|
|
666
|
+
|
|
667
|
+
```
|
|
668
|
+
START: What type of data are you sending?
|
|
669
|
+
│
|
|
670
|
+
├─ Full inventory snapshot (all SKUs/locations)
|
|
671
|
+
│ └─ **Use BPP** (default - don't set meta.preprocessing)
|
|
672
|
+
│ → Many unchanged records will be filtered
|
|
673
|
+
│
|
|
674
|
+
├─ Delta feed (only changed records)
|
|
675
|
+
│ └─ **Skip BPP** (set meta.preprocessing: "skip")
|
|
676
|
+
│ → All records are already changes
|
|
677
|
+
│
|
|
678
|
+
├─ Mixed/Unknown
|
|
679
|
+
│ ├─ High percentage unchanged (>50%)
|
|
680
|
+
│ │ └─ **Use BPP** → Better performance
|
|
681
|
+
│ │
|
|
682
|
+
│ └─ Most records changed (>80%)
|
|
683
|
+
│ └─ **Skip BPP** → Faster (no comparison overhead)
|
|
684
|
+
│
|
|
685
|
+
└─ Real-time/urgent updates
|
|
686
|
+
└─ **Skip BPP** → Lowest latency
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Performance Comparison
|
|
690
|
+
|
|
691
|
+
#### Scenario: 100K Inventory Records Sent Daily
|
|
692
|
+
|
|
693
|
+
| Metric | With BPP (Default) | BPP Skip (`preprocessing: "skip"`) |
|
|
694
|
+
| ------------------------- | ------------------ | ---------------------------------- |
|
|
695
|
+
| Records sent to API | 100K | 100K |
|
|
696
|
+
| Records actually changed | 5K (5%) | 100K (100% assumed) |
|
|
697
|
+
| Workflow events generated | 5K | 100K |
|
|
698
|
+
| Processing time | ~8 seconds | ~45 seconds |
|
|
699
|
+
| Workflow engine load | Low | High |
|
|
700
|
+
| **Recommended for** | Full snapshots | Delta feeds only |
|
|
701
|
+
|
|
702
|
+
### Common Mistakes
|
|
703
|
+
|
|
704
|
+
#### ❌ Mistake 1: Using BPP for Delta Feeds
|
|
705
|
+
|
|
706
|
+
```typescript
|
|
707
|
+
// WRONG: Wasting resources on comparison
|
|
708
|
+
const job = await client.createJob({
|
|
709
|
+
name: 'Delta Feed',
|
|
710
|
+
retailerId: '1',
|
|
711
|
+
// BPP enabled by default - unnecessary for delta!
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
await client.sendBatch(job.id, {
|
|
715
|
+
action: 'UPSERT',
|
|
716
|
+
entityType: 'INVENTORY',
|
|
717
|
+
source: 'DELTA_FEED',
|
|
718
|
+
event: 'INVENTORY_DELTA',
|
|
719
|
+
entities: onlyChangedRecords, // Already filtered!
|
|
720
|
+
});
|
|
721
|
+
// Result: BPP compares all records unnecessarily
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
**✅ Fix:**
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
const job = await client.createJob({
|
|
728
|
+
name: 'Delta Feed',
|
|
729
|
+
retailerId: '1',
|
|
730
|
+
meta: {
|
|
731
|
+
preprocessing: 'skip', // ← Skip BPP for delta feeds
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
#### ❌ Mistake 2: Skipping BPP for Full Snapshots
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
// WRONG: Overwhelming workflow with unchanged records
|
|
740
|
+
const job = await client.createJob({
|
|
741
|
+
name: 'Full Inventory',
|
|
742
|
+
retailerId: '1',
|
|
743
|
+
meta: {
|
|
744
|
+
preprocessing: 'skip', // ← Bad for full snapshots!
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
await client.sendBatch(job.id, {
|
|
749
|
+
action: 'UPSERT',
|
|
750
|
+
entityType: 'INVENTORY',
|
|
751
|
+
source: 'FULL_INVENTORY_SYNC',
|
|
752
|
+
event: 'INVENTORY_SNAPSHOT',
|
|
753
|
+
entities: allInventory, // 100K records, 95K unchanged
|
|
754
|
+
});
|
|
755
|
+
// Result: 100K workflow events (95K unnecessary)
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
**✅ Fix:**
|
|
759
|
+
|
|
760
|
+
```typescript
|
|
761
|
+
const job = await client.createJob({
|
|
762
|
+
name: 'Full Inventory',
|
|
763
|
+
retailerId: '1',
|
|
764
|
+
// BPP enabled by default - perfect!
|
|
765
|
+
});
|
|
766
|
+
// Result: Only 5K workflow events (95K filtered by BPP)
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Key Takeaways
|
|
770
|
+
|
|
771
|
+
- 🎯 **BPP = Change Detection**, not data transformation
|
|
772
|
+
- 🎯 **Use BPP** for full inventory snapshots (most common)
|
|
773
|
+
- 🎯 **Skip BPP** for delta feeds (already filtered)
|
|
774
|
+
- 🎯 **Account setting** takes precedence over job setting
|
|
775
|
+
- 🎯 **Default is BPP enabled** (safest for most cases)
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## Batch Size Optimization
|
|
780
|
+
|
|
781
|
+
### Calculate Optimal Batch Size
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
interface BatchSizeConfig {
|
|
785
|
+
maxBatchSize: number; // Maximum records per batch (default: 10000)
|
|
786
|
+
maxPayloadSize: number; // Maximum payload size in bytes (default: 10MB)
|
|
787
|
+
avgRecordSize?: number; // Average record size (auto-calculated if not provided)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function calculateOptimalBatchSize(
|
|
791
|
+
records: any[],
|
|
792
|
+
config: BatchSizeConfig = {
|
|
793
|
+
maxBatchSize: 10000,
|
|
794
|
+
maxPayloadSize: 10 * 1024 * 1024, // 10MB
|
|
795
|
+
}
|
|
796
|
+
): number {
|
|
797
|
+
// Calculate average record size if not provided
|
|
798
|
+
const avgRecordSize = config.avgRecordSize || JSON.stringify(records.slice(0, 100)).length / 100;
|
|
799
|
+
|
|
800
|
+
// Calculate records per max payload
|
|
801
|
+
const recordsPerPayload = Math.floor(config.maxPayloadSize / avgRecordSize);
|
|
802
|
+
|
|
803
|
+
// Return minimum of calculated size and max batch size
|
|
804
|
+
const optimalSize = Math.min(recordsPerPayload, config.maxBatchSize);
|
|
805
|
+
|
|
806
|
+
console.log(`Optimal batch size: ${optimalSize} (based on ${avgRecordSize} bytes/record)`);
|
|
807
|
+
|
|
808
|
+
return optimalSize;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Usage
|
|
812
|
+
const batchSize = calculateOptimalBatchSize(inventoryData);
|
|
813
|
+
const batches = chunkArray(inventoryData, batchSize);
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### Adaptive Batch Sizing
|
|
817
|
+
|
|
818
|
+
```typescript
|
|
819
|
+
class AdaptiveBatchProcessor {
|
|
820
|
+
private currentBatchSize: number;
|
|
821
|
+
private readonly minBatchSize = 100;
|
|
822
|
+
private readonly maxBatchSize = 10000;
|
|
823
|
+
|
|
824
|
+
constructor(initialBatchSize: number = 1000) {
|
|
825
|
+
this.currentBatchSize = initialBatchSize;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
async processBatch(client: FluentClient, jobId: string, data: any[]): Promise<void> {
|
|
829
|
+
const batches = chunkArray(data, this.currentBatchSize);
|
|
830
|
+
|
|
831
|
+
for (const batchData of batches) {
|
|
832
|
+
const startTime = Date.now();
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
const batch = await client.sendBatch(jobId, {
|
|
836
|
+
action: 'UPSERT',
|
|
837
|
+
entityType: 'INVENTORY',
|
|
838
|
+
source: 'ADAPTIVE_BATCH',
|
|
839
|
+
event: 'INVENTORY_BATCH_IMPORT',
|
|
840
|
+
entities: batchData,
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const processingTime = Date.now() - startTime;
|
|
844
|
+
|
|
845
|
+
// Adjust batch size based on performance
|
|
846
|
+
this.adjustBatchSize(processingTime, batchData.length);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
// Reduce batch size on error
|
|
849
|
+
this.currentBatchSize = Math.max(this.minBatchSize, Math.floor(this.currentBatchSize / 2));
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
private adjustBatchSize(processingTime: number, recordCount: number): void {
|
|
856
|
+
const avgTimePerRecord = processingTime / recordCount;
|
|
857
|
+
|
|
858
|
+
// If processing is fast, increase batch size
|
|
859
|
+
if (avgTimePerRecord < 10 && this.currentBatchSize < this.maxBatchSize) {
|
|
860
|
+
this.currentBatchSize = Math.min(this.maxBatchSize, Math.floor(this.currentBatchSize * 1.2));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// If processing is slow, decrease batch size
|
|
864
|
+
if (avgTimePerRecord > 50 && this.currentBatchSize > this.minBatchSize) {
|
|
865
|
+
this.currentBatchSize = Math.max(this.minBatchSize, Math.floor(this.currentBatchSize * 0.8));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## Job Strategies
|
|
874
|
+
|
|
875
|
+
### Strategy 1: DAILY (Recommended for frequent updates)
|
|
876
|
+
|
|
877
|
+
**Use when:**
|
|
878
|
+
|
|
879
|
+
- Multiple files processed per day
|
|
880
|
+
- Want to group all updates under one job
|
|
881
|
+
- Need centralized tracking
|
|
882
|
+
|
|
883
|
+
**Pattern:**
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
import { FluentClient, StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
887
|
+
|
|
888
|
+
async function dailyJobStrategy(
|
|
889
|
+
client: FluentClient,
|
|
890
|
+
files: string[],
|
|
891
|
+
kvAdapter: VersoriKVAdapter,
|
|
892
|
+
state: StateService
|
|
893
|
+
): Promise<void> {
|
|
894
|
+
const workflowId = 'daily-inventory-sync';
|
|
895
|
+
|
|
896
|
+
// Get or create today's job using StateService
|
|
897
|
+
let job = await state.getDailyJob(kvAdapter, workflowId);
|
|
898
|
+
|
|
899
|
+
if (!job) {
|
|
900
|
+
const dateKey = new Date().toISOString().split('T')[0];
|
|
901
|
+
const newJob = await client.createJob({
|
|
902
|
+
name: `Daily Inventory Sync - ${dateKey}`,
|
|
903
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
904
|
+
meta: { date: dateKey, type: 'daily' },
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Store job reference with 24-hour expiration
|
|
908
|
+
await state.setDailyJob(kvAdapter, workflowId, newJob.id, 24);
|
|
909
|
+
job = { jobId: newJob.id };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Process all files with same job
|
|
913
|
+
for (const file of files) {
|
|
914
|
+
const data = await processFile(file);
|
|
915
|
+
|
|
916
|
+
await client.sendBatch(job.jobId, {
|
|
917
|
+
action: 'UPSERT',
|
|
918
|
+
entityType: 'INVENTORY',
|
|
919
|
+
source: 'DAILY_JOB_STRATEGY',
|
|
920
|
+
event: 'INVENTORY_DAILY_SYNC',
|
|
921
|
+
entities: data,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
**Benefits:**
|
|
928
|
+
|
|
929
|
+
- ✅ Single job for all daily updates
|
|
930
|
+
- ✅ Easier monitoring and reporting
|
|
931
|
+
- ✅ Reduced job creation overhead
|
|
932
|
+
|
|
933
|
+
**Drawbacks:**
|
|
934
|
+
|
|
935
|
+
- ❌ Job expiration risk if processing takes too long
|
|
936
|
+
- ❌ All batches fail if job fails
|
|
937
|
+
|
|
938
|
+
### Strategy 2: PER_FILE (Recommended for large datasets)
|
|
939
|
+
|
|
940
|
+
**Use when:**
|
|
941
|
+
|
|
942
|
+
- Processing large files (50k+ records)
|
|
943
|
+
- Want isolated failure handling
|
|
944
|
+
- Need per-file tracking
|
|
945
|
+
|
|
946
|
+
**Pattern:**
|
|
947
|
+
|
|
948
|
+
```typescript
|
|
949
|
+
async function perFileJobStrategy(client: FluentClient, files: string[]): Promise<void> {
|
|
950
|
+
for (const file of files) {
|
|
951
|
+
// Create new job for each file
|
|
952
|
+
const job = await client.createJob({
|
|
953
|
+
name: `Import - ${file}`,
|
|
954
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
955
|
+
metadata: { file, type: 'per-file' },
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const data = await processFile(file);
|
|
959
|
+
const batches = chunkArray(data, 1000);
|
|
960
|
+
|
|
961
|
+
for (const batchData of batches) {
|
|
962
|
+
await client.sendBatch(job.id, {
|
|
963
|
+
action: 'UPSERT',
|
|
964
|
+
entityType: 'INVENTORY',
|
|
965
|
+
source: 'PER_FILE_STRATEGY',
|
|
966
|
+
event: 'INVENTORY_FILE_IMPORT',
|
|
967
|
+
entities: batchData,
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
**Benefits:**
|
|
975
|
+
|
|
976
|
+
- ✅ Isolated failure handling
|
|
977
|
+
- ✅ No job expiration risk
|
|
978
|
+
- ✅ Clear per-file tracking
|
|
979
|
+
|
|
980
|
+
**Drawbacks:**
|
|
981
|
+
|
|
982
|
+
- ❌ More jobs created (overhead)
|
|
983
|
+
- ❌ Harder to aggregate metrics
|
|
984
|
+
|
|
985
|
+
### Strategy 3: BATCHES_PER_JOB (Advanced)
|
|
986
|
+
|
|
987
|
+
**Use when:**
|
|
988
|
+
|
|
989
|
+
- Need balance between DAILY and PER_FILE
|
|
990
|
+
- Want to limit batches per job
|
|
991
|
+
|
|
992
|
+
**Pattern:**
|
|
993
|
+
|
|
994
|
+
```typescript
|
|
995
|
+
async function batchesPerJobStrategy(
|
|
996
|
+
client: FluentClient,
|
|
997
|
+
data: any[],
|
|
998
|
+
maxBatchesPerJob: number = 10
|
|
999
|
+
): Promise<void> {
|
|
1000
|
+
const batchSize = 1000;
|
|
1001
|
+
const batches = chunkArray(data, batchSize);
|
|
1002
|
+
|
|
1003
|
+
let currentJob: any = null;
|
|
1004
|
+
let batchCount = 0;
|
|
1005
|
+
|
|
1006
|
+
for (const batchData of batches) {
|
|
1007
|
+
// Create new job if needed
|
|
1008
|
+
if (!currentJob || batchCount >= maxBatchesPerJob) {
|
|
1009
|
+
currentJob = await client.createJob({
|
|
1010
|
+
name: `Inventory Import - ${Date.now()}`,
|
|
1011
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
1012
|
+
});
|
|
1013
|
+
batchCount = 0;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Send batch
|
|
1017
|
+
await client.sendBatch(currentJob.id, {
|
|
1018
|
+
action: 'UPSERT',
|
|
1019
|
+
entityType: 'INVENTORY',
|
|
1020
|
+
source: 'BATCHES_PER_JOB',
|
|
1021
|
+
event: 'INVENTORY_BATCH_CHUNK',
|
|
1022
|
+
entities: batchData,
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
batchCount++;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
1032
|
+
## Job Expiration Handling
|
|
1033
|
+
|
|
1034
|
+
Jobs expire after a certain time (typically 24 hours). Handle expiration gracefully:
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
async function sendBatchWithExpirationHandling(
|
|
1038
|
+
client: FluentClient,
|
|
1039
|
+
jobId: string,
|
|
1040
|
+
batchData: any[]
|
|
1041
|
+
): Promise<string> {
|
|
1042
|
+
try {
|
|
1043
|
+
const batch = await client.sendBatch(jobId, {
|
|
1044
|
+
action: 'UPSERT',
|
|
1045
|
+
entityType: 'INVENTORY',
|
|
1046
|
+
source: 'EXPIRATION_HANDLING',
|
|
1047
|
+
event: 'INVENTORY_BATCH',
|
|
1048
|
+
entities: batchData,
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
return batch.id;
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
if (error.code === 'JOB_EXPIRED' || error.message.includes('expired')) {
|
|
1054
|
+
console.log('Job expired, creating new job...');
|
|
1055
|
+
|
|
1056
|
+
// Create new job
|
|
1057
|
+
const newJob = await client.createJob({
|
|
1058
|
+
name: `Recovery Job - ${Date.now()}`,
|
|
1059
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
1060
|
+
metadata: {
|
|
1061
|
+
originalJobId: jobId,
|
|
1062
|
+
recoveryReason: 'JOB_EXPIRED',
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// Retry with new job
|
|
1067
|
+
const batch = await client.sendBatch(newJob.id, {
|
|
1068
|
+
action: 'UPSERT',
|
|
1069
|
+
entityType: 'INVENTORY',
|
|
1070
|
+
source: 'EXPIRATION_RECOVERY',
|
|
1071
|
+
event: 'INVENTORY_BATCH_RECOVERY',
|
|
1072
|
+
entities: batchData,
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
return batch.id;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
throw error; // Re-throw if not expiration error
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
---
|
|
1084
|
+
|
|
1085
|
+
## Partial Batch Recovery (NEW)
|
|
1086
|
+
|
|
1087
|
+
Use PartialBatchRecovery to automatically analyze batch failures, separate retryable vs non-retryable records, and resend only what can succeed.
|
|
1088
|
+
|
|
1089
|
+
### Capabilities
|
|
1090
|
+
|
|
1091
|
+
- Analyze batch error responses
|
|
1092
|
+
- Identify retryable vs non-retryable records
|
|
1093
|
+
- Generate retry payloads and rejection reports
|
|
1094
|
+
- Support new-job or append-to-existing strategies
|
|
1095
|
+
|
|
1096
|
+
### Example
|
|
1097
|
+
|
|
1098
|
+
```typescript
|
|
1099
|
+
import { PartialBatchRecovery } from '@fluentcommerce/fc-connect-sdk';
|
|
1100
|
+
|
|
1101
|
+
// After a batch completes with failures
|
|
1102
|
+
const status = await client.getBatchStatus(job.id, batch.id);
|
|
1103
|
+
|
|
1104
|
+
if (status.status === 'FAILED' && Array.isArray(status.errors) && status.errors.length > 0) {
|
|
1105
|
+
const recovery = new PartialBatchRecovery(logger);
|
|
1106
|
+
|
|
1107
|
+
// 'originalEntities' is the array you sent in this batch
|
|
1108
|
+
const analysis = recovery.analyze(status.errors, originalEntities);
|
|
1109
|
+
|
|
1110
|
+
// Retry only retryable records
|
|
1111
|
+
if (analysis.retryable.length > 0) {
|
|
1112
|
+
const recoveryJob = await client.createJob({
|
|
1113
|
+
name: `Recovery - ${job.id}`,
|
|
1114
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
1115
|
+
meta: { originalJobId: job.id, recoveryOfBatchId: batch.id },
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
await client.sendBatch(recoveryJob.id, {
|
|
1119
|
+
action: 'UPSERT',
|
|
1120
|
+
entityType: 'INVENTORY',
|
|
1121
|
+
source: 'PARTIAL_BATCH_RECOVERY',
|
|
1122
|
+
event: 'INVENTORY_RECOVERY',
|
|
1123
|
+
entities: analysis.retryable,
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Persist non-retryable errors for investigation
|
|
1128
|
+
if (analysis.nonRetryable.length > 0) {
|
|
1129
|
+
await saveRejectionReport(job.id, batch.id, analysis.nonRetryable);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
Notes:
|
|
1135
|
+
|
|
1136
|
+
- Typical retryable categories: transient network errors, rate limits, temporary validation states
|
|
1137
|
+
- Typical non-retryable categories: schema violations, permanently invalid data
|
|
1138
|
+
- Always store a rejection report for auditing and manual remediation
|
|
1139
|
+
|
|
1140
|
+
---
|
|
1141
|
+
|
|
1142
|
+
## Comprehensive Error Handling Patterns
|
|
1143
|
+
|
|
1144
|
+
### Pattern 1: Batch Processing with Rate Limiting
|
|
1145
|
+
|
|
1146
|
+
```typescript
|
|
1147
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
1148
|
+
|
|
1149
|
+
async function processBatchWithRateLimiting(
|
|
1150
|
+
client: any,
|
|
1151
|
+
jobId: string,
|
|
1152
|
+
entities: any[],
|
|
1153
|
+
logger: any
|
|
1154
|
+
) {
|
|
1155
|
+
try {
|
|
1156
|
+
const batch = await client.sendBatch(jobId, {
|
|
1157
|
+
action: 'UPSERT',
|
|
1158
|
+
entityType: 'INVENTORY',
|
|
1159
|
+
source: 'BATCH_PROCESSING',
|
|
1160
|
+
event: 'INVENTORY_BATCH',
|
|
1161
|
+
entities
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
logger.info('Batch sent successfully', {
|
|
1165
|
+
batchId: batch.id,
|
|
1166
|
+
recordCount: entities.length
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
return batch.id;
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
logger.error('Batch submission failed', {
|
|
1172
|
+
jobId,
|
|
1173
|
+
recordCount: entities.length,
|
|
1174
|
+
error: error.message,
|
|
1175
|
+
errorCode: error.code
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
if (error.code === 'RATE_LIMIT_ERROR' || error.message.includes('rate limit')) {
|
|
1179
|
+
logger.warn('Rate limit hit, implementing backoff', { jobId });
|
|
1180
|
+
|
|
1181
|
+
// Exponential backoff retry
|
|
1182
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
const retryBatch = await client.sendBatch(jobId, {
|
|
1186
|
+
action: 'UPSERT',
|
|
1187
|
+
entityType: 'INVENTORY',
|
|
1188
|
+
source: 'BATCH_RETRY',
|
|
1189
|
+
event: 'INVENTORY_BATCH_RETRY',
|
|
1190
|
+
entities
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
logger.info('Batch sent successfully after retry', {
|
|
1194
|
+
batchId: retryBatch.id
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
return retryBatch.id;
|
|
1198
|
+
} catch (retryError) {
|
|
1199
|
+
logger.error('Batch failed after retry', {
|
|
1200
|
+
jobId,
|
|
1201
|
+
error: retryError.message
|
|
1202
|
+
});
|
|
1203
|
+
throw retryError;
|
|
1204
|
+
}
|
|
1205
|
+
} else if (error.code === 'VALIDATION_ERROR') {
|
|
1206
|
+
logger.error('Batch validation failed - data does not match schema', {
|
|
1207
|
+
jobId,
|
|
1208
|
+
error: error.message
|
|
1209
|
+
});
|
|
1210
|
+
throw new Error('Validation error: Check data schema compatibility');
|
|
1211
|
+
} else {
|
|
1212
|
+
throw error;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
### Pattern 2: Network Error Recovery
|
|
1219
|
+
|
|
1220
|
+
```typescript
|
|
1221
|
+
async function executeWithNetworkRetry<T>(
|
|
1222
|
+
operation: () => Promise<T>,
|
|
1223
|
+
operationName: string,
|
|
1224
|
+
logger: any,
|
|
1225
|
+
maxRetries: number = 3
|
|
1226
|
+
): Promise<T> {
|
|
1227
|
+
let lastError: Error;
|
|
1228
|
+
|
|
1229
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1230
|
+
try {
|
|
1231
|
+
return await operation();
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
lastError = error;
|
|
1234
|
+
|
|
1235
|
+
logger.warn(`${operationName} failed`, {
|
|
1236
|
+
attempt: attempt + 1,
|
|
1237
|
+
maxRetries,
|
|
1238
|
+
error: error.message
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
// Check if error is retryable
|
|
1242
|
+
const isRetryable =
|
|
1243
|
+
error.code === 'ECONNRESET' ||
|
|
1244
|
+
error.code === 'ETIMEDOUT' ||
|
|
1245
|
+
error.message.includes('network') ||
|
|
1246
|
+
error.message.includes('timeout');
|
|
1247
|
+
|
|
1248
|
+
if (!isRetryable || attempt === maxRetries - 1) {
|
|
1249
|
+
throw lastError;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Exponential backoff
|
|
1253
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
1254
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
throw lastError!;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Usage
|
|
1262
|
+
const batch = await executeWithNetworkRetry(
|
|
1263
|
+
() => client.sendBatch(jobId, payload),
|
|
1264
|
+
'sendBatch',
|
|
1265
|
+
logger
|
|
1266
|
+
);
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
---
|
|
1270
|
+
|
|
1271
|
+
## Key Takeaways
|
|
1272
|
+
|
|
1273
|
+
- 🎯 **Batch API is INVENTORY only** - No orders, products, or locations
|
|
1274
|
+
- 🎯 **Choose right job strategy** - DAILY for frequent updates, PER_FILE for large datasets
|
|
1275
|
+
- 🎯 **Optimize batch sizes** - Balance payload size and API limits
|
|
1276
|
+
- 🎯 **Handle job expiration** - Implement recovery for long-running processes
|
|
1277
|
+
- 🎯 **Monitor status properly** - Poll batch and job status with timeouts
|
|
1278
|
+
- 🎯 **Implement error recovery** - Retry transient errors, log permanent failures
|
|
1279
|
+
- 🎯 **Use structured logging** - Include context in all error logs
|
|
1280
|
+
|
|
1281
|
+
---
|
|
1282
|
+
|
|
1283
|
+
## Next Steps
|
|
1284
|
+
|
|
1285
|
+
Continue to [Module 7: State Management](./02-core-guides-ingestion-07-state-management.md) to learn how to prevent duplicate processing with state tracking.
|
|
1286
|
+
|
|
1287
|
+
---
|
|
1288
|
+
|
|
1289
|
+
[← Previous: Field Mapping](02-core-guides-ingestion-04-field-mapping.md) | [Back to Guide](../ingestion-readme.md) | [Next: State Management →](./02-core-guides-ingestion-07-state-management.md)
|
|
1290
|
+
|
|
1291
|
+
## Related Documentation
|
|
1292
|
+
|
|
1293
|
+
- [Batch API Reference](../../api-reference/modules/api-reference-01-client-api.md#job-batch-operations) - Complete API documentation
|
|
1294
|
+
- [Job Strategies](02-core-guides-ingestion-08-performance-optimization.md#job-strategy-comparison) - Detailed strategy comparison
|
|
1295
|
+
- [Schema Validation](../../mapping/mapping-readme.md#04-REFERENCE/schema-validation) - Validate mappings
|