@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,650 +1,650 @@
|
|
|
1
|
-
# Module 4: Advanced Scenarios
|
|
2
|
-
|
|
3
|
-
**Level:** Advanced
|
|
4
|
-
**Estimated Time:** 60 minutes
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
This module covers advanced connector patterns including state management with checkpoints, multi-source aggregation, and large file streaming. These patterns are essential for production-grade connectors.
|
|
9
|
-
|
|
10
|
-
## Learning Objectives
|
|
11
|
-
|
|
12
|
-
By the end of this module, you will:
|
|
13
|
-
|
|
14
|
-
- ✅ Implement record-level state management with checkpoints
|
|
15
|
-
- ✅ Handle large files with streaming and chunking
|
|
16
|
-
- ✅ Aggregate data from multiple sources
|
|
17
|
-
- ✅ Reconcile differences between sources and Fluent
|
|
18
|
-
- ✅ Implement recovery from partial failures
|
|
19
|
-
- ✅ Optimize memory usage for large datasets
|
|
20
|
-
|
|
21
|
-
## Scenario 4: S3 CSV Batch Processing with State Management
|
|
22
|
-
|
|
23
|
-
**Goal:** Process large CSV files from S3 with state tracking to handle interruptions and prevent duplicates.
|
|
24
|
-
|
|
25
|
-
**Complexity:** High
|
|
26
|
-
**Components:** S3DataSource, CSVParserService, StateService, FluentClient
|
|
27
|
-
**Estimated Lines:** ~400 lines
|
|
28
|
-
|
|
29
|
-
### Problem Statement
|
|
30
|
-
|
|
31
|
-
You need to process CSV files that may contain 100K+ records. Requirements:
|
|
32
|
-
|
|
33
|
-
1. Stream large files (don't load all into memory)
|
|
34
|
-
2. Process in batches of 500 records
|
|
35
|
-
3. Save checkpoint after each batch
|
|
36
|
-
4. Resume from checkpoint on failure
|
|
37
|
-
5. Prevent duplicate processing
|
|
38
|
-
|
|
39
|
-
### Key Implementation Pattern
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
import {
|
|
43
|
-
createClient,
|
|
44
|
-
S3DataSource,
|
|
45
|
-
CSVParserService,
|
|
46
|
-
StateService,
|
|
47
|
-
BatchAPIClient,
|
|
48
|
-
createConsoleLogger,
|
|
49
|
-
toStructuredLogger
|
|
50
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
51
|
-
|
|
52
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
53
|
-
service: 's3-batch-processing',
|
|
54
|
-
module: 'advanced-scenarios'
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Initialize State Service
|
|
58
|
-
const stateService = new StateService(logger);
|
|
59
|
-
|
|
60
|
-
// For checkpoints, use KV directly:
|
|
61
|
-
// const kv = openKv();
|
|
62
|
-
// await kv.set(['checkpoint', jobId], { recordsProcessed, lastRecordId, timestamp });
|
|
63
|
-
|
|
64
|
-
// S3 Source for CSV files
|
|
65
|
-
const s3Source = new S3DataSource(
|
|
66
|
-
{
|
|
67
|
-
type: 'S3_CSV',
|
|
68
|
-
connectionId: 's3-inventory',
|
|
69
|
-
name: 'Inventory S3',
|
|
70
|
-
settings: {
|
|
71
|
-
bucketName: 'inventory-updates',
|
|
72
|
-
region: 'us-east-1',
|
|
73
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
74
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
75
|
-
prefix: 'daily/',
|
|
76
|
-
filePattern: '*.csv',
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
logger
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
// CSV Parser configuration
|
|
83
|
-
// Constructor CAN take logger as first parameter (optional)
|
|
84
|
-
const csvParser = new CSVParserService(logger);
|
|
85
|
-
// Parse options passed to parse() method
|
|
86
|
-
|
|
87
|
-
const fluentClient = await createClient({
|
|
88
|
-
config: {
|
|
89
|
-
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
90
|
-
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
91
|
-
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
92
|
-
username: process.env.FLUENT_USERNAME!,
|
|
93
|
-
password: process.env.FLUENT_PASSWORD!,
|
|
94
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Manual workflow composition for full control
|
|
99
|
-
async function processLargeCSVBatch() {
|
|
100
|
-
logger.info('Starting large CSV batch processing');
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
// Get checkpoint from KV (for resuming)
|
|
104
|
-
// Use KV adapter directly for checkpoints
|
|
105
|
-
const kv = openKv();
|
|
106
|
-
const checkpointData = await kv.get(['checkpoint', 'inventory-batch']);
|
|
107
|
-
const checkpoint = checkpointData?.value;
|
|
108
|
-
|
|
109
|
-
// List CSV files
|
|
110
|
-
const files = await s3Source.listFiles({
|
|
111
|
-
prefix: 'daily/',
|
|
112
|
-
lastProcessedTimestamp: checkpoint?.lastProcessedTimestamp,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
logger.info(`Found ${files.length} files to process`);
|
|
116
|
-
|
|
117
|
-
for (const file of files) {
|
|
118
|
-
// Check if file was already processed (using KV directly)
|
|
119
|
-
const fileStateData = await kv.get(['file-state', file.name]);
|
|
120
|
-
const fileState = fileStateData?.value;
|
|
121
|
-
if (fileState?.status === 'completed') {
|
|
122
|
-
logger.info(`Skipping already processed file: ${file.name}`);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
logger.info(`Processing large file: ${file.name} (${file.size} bytes)`);
|
|
127
|
-
|
|
128
|
-
// Mark file as in-progress (using KV directly)
|
|
129
|
-
await kv.set(['file-state', file.name], {
|
|
130
|
-
status: 'in-progress',
|
|
131
|
-
startedAt: new Date().toISOString(),
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
// Stream large CSV file
|
|
136
|
-
const stream = await s3Source.getFileStream(file.name);
|
|
137
|
-
|
|
138
|
-
let batchNumber = 0;
|
|
139
|
-
let totalProcessed = 0;
|
|
140
|
-
let buffer: any[] = [];
|
|
141
|
-
|
|
142
|
-
// Process streaming data with async iterator
|
|
143
|
-
// Using CSVParserService.parseStreamStreaming for memory-efficient processing
|
|
144
|
-
for await (const record of csvParser.parseStreamStreaming(stream, {}, 1)) {
|
|
145
|
-
buffer.push(record);
|
|
146
|
-
|
|
147
|
-
// Process buffer when it reaches batch size
|
|
148
|
-
if (buffer.length >= 500) {
|
|
149
|
-
await processBatch(buffer, file.name, ++batchNumber);
|
|
150
|
-
totalProcessed += buffer.length;
|
|
151
|
-
buffer = [];
|
|
152
|
-
|
|
153
|
-
// Update checkpoint (using KV directly)
|
|
154
|
-
await kv.set(['checkpoint', 'inventory-batch'], {
|
|
155
|
-
file: file.name,
|
|
156
|
-
recordsProcessed: totalProcessed,
|
|
157
|
-
lastBatch: batchNumber,
|
|
158
|
-
timestamp: new Date().toISOString(),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Process remaining records
|
|
164
|
-
if (buffer.length > 0) {
|
|
165
|
-
await processBatch(buffer, file.name, ++batchNumber);
|
|
166
|
-
totalProcessed += buffer.length;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Mark file as completed (using KV directly)
|
|
170
|
-
await kv.set(['file-state', file.name], {
|
|
171
|
-
status: 'completed',
|
|
172
|
-
completedAt: new Date().toISOString(),
|
|
173
|
-
recordsProcessed: totalProcessed,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
logger.info(`File completed: ${file.name}, processed ${totalProcessed} records`);
|
|
177
|
-
|
|
178
|
-
// Archive processed file
|
|
179
|
-
await s3Source.moveFile(
|
|
180
|
-
file.name,
|
|
181
|
-
`archive/${new Date().toISOString().split('T')[0]}/${file.name}`
|
|
182
|
-
);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
logger.error(`Failed to process file ${file.name}:`, error);
|
|
185
|
-
|
|
186
|
-
// Mark file as failed (using KV directly)
|
|
187
|
-
await kv.set(['file-state', file.name], {
|
|
188
|
-
status: 'failed',
|
|
189
|
-
error: (error as Error).message,
|
|
190
|
-
failedAt: new Date().toISOString(),
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Move to error bucket
|
|
194
|
-
await s3Source.copyFile(file.name, `errors/${file.name}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
logger.info('Batch processing completed');
|
|
199
|
-
} catch (error) {
|
|
200
|
-
logger.error('Batch processing failed:', error);
|
|
201
|
-
throw error;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function processBatch(records: any[], fileName: string, batchNumber: number) {
|
|
206
|
-
logger.info(`Processing batch ${batchNumber} with ${records.length} records`);
|
|
207
|
-
|
|
208
|
-
// Transform records
|
|
209
|
-
const inventoryUpdates = records.map(record => ({
|
|
210
|
-
ref: record.sku,
|
|
211
|
-
productRef: record.sku,
|
|
212
|
-
locationRef: record.location,
|
|
213
|
-
qty: parseInt(record.quantity),
|
|
214
|
-
status: record.status || 'ACTIVE',
|
|
215
|
-
type: 'ADJUSTMENT',
|
|
216
|
-
attributes: {
|
|
217
|
-
source: 'S3_CSV',
|
|
218
|
-
file: fileName,
|
|
219
|
-
batch: batchNumber,
|
|
220
|
-
},
|
|
221
|
-
}));
|
|
222
|
-
|
|
223
|
-
// Create job for this batch
|
|
224
|
-
const job = await fluentClient.createJob({
|
|
225
|
-
name: `Inventory Batch ${fileName} - ${batchNumber}`,
|
|
226
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Send batch to Fluent
|
|
230
|
-
await fluentClient.sendBatch(job.id, {
|
|
231
|
-
action: 'UPSERT',
|
|
232
|
-
entityType: 'INVENTORY',
|
|
233
|
-
entities: inventoryUpdates,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Poll status with getBatchStatus if needed; no explicit update call is required
|
|
237
|
-
|
|
238
|
-
logger.info(`Batch ${batchNumber} completed`);
|
|
239
|
-
|
|
240
|
-
return inventoryUpdates.length;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Execute with error recovery
|
|
244
|
-
async function runWithRecovery() {
|
|
245
|
-
const maxRetries = 3;
|
|
246
|
-
let attempt = 0;
|
|
247
|
-
|
|
248
|
-
while (attempt < maxRetries) {
|
|
249
|
-
try {
|
|
250
|
-
await processLargeCSVBatch();
|
|
251
|
-
break;
|
|
252
|
-
} catch (error) {
|
|
253
|
-
attempt++;
|
|
254
|
-
logger.error(`Attempt ${attempt} failed:`, error);
|
|
255
|
-
|
|
256
|
-
if (attempt >= maxRetries) {
|
|
257
|
-
logger.error('Max retries reached, giving up');
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Wait before retry (exponential backoff)
|
|
262
|
-
await new Promise(resolve => setTimeout(resolve, 5000 * attempt));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
runWithRecovery();
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Key Concepts: State Management
|
|
271
|
-
|
|
272
|
-
**Checkpoint Strategy:**
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
// Use KV adapter directly for checkpoints
|
|
276
|
-
const kv = openKv();
|
|
277
|
-
|
|
278
|
-
// Save checkpoint after each batch (using KV directly)
|
|
279
|
-
await kv.set(['checkpoint', 'job-id'], {
|
|
280
|
-
file: currentFile,
|
|
281
|
-
lastRecordId: record.id,
|
|
282
|
-
recordsProcessed: count,
|
|
283
|
-
timestamp: new Date().toISOString(),
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// Resume from checkpoint on failure
|
|
287
|
-
const checkpointData = await kv.get(['checkpoint', 'job-id']);
|
|
288
|
-
const checkpoint = checkpointData?.value;
|
|
289
|
-
if (checkpoint) {
|
|
290
|
-
logger.info(`Resuming from batch ${checkpoint.lastBatch}`);
|
|
291
|
-
startFrom = checkpoint.recordsProcessed;
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
**Streaming Pattern:**
|
|
296
|
-
|
|
297
|
-
```typescript
|
|
298
|
-
// Don't load entire file into memory
|
|
299
|
-
const stream = await s3Source.getFileStream(file.name);
|
|
300
|
-
let buffer = [];
|
|
301
|
-
|
|
302
|
-
// Use CSVParserService.parseStreamStreaming for memory-efficient processing
|
|
303
|
-
const csvParser = new CSVParserService();
|
|
304
|
-
|
|
305
|
-
for await (const record of csvParser.parseStreamStreaming(stream, {}, 1)) {
|
|
306
|
-
buffer.push(record);
|
|
307
|
-
|
|
308
|
-
if (buffer.length >= BATCH_SIZE) {
|
|
309
|
-
await processBatch(buffer); // Process batch
|
|
310
|
-
buffer = []; // Clear buffer
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Process remaining records
|
|
315
|
-
if (buffer.length > 0) {
|
|
316
|
-
await processBatch(buffer);
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## Scenario 5: Multi-Source Inventory Aggregation
|
|
321
|
-
|
|
322
|
-
**Goal:** Aggregate inventory from both SFTP and S3 sources, reconcile differences, and update Fluent.
|
|
323
|
-
|
|
324
|
-
**Complexity:** High
|
|
325
|
-
**Components:** SftpDataSource, S3DataSource, Multiple parsers, Reconciliation logic
|
|
326
|
-
**Estimated Lines:** ~350 lines
|
|
327
|
-
|
|
328
|
-
### Problem Statement
|
|
329
|
-
|
|
330
|
-
Inventory data comes from multiple sources:
|
|
331
|
-
|
|
332
|
-
- **SFTP:** Warehouse inventory (CSV)
|
|
333
|
-
- **S3:** Store inventory (JSON)
|
|
334
|
-
|
|
335
|
-
You need to:
|
|
336
|
-
|
|
337
|
-
1. Collect from both sources
|
|
338
|
-
2. Merge by SKU + Location
|
|
339
|
-
3. Compare with current Fluent inventory
|
|
340
|
-
4. Send only differences (deltas)
|
|
341
|
-
|
|
342
|
-
### Key Implementation Pattern
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
import {
|
|
346
|
-
createClient,
|
|
347
|
-
SftpDataSource,
|
|
348
|
-
S3DataSource,
|
|
349
|
-
createConsoleLogger,
|
|
350
|
-
toStructuredLogger
|
|
351
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
352
|
-
|
|
353
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
354
|
-
service: 'multi-source-aggregation',
|
|
355
|
-
module: 'advanced-scenarios'
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
// Multiple data sources
|
|
359
|
-
const sftpSource = new SftpDataSource(
|
|
360
|
-
{
|
|
361
|
-
type: 'SFTP_CSV',
|
|
362
|
-
connectionId: 'warehouse-sftp',
|
|
363
|
-
name: 'Warehouse SFTP',
|
|
364
|
-
settings: {
|
|
365
|
-
host: process.env.WAREHOUSE_SFTP_HOST!,
|
|
366
|
-
username: process.env.WAREHOUSE_SFTP_USER!,
|
|
367
|
-
password: process.env.WAREHOUSE_SFTP_PASS!,
|
|
368
|
-
remotePath: '/inventory',
|
|
369
|
-
filePattern: '*.csv',
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
logger
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
const s3Source = new S3DataSource(
|
|
376
|
-
{
|
|
377
|
-
type: 'S3_JSON',
|
|
378
|
-
connectionId: 'store-s3',
|
|
379
|
-
name: 'Store Inventory S3',
|
|
380
|
-
settings: {
|
|
381
|
-
bucketName: 'store-inventory',
|
|
382
|
-
region: 'us-east-1',
|
|
383
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
384
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
385
|
-
prefix: 'stores/',
|
|
386
|
-
filePattern: '*.json',
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
logger
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
const fluentClient = await createClient({
|
|
393
|
-
config: {
|
|
394
|
-
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
395
|
-
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
396
|
-
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
397
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
async function aggregateInventory() {
|
|
402
|
-
logger.info('Starting multi-source inventory aggregation');
|
|
403
|
-
|
|
404
|
-
try {
|
|
405
|
-
// Collect inventory from all sources
|
|
406
|
-
const inventoryMap = new Map<
|
|
407
|
-
string,
|
|
408
|
-
{
|
|
409
|
-
sku: string;
|
|
410
|
-
locations: Map<string, number>;
|
|
411
|
-
sources: Set<string>;
|
|
412
|
-
}
|
|
413
|
-
>();
|
|
414
|
-
|
|
415
|
-
// 1. Process SFTP warehouse data
|
|
416
|
-
logger.info('Processing warehouse inventory from SFTP');
|
|
417
|
-
const sftpFiles = await sftpSource.listFiles();
|
|
418
|
-
|
|
419
|
-
for (const file of sftpFiles) {
|
|
420
|
-
const content = await sftpSource.downloadFile(file.name);
|
|
421
|
-
const parser = new CSVParserService();
|
|
422
|
-
const records = await parser.parse(content as string);
|
|
423
|
-
|
|
424
|
-
records.forEach(record => {
|
|
425
|
-
const key = record.sku;
|
|
426
|
-
if (!inventoryMap.has(key)) {
|
|
427
|
-
inventoryMap.set(key, {
|
|
428
|
-
sku: record.sku,
|
|
429
|
-
locations: new Map(),
|
|
430
|
-
sources: new Set(),
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const item = inventoryMap.get(key)!;
|
|
435
|
-
const currentQty = item.locations.get(record.location) || 0;
|
|
436
|
-
item.locations.set(record.location, currentQty + parseInt(record.quantity));
|
|
437
|
-
item.sources.add('WAREHOUSE_SFTP');
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// 2. Process S3 store data
|
|
442
|
-
logger.info('Processing store inventory from S3');
|
|
443
|
-
const s3Files = await s3Source.listFiles();
|
|
444
|
-
|
|
445
|
-
for (const file of s3Files) {
|
|
446
|
-
const content = await s3Source.downloadFile(file.name);
|
|
447
|
-
const storeData = JSON.parse(content.toString());
|
|
448
|
-
|
|
449
|
-
storeData.inventory.forEach((record: any) => {
|
|
450
|
-
const key = record.productId;
|
|
451
|
-
if (!inventoryMap.has(key)) {
|
|
452
|
-
inventoryMap.set(key, {
|
|
453
|
-
sku: record.productId,
|
|
454
|
-
locations: new Map(),
|
|
455
|
-
sources: new Set(),
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const item = inventoryMap.get(key)!;
|
|
460
|
-
const currentQty = item.locations.get(record.storeId) || 0;
|
|
461
|
-
item.locations.set(record.storeId, currentQty + record.availableQty);
|
|
462
|
-
item.sources.add('STORE_S3');
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// 3. Query current Fluent inventory for reconciliation
|
|
467
|
-
logger.info('Fetching current Fluent inventory');
|
|
468
|
-
const query = `
|
|
469
|
-
query GetCurrentInventory($retailerId: ID!, $first: Int!) {
|
|
470
|
-
inventoryPositions(retailerId: $retailerId, first: $first) {
|
|
471
|
-
edges {
|
|
472
|
-
node {
|
|
473
|
-
ref
|
|
474
|
-
productRef
|
|
475
|
-
locationRef
|
|
476
|
-
qty
|
|
477
|
-
availableQty
|
|
478
|
-
}
|
|
479
|
-
cursor
|
|
480
|
-
}
|
|
481
|
-
pageInfo { hasNextPage }
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
`;
|
|
485
|
-
|
|
486
|
-
const currentInventory = await fluentClient.graphql({
|
|
487
|
-
query,
|
|
488
|
-
variables: {
|
|
489
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
490
|
-
first: 1000,
|
|
491
|
-
},
|
|
492
|
-
pagination: { maxPages: 10 },
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// 4. Reconcile and prepare updates
|
|
496
|
-
const updates: any[] = [];
|
|
497
|
-
const reconciliationReport: any[] = [];
|
|
498
|
-
|
|
499
|
-
inventoryMap.forEach((aggregated, sku) => {
|
|
500
|
-
aggregated.locations.forEach((newQty, location) => {
|
|
501
|
-
// Find current Fluent inventory
|
|
502
|
-
const current = currentInventory.data.inventoryPositions.edges.find(
|
|
503
|
-
(edge: any) => edge.node.productRef === sku && edge.node.locationRef === location
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
const currentQty = current?.node.qty || 0;
|
|
507
|
-
const difference = newQty - currentQty;
|
|
508
|
-
|
|
509
|
-
if (difference !== 0) {
|
|
510
|
-
updates.push({
|
|
511
|
-
ref: `${sku}-${location}`,
|
|
512
|
-
productRef: sku,
|
|
513
|
-
locationRef: location,
|
|
514
|
-
qty: newQty,
|
|
515
|
-
type: difference > 0 ? 'RECEIPT' : 'DISPATCH',
|
|
516
|
-
adjustmentQty: Math.abs(difference),
|
|
517
|
-
reason: 'MULTI_SOURCE_SYNC',
|
|
518
|
-
attributes: {
|
|
519
|
-
sources: Array.from(aggregated.sources).join(','),
|
|
520
|
-
previousQty: currentQty,
|
|
521
|
-
newQty: newQty,
|
|
522
|
-
syncedAt: new Date().toISOString(),
|
|
523
|
-
},
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
reconciliationReport.push({
|
|
527
|
-
sku,
|
|
528
|
-
location,
|
|
529
|
-
previousQty: currentQty,
|
|
530
|
-
newQty: newQty,
|
|
531
|
-
difference,
|
|
532
|
-
sources: Array.from(aggregated.sources),
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
logger.info(`Found ${updates.length} inventory differences to sync`);
|
|
539
|
-
|
|
540
|
-
// 5. Apply updates to Fluent
|
|
541
|
-
if (updates.length > 0) {
|
|
542
|
-
const job = await fluentClient.createJob({
|
|
543
|
-
name: `Multi-Source Inventory Sync - ${new Date().toISOString()}`,
|
|
544
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
// Process in batches
|
|
548
|
-
const batchSize = 100;
|
|
549
|
-
for (let i = 0; i < updates.length; i += batchSize) {
|
|
550
|
-
const batch = updates.slice(i, i + batchSize);
|
|
551
|
-
|
|
552
|
-
await fluentClient.sendBatch(job.id, {
|
|
553
|
-
action: 'UPSERT',
|
|
554
|
-
entityType: 'INVENTORY',
|
|
555
|
-
entities: batch,
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
logger.info(`Batch ${Math.floor(i / batchSize) + 1} processed`);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Optional: monitor batch status via getBatchStatus
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// 6. Generate and save reconciliation report
|
|
565
|
-
const report = {
|
|
566
|
-
timestamp: new Date().toISOString(),
|
|
567
|
-
sourcesProcessed: {
|
|
568
|
-
sftp: sftpFiles.length,
|
|
569
|
-
s3: s3Files.length,
|
|
570
|
-
},
|
|
571
|
-
totalSKUs: inventoryMap.size,
|
|
572
|
-
totalUpdates: updates.length,
|
|
573
|
-
reconciliation: reconciliationReport,
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
// Save report to S3
|
|
577
|
-
await s3Source.uploadFile(
|
|
578
|
-
`reports/reconciliation-${new Date().toISOString()}.json`,
|
|
579
|
-
JSON.stringify(report, null, 2)
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
logger.info('Multi-source inventory aggregation completed', {
|
|
583
|
-
totalSKUs: inventoryMap.size,
|
|
584
|
-
totalUpdates: updates.length,
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
return report;
|
|
588
|
-
} catch (error) {
|
|
589
|
-
logger.error('Aggregation failed:', error);
|
|
590
|
-
throw error;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Schedule hourly
|
|
595
|
-
setInterval(aggregateInventory, 60 * 60 * 1000);
|
|
596
|
-
aggregateInventory();
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Key Concepts: Multi-Source Aggregation
|
|
600
|
-
|
|
601
|
-
**Data Collection Pattern:**
|
|
602
|
-
|
|
603
|
-
```typescript
|
|
604
|
-
// Use Map for efficient lookups and aggregation
|
|
605
|
-
const inventoryMap = new Map<
|
|
606
|
-
string,
|
|
607
|
-
{
|
|
608
|
-
sku: string;
|
|
609
|
-
locations: Map<string, number>;
|
|
610
|
-
sources: Set<string>;
|
|
611
|
-
}
|
|
612
|
-
>();
|
|
613
|
-
|
|
614
|
-
// Aggregate from multiple sources
|
|
615
|
-
sources.forEach(source => {
|
|
616
|
-
source.records.forEach(record => {
|
|
617
|
-
const item = inventoryMap.get(record.sku) || createNewItem();
|
|
618
|
-
item.locations.set(record.location, record.qty);
|
|
619
|
-
item.sources.add(source.name);
|
|
620
|
-
});
|
|
621
|
-
});
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
**Reconciliation Pattern:**
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
// Compare aggregated vs current
|
|
628
|
-
const difference = newQty - currentQty;
|
|
629
|
-
|
|
630
|
-
if (difference !== 0) {
|
|
631
|
-
updates.push({
|
|
632
|
-
qty: newQty,
|
|
633
|
-
type: difference > 0 ? 'RECEIPT' : 'DISPATCH',
|
|
634
|
-
adjustmentQty: Math.abs(difference),
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
## Key Takeaways
|
|
640
|
-
|
|
641
|
-
- 🎯 Stream large files to avoid memory issues (don't load all at once)
|
|
642
|
-
- 🎯 Save checkpoints frequently to enable recovery from failures
|
|
643
|
-
- 🎯 Multi-source aggregation requires careful reconciliation logic
|
|
644
|
-
- 🎯 Always track differences, not just totals (delta updates)
|
|
645
|
-
- 🎯 State management is critical for production reliability
|
|
646
|
-
- 🎯 Generate reports for visibility into sync operations
|
|
647
|
-
|
|
648
|
-
## Next Steps
|
|
649
|
-
|
|
650
|
-
Continue to [Module 5: Bidirectional Sync](./connector-scenarios-05-bidirectional-sync.md) to learn extraction patterns and Fluent-to-external system exports.
|
|
1
|
+
# Module 4: Advanced Scenarios
|
|
2
|
+
|
|
3
|
+
**Level:** Advanced
|
|
4
|
+
**Estimated Time:** 60 minutes
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This module covers advanced connector patterns including state management with checkpoints, multi-source aggregation, and large file streaming. These patterns are essential for production-grade connectors.
|
|
9
|
+
|
|
10
|
+
## Learning Objectives
|
|
11
|
+
|
|
12
|
+
By the end of this module, you will:
|
|
13
|
+
|
|
14
|
+
- ✅ Implement record-level state management with checkpoints
|
|
15
|
+
- ✅ Handle large files with streaming and chunking
|
|
16
|
+
- ✅ Aggregate data from multiple sources
|
|
17
|
+
- ✅ Reconcile differences between sources and Fluent
|
|
18
|
+
- ✅ Implement recovery from partial failures
|
|
19
|
+
- ✅ Optimize memory usage for large datasets
|
|
20
|
+
|
|
21
|
+
## Scenario 4: S3 CSV Batch Processing with State Management
|
|
22
|
+
|
|
23
|
+
**Goal:** Process large CSV files from S3 with state tracking to handle interruptions and prevent duplicates.
|
|
24
|
+
|
|
25
|
+
**Complexity:** High
|
|
26
|
+
**Components:** S3DataSource, CSVParserService, StateService, FluentClient
|
|
27
|
+
**Estimated Lines:** ~400 lines
|
|
28
|
+
|
|
29
|
+
### Problem Statement
|
|
30
|
+
|
|
31
|
+
You need to process CSV files that may contain 100K+ records. Requirements:
|
|
32
|
+
|
|
33
|
+
1. Stream large files (don't load all into memory)
|
|
34
|
+
2. Process in batches of 500 records
|
|
35
|
+
3. Save checkpoint after each batch
|
|
36
|
+
4. Resume from checkpoint on failure
|
|
37
|
+
5. Prevent duplicate processing
|
|
38
|
+
|
|
39
|
+
### Key Implementation Pattern
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import {
|
|
43
|
+
createClient,
|
|
44
|
+
S3DataSource,
|
|
45
|
+
CSVParserService,
|
|
46
|
+
StateService,
|
|
47
|
+
BatchAPIClient,
|
|
48
|
+
createConsoleLogger,
|
|
49
|
+
toStructuredLogger
|
|
50
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
51
|
+
|
|
52
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
53
|
+
service: 's3-batch-processing',
|
|
54
|
+
module: 'advanced-scenarios'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Initialize State Service
|
|
58
|
+
const stateService = new StateService(logger);
|
|
59
|
+
|
|
60
|
+
// For checkpoints, use KV directly:
|
|
61
|
+
// const kv = openKv();
|
|
62
|
+
// await kv.set(['checkpoint', jobId], { recordsProcessed, lastRecordId, timestamp });
|
|
63
|
+
|
|
64
|
+
// S3 Source for CSV files
|
|
65
|
+
const s3Source = new S3DataSource(
|
|
66
|
+
{
|
|
67
|
+
type: 'S3_CSV',
|
|
68
|
+
connectionId: 's3-inventory',
|
|
69
|
+
name: 'Inventory S3',
|
|
70
|
+
settings: {
|
|
71
|
+
bucketName: 'inventory-updates',
|
|
72
|
+
region: 'us-east-1',
|
|
73
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
74
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
75
|
+
prefix: 'daily/',
|
|
76
|
+
filePattern: '*.csv',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
logger
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// CSV Parser configuration
|
|
83
|
+
// Constructor CAN take logger as first parameter (optional)
|
|
84
|
+
const csvParser = new CSVParserService(logger);
|
|
85
|
+
// Parse options passed to parse() method
|
|
86
|
+
|
|
87
|
+
const fluentClient = await createClient({
|
|
88
|
+
config: {
|
|
89
|
+
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
90
|
+
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
91
|
+
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
92
|
+
username: process.env.FLUENT_USERNAME!,
|
|
93
|
+
password: process.env.FLUENT_PASSWORD!,
|
|
94
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Manual workflow composition for full control
|
|
99
|
+
async function processLargeCSVBatch() {
|
|
100
|
+
logger.info('Starting large CSV batch processing');
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Get checkpoint from KV (for resuming)
|
|
104
|
+
// Use KV adapter directly for checkpoints
|
|
105
|
+
const kv = openKv();
|
|
106
|
+
const checkpointData = await kv.get(['checkpoint', 'inventory-batch']);
|
|
107
|
+
const checkpoint = checkpointData?.value;
|
|
108
|
+
|
|
109
|
+
// List CSV files
|
|
110
|
+
const files = await s3Source.listFiles({
|
|
111
|
+
prefix: 'daily/',
|
|
112
|
+
lastProcessedTimestamp: checkpoint?.lastProcessedTimestamp,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
logger.info(`Found ${files.length} files to process`);
|
|
116
|
+
|
|
117
|
+
for (const file of files) {
|
|
118
|
+
// Check if file was already processed (using KV directly)
|
|
119
|
+
const fileStateData = await kv.get(['file-state', file.name]);
|
|
120
|
+
const fileState = fileStateData?.value;
|
|
121
|
+
if (fileState?.status === 'completed') {
|
|
122
|
+
logger.info(`Skipping already processed file: ${file.name}`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
logger.info(`Processing large file: ${file.name} (${file.size} bytes)`);
|
|
127
|
+
|
|
128
|
+
// Mark file as in-progress (using KV directly)
|
|
129
|
+
await kv.set(['file-state', file.name], {
|
|
130
|
+
status: 'in-progress',
|
|
131
|
+
startedAt: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// Stream large CSV file
|
|
136
|
+
const stream = await s3Source.getFileStream(file.name);
|
|
137
|
+
|
|
138
|
+
let batchNumber = 0;
|
|
139
|
+
let totalProcessed = 0;
|
|
140
|
+
let buffer: any[] = [];
|
|
141
|
+
|
|
142
|
+
// Process streaming data with async iterator
|
|
143
|
+
// Using CSVParserService.parseStreamStreaming for memory-efficient processing
|
|
144
|
+
for await (const record of csvParser.parseStreamStreaming(stream, {}, 1)) {
|
|
145
|
+
buffer.push(record);
|
|
146
|
+
|
|
147
|
+
// Process buffer when it reaches batch size
|
|
148
|
+
if (buffer.length >= 500) {
|
|
149
|
+
await processBatch(buffer, file.name, ++batchNumber);
|
|
150
|
+
totalProcessed += buffer.length;
|
|
151
|
+
buffer = [];
|
|
152
|
+
|
|
153
|
+
// Update checkpoint (using KV directly)
|
|
154
|
+
await kv.set(['checkpoint', 'inventory-batch'], {
|
|
155
|
+
file: file.name,
|
|
156
|
+
recordsProcessed: totalProcessed,
|
|
157
|
+
lastBatch: batchNumber,
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Process remaining records
|
|
164
|
+
if (buffer.length > 0) {
|
|
165
|
+
await processBatch(buffer, file.name, ++batchNumber);
|
|
166
|
+
totalProcessed += buffer.length;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Mark file as completed (using KV directly)
|
|
170
|
+
await kv.set(['file-state', file.name], {
|
|
171
|
+
status: 'completed',
|
|
172
|
+
completedAt: new Date().toISOString(),
|
|
173
|
+
recordsProcessed: totalProcessed,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
logger.info(`File completed: ${file.name}, processed ${totalProcessed} records`);
|
|
177
|
+
|
|
178
|
+
// Archive processed file
|
|
179
|
+
await s3Source.moveFile(
|
|
180
|
+
file.name,
|
|
181
|
+
`archive/${new Date().toISOString().split('T')[0]}/${file.name}`
|
|
182
|
+
);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
logger.error(`Failed to process file ${file.name}:`, error);
|
|
185
|
+
|
|
186
|
+
// Mark file as failed (using KV directly)
|
|
187
|
+
await kv.set(['file-state', file.name], {
|
|
188
|
+
status: 'failed',
|
|
189
|
+
error: (error as Error).message,
|
|
190
|
+
failedAt: new Date().toISOString(),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Move to error bucket
|
|
194
|
+
await s3Source.copyFile(file.name, `errors/${file.name}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logger.info('Batch processing completed');
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logger.error('Batch processing failed:', error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function processBatch(records: any[], fileName: string, batchNumber: number) {
|
|
206
|
+
logger.info(`Processing batch ${batchNumber} with ${records.length} records`);
|
|
207
|
+
|
|
208
|
+
// Transform records
|
|
209
|
+
const inventoryUpdates = records.map(record => ({
|
|
210
|
+
ref: record.sku,
|
|
211
|
+
productRef: record.sku,
|
|
212
|
+
locationRef: record.location,
|
|
213
|
+
qty: parseInt(record.quantity),
|
|
214
|
+
status: record.status || 'ACTIVE',
|
|
215
|
+
type: 'ADJUSTMENT',
|
|
216
|
+
attributes: {
|
|
217
|
+
source: 'S3_CSV',
|
|
218
|
+
file: fileName,
|
|
219
|
+
batch: batchNumber,
|
|
220
|
+
},
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
// Create job for this batch
|
|
224
|
+
const job = await fluentClient.createJob({
|
|
225
|
+
name: `Inventory Batch ${fileName} - ${batchNumber}`,
|
|
226
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Send batch to Fluent
|
|
230
|
+
await fluentClient.sendBatch(job.id, {
|
|
231
|
+
action: 'UPSERT',
|
|
232
|
+
entityType: 'INVENTORY',
|
|
233
|
+
entities: inventoryUpdates,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Poll status with getBatchStatus if needed; no explicit update call is required
|
|
237
|
+
|
|
238
|
+
logger.info(`Batch ${batchNumber} completed`);
|
|
239
|
+
|
|
240
|
+
return inventoryUpdates.length;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Execute with error recovery
|
|
244
|
+
async function runWithRecovery() {
|
|
245
|
+
const maxRetries = 3;
|
|
246
|
+
let attempt = 0;
|
|
247
|
+
|
|
248
|
+
while (attempt < maxRetries) {
|
|
249
|
+
try {
|
|
250
|
+
await processLargeCSVBatch();
|
|
251
|
+
break;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
attempt++;
|
|
254
|
+
logger.error(`Attempt ${attempt} failed:`, error);
|
|
255
|
+
|
|
256
|
+
if (attempt >= maxRetries) {
|
|
257
|
+
logger.error('Max retries reached, giving up');
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Wait before retry (exponential backoff)
|
|
262
|
+
await new Promise(resolve => setTimeout(resolve, 5000 * attempt));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
runWithRecovery();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Key Concepts: State Management
|
|
271
|
+
|
|
272
|
+
**Checkpoint Strategy:**
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Use KV adapter directly for checkpoints
|
|
276
|
+
const kv = openKv();
|
|
277
|
+
|
|
278
|
+
// Save checkpoint after each batch (using KV directly)
|
|
279
|
+
await kv.set(['checkpoint', 'job-id'], {
|
|
280
|
+
file: currentFile,
|
|
281
|
+
lastRecordId: record.id,
|
|
282
|
+
recordsProcessed: count,
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Resume from checkpoint on failure
|
|
287
|
+
const checkpointData = await kv.get(['checkpoint', 'job-id']);
|
|
288
|
+
const checkpoint = checkpointData?.value;
|
|
289
|
+
if (checkpoint) {
|
|
290
|
+
logger.info(`Resuming from batch ${checkpoint.lastBatch}`);
|
|
291
|
+
startFrom = checkpoint.recordsProcessed;
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Streaming Pattern:**
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Don't load entire file into memory
|
|
299
|
+
const stream = await s3Source.getFileStream(file.name);
|
|
300
|
+
let buffer = [];
|
|
301
|
+
|
|
302
|
+
// Use CSVParserService.parseStreamStreaming for memory-efficient processing
|
|
303
|
+
const csvParser = new CSVParserService();
|
|
304
|
+
|
|
305
|
+
for await (const record of csvParser.parseStreamStreaming(stream, {}, 1)) {
|
|
306
|
+
buffer.push(record);
|
|
307
|
+
|
|
308
|
+
if (buffer.length >= BATCH_SIZE) {
|
|
309
|
+
await processBatch(buffer); // Process batch
|
|
310
|
+
buffer = []; // Clear buffer
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Process remaining records
|
|
315
|
+
if (buffer.length > 0) {
|
|
316
|
+
await processBatch(buffer);
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Scenario 5: Multi-Source Inventory Aggregation
|
|
321
|
+
|
|
322
|
+
**Goal:** Aggregate inventory from both SFTP and S3 sources, reconcile differences, and update Fluent.
|
|
323
|
+
|
|
324
|
+
**Complexity:** High
|
|
325
|
+
**Components:** SftpDataSource, S3DataSource, Multiple parsers, Reconciliation logic
|
|
326
|
+
**Estimated Lines:** ~350 lines
|
|
327
|
+
|
|
328
|
+
### Problem Statement
|
|
329
|
+
|
|
330
|
+
Inventory data comes from multiple sources:
|
|
331
|
+
|
|
332
|
+
- **SFTP:** Warehouse inventory (CSV)
|
|
333
|
+
- **S3:** Store inventory (JSON)
|
|
334
|
+
|
|
335
|
+
You need to:
|
|
336
|
+
|
|
337
|
+
1. Collect from both sources
|
|
338
|
+
2. Merge by SKU + Location
|
|
339
|
+
3. Compare with current Fluent inventory
|
|
340
|
+
4. Send only differences (deltas)
|
|
341
|
+
|
|
342
|
+
### Key Implementation Pattern
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import {
|
|
346
|
+
createClient,
|
|
347
|
+
SftpDataSource,
|
|
348
|
+
S3DataSource,
|
|
349
|
+
createConsoleLogger,
|
|
350
|
+
toStructuredLogger
|
|
351
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
352
|
+
|
|
353
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
354
|
+
service: 'multi-source-aggregation',
|
|
355
|
+
module: 'advanced-scenarios'
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Multiple data sources
|
|
359
|
+
const sftpSource = new SftpDataSource(
|
|
360
|
+
{
|
|
361
|
+
type: 'SFTP_CSV',
|
|
362
|
+
connectionId: 'warehouse-sftp',
|
|
363
|
+
name: 'Warehouse SFTP',
|
|
364
|
+
settings: {
|
|
365
|
+
host: process.env.WAREHOUSE_SFTP_HOST!,
|
|
366
|
+
username: process.env.WAREHOUSE_SFTP_USER!,
|
|
367
|
+
password: process.env.WAREHOUSE_SFTP_PASS!,
|
|
368
|
+
remotePath: '/inventory',
|
|
369
|
+
filePattern: '*.csv',
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
logger
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const s3Source = new S3DataSource(
|
|
376
|
+
{
|
|
377
|
+
type: 'S3_JSON',
|
|
378
|
+
connectionId: 'store-s3',
|
|
379
|
+
name: 'Store Inventory S3',
|
|
380
|
+
settings: {
|
|
381
|
+
bucketName: 'store-inventory',
|
|
382
|
+
region: 'us-east-1',
|
|
383
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
384
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
385
|
+
prefix: 'stores/',
|
|
386
|
+
filePattern: '*.json',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
logger
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const fluentClient = await createClient({
|
|
393
|
+
config: {
|
|
394
|
+
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
395
|
+
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
396
|
+
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
397
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
async function aggregateInventory() {
|
|
402
|
+
logger.info('Starting multi-source inventory aggregation');
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
// Collect inventory from all sources
|
|
406
|
+
const inventoryMap = new Map<
|
|
407
|
+
string,
|
|
408
|
+
{
|
|
409
|
+
sku: string;
|
|
410
|
+
locations: Map<string, number>;
|
|
411
|
+
sources: Set<string>;
|
|
412
|
+
}
|
|
413
|
+
>();
|
|
414
|
+
|
|
415
|
+
// 1. Process SFTP warehouse data
|
|
416
|
+
logger.info('Processing warehouse inventory from SFTP');
|
|
417
|
+
const sftpFiles = await sftpSource.listFiles();
|
|
418
|
+
|
|
419
|
+
for (const file of sftpFiles) {
|
|
420
|
+
const content = await sftpSource.downloadFile(file.name);
|
|
421
|
+
const parser = new CSVParserService();
|
|
422
|
+
const records = await parser.parse(content as string);
|
|
423
|
+
|
|
424
|
+
records.forEach(record => {
|
|
425
|
+
const key = record.sku;
|
|
426
|
+
if (!inventoryMap.has(key)) {
|
|
427
|
+
inventoryMap.set(key, {
|
|
428
|
+
sku: record.sku,
|
|
429
|
+
locations: new Map(),
|
|
430
|
+
sources: new Set(),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const item = inventoryMap.get(key)!;
|
|
435
|
+
const currentQty = item.locations.get(record.location) || 0;
|
|
436
|
+
item.locations.set(record.location, currentQty + parseInt(record.quantity));
|
|
437
|
+
item.sources.add('WAREHOUSE_SFTP');
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 2. Process S3 store data
|
|
442
|
+
logger.info('Processing store inventory from S3');
|
|
443
|
+
const s3Files = await s3Source.listFiles();
|
|
444
|
+
|
|
445
|
+
for (const file of s3Files) {
|
|
446
|
+
const content = await s3Source.downloadFile(file.name);
|
|
447
|
+
const storeData = JSON.parse(content.toString());
|
|
448
|
+
|
|
449
|
+
storeData.inventory.forEach((record: any) => {
|
|
450
|
+
const key = record.productId;
|
|
451
|
+
if (!inventoryMap.has(key)) {
|
|
452
|
+
inventoryMap.set(key, {
|
|
453
|
+
sku: record.productId,
|
|
454
|
+
locations: new Map(),
|
|
455
|
+
sources: new Set(),
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const item = inventoryMap.get(key)!;
|
|
460
|
+
const currentQty = item.locations.get(record.storeId) || 0;
|
|
461
|
+
item.locations.set(record.storeId, currentQty + record.availableQty);
|
|
462
|
+
item.sources.add('STORE_S3');
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 3. Query current Fluent inventory for reconciliation
|
|
467
|
+
logger.info('Fetching current Fluent inventory');
|
|
468
|
+
const query = `
|
|
469
|
+
query GetCurrentInventory($retailerId: ID!, $first: Int!) {
|
|
470
|
+
inventoryPositions(retailerId: $retailerId, first: $first) {
|
|
471
|
+
edges {
|
|
472
|
+
node {
|
|
473
|
+
ref
|
|
474
|
+
productRef
|
|
475
|
+
locationRef
|
|
476
|
+
qty
|
|
477
|
+
availableQty
|
|
478
|
+
}
|
|
479
|
+
cursor
|
|
480
|
+
}
|
|
481
|
+
pageInfo { hasNextPage }
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
`;
|
|
485
|
+
|
|
486
|
+
const currentInventory = await fluentClient.graphql({
|
|
487
|
+
query,
|
|
488
|
+
variables: {
|
|
489
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
490
|
+
first: 1000,
|
|
491
|
+
},
|
|
492
|
+
pagination: { maxPages: 10 },
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// 4. Reconcile and prepare updates
|
|
496
|
+
const updates: any[] = [];
|
|
497
|
+
const reconciliationReport: any[] = [];
|
|
498
|
+
|
|
499
|
+
inventoryMap.forEach((aggregated, sku) => {
|
|
500
|
+
aggregated.locations.forEach((newQty, location) => {
|
|
501
|
+
// Find current Fluent inventory
|
|
502
|
+
const current = currentInventory.data.inventoryPositions.edges.find(
|
|
503
|
+
(edge: any) => edge.node.productRef === sku && edge.node.locationRef === location
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const currentQty = current?.node.qty || 0;
|
|
507
|
+
const difference = newQty - currentQty;
|
|
508
|
+
|
|
509
|
+
if (difference !== 0) {
|
|
510
|
+
updates.push({
|
|
511
|
+
ref: `${sku}-${location}`,
|
|
512
|
+
productRef: sku,
|
|
513
|
+
locationRef: location,
|
|
514
|
+
qty: newQty,
|
|
515
|
+
type: difference > 0 ? 'RECEIPT' : 'DISPATCH',
|
|
516
|
+
adjustmentQty: Math.abs(difference),
|
|
517
|
+
reason: 'MULTI_SOURCE_SYNC',
|
|
518
|
+
attributes: {
|
|
519
|
+
sources: Array.from(aggregated.sources).join(','),
|
|
520
|
+
previousQty: currentQty,
|
|
521
|
+
newQty: newQty,
|
|
522
|
+
syncedAt: new Date().toISOString(),
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
reconciliationReport.push({
|
|
527
|
+
sku,
|
|
528
|
+
location,
|
|
529
|
+
previousQty: currentQty,
|
|
530
|
+
newQty: newQty,
|
|
531
|
+
difference,
|
|
532
|
+
sources: Array.from(aggregated.sources),
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
logger.info(`Found ${updates.length} inventory differences to sync`);
|
|
539
|
+
|
|
540
|
+
// 5. Apply updates to Fluent
|
|
541
|
+
if (updates.length > 0) {
|
|
542
|
+
const job = await fluentClient.createJob({
|
|
543
|
+
name: `Multi-Source Inventory Sync - ${new Date().toISOString()}`,
|
|
544
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Process in batches
|
|
548
|
+
const batchSize = 100;
|
|
549
|
+
for (let i = 0; i < updates.length; i += batchSize) {
|
|
550
|
+
const batch = updates.slice(i, i + batchSize);
|
|
551
|
+
|
|
552
|
+
await fluentClient.sendBatch(job.id, {
|
|
553
|
+
action: 'UPSERT',
|
|
554
|
+
entityType: 'INVENTORY',
|
|
555
|
+
entities: batch,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
logger.info(`Batch ${Math.floor(i / batchSize) + 1} processed`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Optional: monitor batch status via getBatchStatus
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 6. Generate and save reconciliation report
|
|
565
|
+
const report = {
|
|
566
|
+
timestamp: new Date().toISOString(),
|
|
567
|
+
sourcesProcessed: {
|
|
568
|
+
sftp: sftpFiles.length,
|
|
569
|
+
s3: s3Files.length,
|
|
570
|
+
},
|
|
571
|
+
totalSKUs: inventoryMap.size,
|
|
572
|
+
totalUpdates: updates.length,
|
|
573
|
+
reconciliation: reconciliationReport,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// Save report to S3
|
|
577
|
+
await s3Source.uploadFile(
|
|
578
|
+
`reports/reconciliation-${new Date().toISOString()}.json`,
|
|
579
|
+
JSON.stringify(report, null, 2)
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
logger.info('Multi-source inventory aggregation completed', {
|
|
583
|
+
totalSKUs: inventoryMap.size,
|
|
584
|
+
totalUpdates: updates.length,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
return report;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
logger.error('Aggregation failed:', error);
|
|
590
|
+
throw error;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Schedule hourly
|
|
595
|
+
setInterval(aggregateInventory, 60 * 60 * 1000);
|
|
596
|
+
aggregateInventory();
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Key Concepts: Multi-Source Aggregation
|
|
600
|
+
|
|
601
|
+
**Data Collection Pattern:**
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
// Use Map for efficient lookups and aggregation
|
|
605
|
+
const inventoryMap = new Map<
|
|
606
|
+
string,
|
|
607
|
+
{
|
|
608
|
+
sku: string;
|
|
609
|
+
locations: Map<string, number>;
|
|
610
|
+
sources: Set<string>;
|
|
611
|
+
}
|
|
612
|
+
>();
|
|
613
|
+
|
|
614
|
+
// Aggregate from multiple sources
|
|
615
|
+
sources.forEach(source => {
|
|
616
|
+
source.records.forEach(record => {
|
|
617
|
+
const item = inventoryMap.get(record.sku) || createNewItem();
|
|
618
|
+
item.locations.set(record.location, record.qty);
|
|
619
|
+
item.sources.add(source.name);
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**Reconciliation Pattern:**
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
// Compare aggregated vs current
|
|
628
|
+
const difference = newQty - currentQty;
|
|
629
|
+
|
|
630
|
+
if (difference !== 0) {
|
|
631
|
+
updates.push({
|
|
632
|
+
qty: newQty,
|
|
633
|
+
type: difference > 0 ? 'RECEIPT' : 'DISPATCH',
|
|
634
|
+
adjustmentQty: Math.abs(difference),
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## Key Takeaways
|
|
640
|
+
|
|
641
|
+
- 🎯 Stream large files to avoid memory issues (don't load all at once)
|
|
642
|
+
- 🎯 Save checkpoints frequently to enable recovery from failures
|
|
643
|
+
- 🎯 Multi-source aggregation requires careful reconciliation logic
|
|
644
|
+
- 🎯 Always track differences, not just totals (delta updates)
|
|
645
|
+
- 🎯 State management is critical for production reliability
|
|
646
|
+
- 🎯 Generate reports for visibility into sync operations
|
|
647
|
+
|
|
648
|
+
## Next Steps
|
|
649
|
+
|
|
650
|
+
Continue to [Module 5: Bidirectional Sync](./connector-scenarios-05-bidirectional-sync.md) to learn extraction patterns and Fluent-to-external system exports.
|