@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,941 +1,941 @@
|
|
|
1
|
-
# Module 4: File Processing Patterns
|
|
2
|
-
|
|
3
|
-
Master file processing strategies - streaming vs buffering, batch processing, state management, validation, and error recovery.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Streaming vs Buffering](#streaming-vs-buffering)
|
|
8
|
-
- [Batch Processing](#batch-processing)
|
|
9
|
-
- [State Management](#state-management)
|
|
10
|
-
- [Format-Specific Processing](#format-specific-processing)
|
|
11
|
-
- [Validation Strategies](#validation-strategies)
|
|
12
|
-
- [Error Recovery](#error-recovery)
|
|
13
|
-
- [Performance Optimization](#performance-optimization)
|
|
14
|
-
- [Production Patterns](#production-patterns)
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Streaming vs Buffering
|
|
19
|
-
|
|
20
|
-
### When to Use Each Strategy
|
|
21
|
-
|
|
22
|
-
| Strategy | Best For | File Size | Memory Usage | Processing Speed |
|
|
23
|
-
| --------------------- | ------------------------------- | --------- | ---------------------------- | ---------------- |
|
|
24
|
-
| **Buffering** | Small files, simple processing | < 10MB | High (entire file in memory) | Fastest |
|
|
25
|
-
| **Streaming** | Large files, memory-constrained | > 10MB | Low (chunks processed) | Moderate |
|
|
26
|
-
| **Chunked Streaming** | Very large files | > 100MB | Very low (fixed chunk size) | Slowest |
|
|
27
|
-
|
|
28
|
-
### Buffering Pattern (Default)
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
// Load entire file into memory (simple, fast for small files)
|
|
32
|
-
const csvContent = await s3Source.downloadFile('inventory.csv');
|
|
33
|
-
// content is entire file in memory (string or Buffer)
|
|
34
|
-
|
|
35
|
-
const records = await csvParser.parse(csvContent);
|
|
36
|
-
// Process all records at once
|
|
37
|
-
for (const record of records) {
|
|
38
|
-
await processRecord(record);
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Pros**:
|
|
43
|
-
|
|
44
|
-
- ✅ Simple API
|
|
45
|
-
- ✅ Fastest for small files (<10MB)
|
|
46
|
-
- ✅ Easy to debug (full content available)
|
|
47
|
-
|
|
48
|
-
**Cons**:
|
|
49
|
-
|
|
50
|
-
- ❌ High memory usage for large files
|
|
51
|
-
- ❌ Can cause OOM (Out of Memory) errors
|
|
52
|
-
- ❌ Slow initial load for large files
|
|
53
|
-
|
|
54
|
-
### Chunked Processing Pattern
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// Process large file in chunks to minimize memory usage
|
|
58
|
-
async function processLargeFile(dataSource: S3DataSource, key: string) {
|
|
59
|
-
// Download file
|
|
60
|
-
const content = await dataSource.downloadFile(key, { encoding: 'binary' });
|
|
61
|
-
|
|
62
|
-
// Process in 1MB chunks
|
|
63
|
-
const chunkSize = 1024 * 1024; // 1MB
|
|
64
|
-
let offset = 0;
|
|
65
|
-
|
|
66
|
-
while (offset < (content as Buffer).length) {
|
|
67
|
-
const chunk = (content as Buffer).slice(offset, offset + chunkSize);
|
|
68
|
-
|
|
69
|
-
// Process chunk
|
|
70
|
-
await processChunk(chunk);
|
|
71
|
-
|
|
72
|
-
offset += chunkSize;
|
|
73
|
-
|
|
74
|
-
// Optional: Log progress
|
|
75
|
-
const progress = ((offset / (content as Buffer).length) * 100).toFixed(2);
|
|
76
|
-
logger.info(`Processing... ${progress}%`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function processChunk(chunk: Buffer) {
|
|
81
|
-
// Your chunk processing logic
|
|
82
|
-
const chunkRecords = parseChunk(chunk);
|
|
83
|
-
await sendToFluent(chunkRecords);
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**Pros**:
|
|
88
|
-
|
|
89
|
-
- ✅ Lower memory usage (fixed chunk size)
|
|
90
|
-
- ✅ Progress tracking
|
|
91
|
-
- ✅ Can handle larger files (up to 100MB)
|
|
92
|
-
|
|
93
|
-
**Cons**:
|
|
94
|
-
|
|
95
|
-
- ❌ More complex code
|
|
96
|
-
- ❌ Still loads full file initially
|
|
97
|
-
- ❌ Not suitable for very large files (GB+)
|
|
98
|
-
|
|
99
|
-
### CSV Streaming Pattern
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
import { CSVParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
103
|
-
import { Readable } from 'stream';
|
|
104
|
-
|
|
105
|
-
// Stream large CSV file line-by-line
|
|
106
|
-
async function streamLargeCSV(dataSource: S3DataSource, key: string) {
|
|
107
|
-
const csvContent = await dataSource.downloadFile(key);
|
|
108
|
-
const csvParser = new CSVParserService();
|
|
109
|
-
|
|
110
|
-
// Parse CSV (returns array of records)
|
|
111
|
-
const records = await csvParser.parse(csvContent as string);
|
|
112
|
-
|
|
113
|
-
// Process in batches
|
|
114
|
-
const batchSize = 100;
|
|
115
|
-
for (let i = 0; i < records.length; i += batchSize) {
|
|
116
|
-
const batch = records.slice(i, i + batchSize);
|
|
117
|
-
await processBatch(batch);
|
|
118
|
-
|
|
119
|
-
// Optional: Add delay between batches to avoid rate limiting
|
|
120
|
-
await sleep(100);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function sleep(ms: number): Promise<void> {
|
|
125
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**Pros**:
|
|
130
|
-
|
|
131
|
-
- ✅ Batch processing for better throughput
|
|
132
|
-
- ✅ Rate limiting control
|
|
133
|
-
- ✅ Suitable for medium-large files
|
|
134
|
-
|
|
135
|
-
**Decision Matrix**:
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
File Size Decision:
|
|
139
|
-
< 10MB → Buffer entire file (simplest)
|
|
140
|
-
10-100MB → Chunked processing (balanced)
|
|
141
|
-
> 100MB → Stream with batching (lowest memory)
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## Batch Processing
|
|
147
|
-
|
|
148
|
-
### Batch Size Optimization
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
// Find optimal batch size based on file size and record count
|
|
152
|
-
function calculateOptimalBatchSize(totalRecords: number): number {
|
|
153
|
-
if (totalRecords < 100) return totalRecords; // Small: 1 batch
|
|
154
|
-
if (totalRecords < 1000) return 100; // Medium: 100 per batch
|
|
155
|
-
if (totalRecords < 10000) return 250; // Large: 250 per batch
|
|
156
|
-
return 500; // Very large: 500 per batch
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Process records in optimal batches
|
|
160
|
-
async function processInBatches(records: any[], client: FluentClient) {
|
|
161
|
-
const batchSize = calculateOptimalBatchSize(records.length);
|
|
162
|
-
|
|
163
|
-
logger.info('Starting batch processing', {
|
|
164
|
-
totalRecords: records.length,
|
|
165
|
-
batchSize,
|
|
166
|
-
estimatedBatches: Math.ceil(records.length / batchSize),
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
for (let i = 0; i < records.length; i += batchSize) {
|
|
170
|
-
const batch = records.slice(i, i + batchSize);
|
|
171
|
-
const batchNumber = Math.floor(i / batchSize) + 1;
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
await client.sendBatch(jobId, { entities: batch });
|
|
175
|
-
logger.info(`Batch ${batchNumber} sent successfully`, {
|
|
176
|
-
recordCount: batch.length,
|
|
177
|
-
});
|
|
178
|
-
} catch (error) {
|
|
179
|
-
logger.error(`Batch ${batchNumber} failed`, error as Error, {
|
|
180
|
-
recordCount: batch.length,
|
|
181
|
-
startIndex: i,
|
|
182
|
-
});
|
|
183
|
-
// Continue with next batch (or throw based on strategy)
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Parallel Batch Processing
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
// Process multiple files in parallel with concurrency limit
|
|
193
|
-
async function processMultipleFiles(
|
|
194
|
-
dataSource: DataSource,
|
|
195
|
-
files: string[],
|
|
196
|
-
maxConcurrency: number = 5
|
|
197
|
-
) {
|
|
198
|
-
const results = [];
|
|
199
|
-
|
|
200
|
-
// Process files with concurrency control
|
|
201
|
-
for (let i = 0; i < files.length; i += maxConcurrency) {
|
|
202
|
-
const batch = files.slice(i, i + maxConcurrency);
|
|
203
|
-
|
|
204
|
-
// Process batch in parallel
|
|
205
|
-
const batchResults = await Promise.allSettled(batch.map(file => processFile(dataSource, file)));
|
|
206
|
-
|
|
207
|
-
results.push(...batchResults);
|
|
208
|
-
|
|
209
|
-
// Log progress
|
|
210
|
-
const processed = i + batch.length;
|
|
211
|
-
const total = files.length;
|
|
212
|
-
logger.info(`Progress: ${processed}/${total} files processed`);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Analyze results
|
|
216
|
-
const successful = results.filter(r => r.status === 'fulfilled').length;
|
|
217
|
-
const failed = results.filter(r => r.status === 'rejected').length;
|
|
218
|
-
|
|
219
|
-
logger.info('Batch processing complete', { successful, failed, total: files.length });
|
|
220
|
-
|
|
221
|
-
return results;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function processFile(dataSource: any, file: string) {
|
|
225
|
-
const content = await dataSource.downloadFile(file);
|
|
226
|
-
const records = parseContent(content);
|
|
227
|
-
await sendToFluent(records);
|
|
228
|
-
return { file, recordCount: records.length };
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
**Concurrency Guidelines**:
|
|
233
|
-
|
|
234
|
-
- Local processing: 10-20 concurrent files
|
|
235
|
-
- Cloud serverless (Lambda, Versori): 3-5 concurrent files
|
|
236
|
-
- SFTP servers: 1-3 concurrent connections (avoid overwhelming vendor)
|
|
237
|
-
- S3: 10+ (S3 scales automatically)
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
## State Management
|
|
242
|
-
|
|
243
|
-
### Preventing Duplicate Processing
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
import { StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
247
|
-
|
|
248
|
-
async function processWithStateManagement(dataSource: DataSource, stateService: StateService) {
|
|
249
|
-
const files = await dataSource.listFiles({ prefix: 'inventory/' });
|
|
250
|
-
|
|
251
|
-
for (const file of files) {
|
|
252
|
-
// Create unique state key (file path + last modified timestamp)
|
|
253
|
-
const stateKey = `processed:${file.path}:${file.lastModified.toISOString()}`;
|
|
254
|
-
|
|
255
|
-
// Check if already processed
|
|
256
|
-
const processed = await stateService.isFileProcessed(kvAdapter as any, stateKey);
|
|
257
|
-
|
|
258
|
-
if (processed) {
|
|
259
|
-
logger.info('File already processed, skipping', {
|
|
260
|
-
file: file.name,
|
|
261
|
-
lastModified: file.lastModified,
|
|
262
|
-
});
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
// Process file
|
|
268
|
-
const content = await dataSource.downloadFile(file.path);
|
|
269
|
-
const records = parseContent(content);
|
|
270
|
-
await sendToFluent(records);
|
|
271
|
-
|
|
272
|
-
// Mark as processed
|
|
273
|
-
await stateService.updateSyncState(
|
|
274
|
-
kvAdapter as any,
|
|
275
|
-
[
|
|
276
|
-
{
|
|
277
|
-
fileName: file.name,
|
|
278
|
-
filePath: file.path,
|
|
279
|
-
fileSize: file.size,
|
|
280
|
-
recordCount: records.length,
|
|
281
|
-
processedAt: new Date().toISOString(),
|
|
282
|
-
success: true,
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
'file-processing'
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
logger.info('File processed successfully', { file: file.name });
|
|
289
|
-
} catch (error) {
|
|
290
|
-
logger.error('Failed to process file', error as Error, { file: file.path });
|
|
291
|
-
|
|
292
|
-
// Optional: Mark as failed (for retry logic)
|
|
293
|
-
await stateService.updateSyncState(
|
|
294
|
-
kvAdapter as any,
|
|
295
|
-
[
|
|
296
|
-
{
|
|
297
|
-
fileName: file.name,
|
|
298
|
-
processedAt: new Date().toISOString(),
|
|
299
|
-
success: false,
|
|
300
|
-
error: (error as Error).message,
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
'file-processing'
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### State Key Strategies
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
// Strategy 1: File path + modification time (recommended)
|
|
314
|
-
const stateKey = `processed:${file.path}:${file.lastModified.toISOString()}`;
|
|
315
|
-
// Pros: Handles file updates (new lastModified = new processing)
|
|
316
|
-
// Cons: Same file content re-processed if touched
|
|
317
|
-
|
|
318
|
-
// Strategy 2: File ETag (S3 only)
|
|
319
|
-
const stateKey = `processed:${file.etag}`;
|
|
320
|
-
// Pros: Content-based (same content = same ETag)
|
|
321
|
-
// Cons: Only works with S3
|
|
322
|
-
|
|
323
|
-
// Strategy 3: File name only (simple)
|
|
324
|
-
const stateKey = `processed:${file.name}`;
|
|
325
|
-
// Pros: Simplest
|
|
326
|
-
// Cons: Doesn't handle file updates
|
|
327
|
-
|
|
328
|
-
// Strategy 4: Content hash (most accurate)
|
|
329
|
-
const contentHash = crypto.createHash('md5').update(content).digest('hex');
|
|
330
|
-
const stateKey = `processed:${contentHash}`;
|
|
331
|
-
// Pros: Truly content-based
|
|
332
|
-
// Cons: Requires downloading file first (slow)
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Versori KV State Management
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
import { VersoriKVAdapter, StateService } from '@fluentcommerce/fc-connect-sdk';
|
|
339
|
-
// ✅ CORRECT: Access openKv from Versori context
|
|
340
|
-
// import { openKv } from '@versori/run'; // ❌ WRONG - Not a direct export
|
|
341
|
-
|
|
342
|
-
// In Versori workflow handler:
|
|
343
|
-
const { openKv } = ctx;
|
|
344
|
-
const kvStore = openKv(':project:');
|
|
345
|
-
const kvAdapter = new VersoriKVAdapter(kvStore);
|
|
346
|
-
const stateService = new StateService(kvAdapter);
|
|
347
|
-
|
|
348
|
-
// Use in Versori webhook
|
|
349
|
-
export const processInventory = webhook('inventory-sync', async (ctx) => {
|
|
350
|
-
const client = await createClient(ctx); // Auto-detects Versori context
|
|
351
|
-
const s3Source = new S3DataSource(
|
|
352
|
-
{
|
|
353
|
-
/* config */
|
|
354
|
-
},
|
|
355
|
-
log
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
359
|
-
|
|
360
|
-
for (const file of files) {
|
|
361
|
-
const stateKey = `processed:${file.etag}`;
|
|
362
|
-
|
|
363
|
-
if (await stateService.isFileProcessed(kvAdapter as any, stateKey)) {
|
|
364
|
-
log.info('File already processed', { file: file.name });
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Process file
|
|
369
|
-
const content = await s3Source.downloadFile(file.path);
|
|
370
|
-
await processContent(content, client);
|
|
371
|
-
|
|
372
|
-
// Mark as processed (persisted in Versori KV)
|
|
373
|
-
await stateService.updateSyncState(
|
|
374
|
-
kvAdapter as any,
|
|
375
|
-
[
|
|
376
|
-
{
|
|
377
|
-
fileName: file.name,
|
|
378
|
-
processedAt: new Date().toISOString(),
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
'file-processing'
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
---
|
|
388
|
-
|
|
389
|
-
## Format-Specific Processing
|
|
390
|
-
|
|
391
|
-
### CSV Processing
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
import { CSVParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
395
|
-
|
|
396
|
-
async function processCSV(dataSource: DataSource, file: string) {
|
|
397
|
-
const csvParser = new CSVParserService();
|
|
398
|
-
|
|
399
|
-
// Download CSV
|
|
400
|
-
const content = await dataSource.downloadFile(file);
|
|
401
|
-
|
|
402
|
-
// Parse with validation
|
|
403
|
-
const records = await csvParser.parse(content as string, {
|
|
404
|
-
columns: true, // Use first row as headers
|
|
405
|
-
skip_empty_lines: true, // Skip empty rows
|
|
406
|
-
trim: true, // Trim whitespace
|
|
407
|
-
cast: true, // Auto-cast types (numbers, booleans)
|
|
408
|
-
cast_date: false, // Don't auto-cast dates (handle manually)
|
|
409
|
-
relax_column_count: false, // Enforce consistent column count
|
|
410
|
-
on_record: (record, context) => {
|
|
411
|
-
// Validate each record during parsing
|
|
412
|
-
if (!record.sku || !record.quantity) {
|
|
413
|
-
throw new Error(`Missing required fields at line ${context.lines}`);
|
|
414
|
-
}
|
|
415
|
-
return record;
|
|
416
|
-
},
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
logger.info('CSV parsed successfully', { recordCount: records.length });
|
|
420
|
-
|
|
421
|
-
return records;
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### JSON Processing
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
async function processJSON(dataSource: DataSource, file: string) {
|
|
429
|
-
const content = await dataSource.downloadFile(file);
|
|
430
|
-
|
|
431
|
-
try {
|
|
432
|
-
const data = JSON.parse(content as string);
|
|
433
|
-
|
|
434
|
-
// Handle different JSON structures
|
|
435
|
-
let records: any[];
|
|
436
|
-
|
|
437
|
-
if (Array.isArray(data)) {
|
|
438
|
-
// Array of records
|
|
439
|
-
records = data;
|
|
440
|
-
} else if (data.records && Array.isArray(data.records)) {
|
|
441
|
-
// Wrapped array: { "records": [...] }
|
|
442
|
-
records = data.records;
|
|
443
|
-
} else if (data.data && Array.isArray(data.data)) {
|
|
444
|
-
// Wrapped array: { "data": [...] }
|
|
445
|
-
records = data.data;
|
|
446
|
-
} else {
|
|
447
|
-
// Single record
|
|
448
|
-
records = [data];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
logger.info('JSON parsed successfully', { recordCount: records.length });
|
|
452
|
-
|
|
453
|
-
return records;
|
|
454
|
-
} catch (error) {
|
|
455
|
-
throw new Error(`Invalid JSON in file ${file}: ${(error as Error).message}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### JSON Lines (JSONL) Processing
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
async function processJSONL(dataSource: DataSource, file: string) {
|
|
464
|
-
const content = await dataSource.downloadFile(file);
|
|
465
|
-
const lines = (content as string).split('\n').filter(line => line.trim());
|
|
466
|
-
|
|
467
|
-
const records = [];
|
|
468
|
-
const errors = [];
|
|
469
|
-
|
|
470
|
-
for (let i = 0; i < lines.length; i++) {
|
|
471
|
-
try {
|
|
472
|
-
const record = JSON.parse(lines[i]);
|
|
473
|
-
records.push(record);
|
|
474
|
-
} catch (error) {
|
|
475
|
-
errors.push({
|
|
476
|
-
line: i + 1,
|
|
477
|
-
content: lines[i],
|
|
478
|
-
error: (error as Error).message,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (errors.length > 0) {
|
|
484
|
-
logger.warn('JSONL parsing errors', { errorCount: errors.length, errors });
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
logger.info('JSONL parsed successfully', {
|
|
488
|
-
totalLines: lines.length,
|
|
489
|
-
recordCount: records.length,
|
|
490
|
-
errorCount: errors.length,
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
return records;
|
|
494
|
-
}
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
### Parquet Processing
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
import { ParquetParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
501
|
-
|
|
502
|
-
async function processParquet(dataSource: DataSource, file: string) {
|
|
503
|
-
const parquetParser = new ParquetParserService(logger);
|
|
504
|
-
|
|
505
|
-
// Download Parquet file
|
|
506
|
-
const buffer = await dataSource.downloadFile(file, { encoding: 'binary' });
|
|
507
|
-
|
|
508
|
-
// Parse Parquet
|
|
509
|
-
const records = await parquetParser.parse(buffer as Buffer);
|
|
510
|
-
|
|
511
|
-
logger.info('Parquet parsed successfully', {
|
|
512
|
-
recordCount: records.length,
|
|
513
|
-
fileSize: (buffer as Buffer).length,
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
return records;
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## Validation Strategies
|
|
523
|
-
|
|
524
|
-
### Pre-Processing Validation
|
|
525
|
-
|
|
526
|
-
```typescript
|
|
527
|
-
async function validateBeforeProcessing(file: FileMetadata): Promise<boolean> {
|
|
528
|
-
const validationRules = [
|
|
529
|
-
// Rule 1: File size check
|
|
530
|
-
() => {
|
|
531
|
-
const maxSize = 100 * 1024 * 1024; // 100MB
|
|
532
|
-
if (file.size > maxSize) {
|
|
533
|
-
logger.error('File too large', { size: file.size, maxSize });
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
return true;
|
|
537
|
-
},
|
|
538
|
-
|
|
539
|
-
// Rule 2: File name pattern check
|
|
540
|
-
() => {
|
|
541
|
-
const pattern = /^inventory_\d{8}\.csv$/;
|
|
542
|
-
if (!pattern.test(file.name)) {
|
|
543
|
-
logger.error('Invalid file name pattern', { name: file.name, expected: pattern });
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
return true;
|
|
547
|
-
},
|
|
548
|
-
|
|
549
|
-
// Rule 3: File age check (not too old)
|
|
550
|
-
() => {
|
|
551
|
-
const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
552
|
-
const age = Date.now() - file.lastModified.getTime();
|
|
553
|
-
if (age > maxAge) {
|
|
554
|
-
logger.warn('File too old', {
|
|
555
|
-
name: file.name,
|
|
556
|
-
age: Math.floor(age / (24 * 60 * 60 * 1000)) + ' days',
|
|
557
|
-
});
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
return true;
|
|
561
|
-
},
|
|
562
|
-
];
|
|
563
|
-
|
|
564
|
-
// Run all validation rules
|
|
565
|
-
for (const rule of validationRules) {
|
|
566
|
-
if (!rule()) {
|
|
567
|
-
return false;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return true;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Use in processing pipeline
|
|
575
|
-
async function processWithValidation(dataSource: DataSource) {
|
|
576
|
-
const files = await dataSource.listFiles();
|
|
577
|
-
|
|
578
|
-
for (const file of files) {
|
|
579
|
-
// Validate before downloading
|
|
580
|
-
if (!(await validateBeforeProcessing(file))) {
|
|
581
|
-
logger.warn('File failed validation, skipping', { file: file.name });
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Process validated file
|
|
586
|
-
await processFile(dataSource, file.path);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
### Record-Level Validation
|
|
592
|
-
|
|
593
|
-
```typescript
|
|
594
|
-
interface ValidationRule {
|
|
595
|
-
field: string;
|
|
596
|
-
rule: (value: any) => boolean;
|
|
597
|
-
message: string;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const validationRules: ValidationRule[] = [
|
|
601
|
-
{
|
|
602
|
-
field: 'sku',
|
|
603
|
-
rule: value => typeof value === 'string' && value.length > 0,
|
|
604
|
-
message: 'SKU must be non-empty string',
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
field: 'quantity',
|
|
608
|
-
rule: value => typeof value === 'number' && value >= 0,
|
|
609
|
-
message: 'Quantity must be non-negative number',
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
field: 'price',
|
|
613
|
-
rule: value => typeof value === 'number' && value > 0,
|
|
614
|
-
message: 'Price must be positive number',
|
|
615
|
-
},
|
|
616
|
-
];
|
|
617
|
-
|
|
618
|
-
function validateRecord(record: any, index: number): { valid: boolean; errors: string[] } {
|
|
619
|
-
const errors: string[] = [];
|
|
620
|
-
|
|
621
|
-
for (const rule of validationRules) {
|
|
622
|
-
if (!rule.rule(record[rule.field])) {
|
|
623
|
-
errors.push(`Record ${index}: ${rule.message} (value: ${record[rule.field]})`);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
return { valid: errors.length === 0, errors };
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Use in processing
|
|
631
|
-
async function processWithRecordValidation(records: any[]) {
|
|
632
|
-
const validRecords = [];
|
|
633
|
-
const invalidRecords = [];
|
|
634
|
-
|
|
635
|
-
for (let i = 0; i < records.length; i++) {
|
|
636
|
-
const validation = validateRecord(records[i], i + 1);
|
|
637
|
-
|
|
638
|
-
if (validation.valid) {
|
|
639
|
-
validRecords.push(records[i]);
|
|
640
|
-
} else {
|
|
641
|
-
invalidRecords.push({ record: records[i], errors: validation.errors });
|
|
642
|
-
logger.warn('Invalid record', { index: i + 1, errors: validation.errors });
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
logger.info('Validation complete', {
|
|
647
|
-
total: records.length,
|
|
648
|
-
valid: validRecords.length,
|
|
649
|
-
invalid: invalidRecords.length,
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
// Process only valid records
|
|
653
|
-
if (validRecords.length > 0) {
|
|
654
|
-
await sendToFluent(validRecords);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Report invalid records
|
|
658
|
-
if (invalidRecords.length > 0) {
|
|
659
|
-
await reportInvalidRecords(invalidRecords);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
---
|
|
665
|
-
|
|
666
|
-
## Error Recovery
|
|
667
|
-
|
|
668
|
-
> **For comprehensive error handling patterns:** See [File Operations Error Handling Guide](../data-sources-file-operations-error-handling.md) for detailed coverage of connection errors, discovery errors, reading errors, parsing errors, troubleshooting flowcharts, and production checklists for S3 and SFTP operations.
|
|
669
|
-
|
|
670
|
-
### Retry Strategy
|
|
671
|
-
|
|
672
|
-
```typescript
|
|
673
|
-
async function processWithRetry<T>(
|
|
674
|
-
fn: () => Promise<T>,
|
|
675
|
-
options = {
|
|
676
|
-
maxRetries: 3,
|
|
677
|
-
initialDelay: 1000,
|
|
678
|
-
maxDelay: 30000,
|
|
679
|
-
backoffMultiplier: 2,
|
|
680
|
-
}
|
|
681
|
-
): Promise<T> {
|
|
682
|
-
let lastError: Error;
|
|
683
|
-
|
|
684
|
-
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
685
|
-
try {
|
|
686
|
-
return await fn();
|
|
687
|
-
} catch (error: any) {
|
|
688
|
-
lastError = error;
|
|
689
|
-
|
|
690
|
-
// Don't retry client errors (4xx)
|
|
691
|
-
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
692
|
-
throw error;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
if (attempt < options.maxRetries) {
|
|
696
|
-
const delay = Math.min(
|
|
697
|
-
options.initialDelay * Math.pow(options.backoffMultiplier, attempt),
|
|
698
|
-
options.maxDelay
|
|
699
|
-
);
|
|
700
|
-
|
|
701
|
-
logger.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`, {
|
|
702
|
-
error: error.message,
|
|
703
|
-
retriesLeft: options.maxRetries - attempt,
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
await sleep(delay);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
throw new Error(
|
|
712
|
-
`Operation failed after ${options.maxRetries + 1} attempts: ${lastError!.message}`
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Use in file processing
|
|
717
|
-
async function processFileWithRetry(dataSource: DataSource, file: string) {
|
|
718
|
-
return processWithRetry(async () => {
|
|
719
|
-
const content = await dataSource.downloadFile(file);
|
|
720
|
-
const records = parseContent(content);
|
|
721
|
-
await sendToFluent(records);
|
|
722
|
-
return records.length;
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
### Partial Failure Handling
|
|
728
|
-
|
|
729
|
-
```typescript
|
|
730
|
-
async function processBatchWithPartialFailure(
|
|
731
|
-
records: any[],
|
|
732
|
-
client: FluentClient,
|
|
733
|
-
batchSize: number = 100
|
|
734
|
-
) {
|
|
735
|
-
const results = {
|
|
736
|
-
successful: 0,
|
|
737
|
-
failed: 0,
|
|
738
|
-
errors: [] as Array<{ batch: number; error: string; records: any[] }>,
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
for (let i = 0; i < records.length; i += batchSize) {
|
|
742
|
-
const batch = records.slice(i, i + batchSize);
|
|
743
|
-
const batchNumber = Math.floor(i / batchSize) + 1;
|
|
744
|
-
|
|
745
|
-
try {
|
|
746
|
-
await client.sendBatch(jobId, { entities: batch });
|
|
747
|
-
results.successful += batch.length;
|
|
748
|
-
|
|
749
|
-
logger.info(`Batch ${batchNumber} succeeded`, { recordCount: batch.length });
|
|
750
|
-
} catch (error) {
|
|
751
|
-
results.failed += batch.length;
|
|
752
|
-
results.errors.push({
|
|
753
|
-
batch: batchNumber,
|
|
754
|
-
error: (error as Error).message,
|
|
755
|
-
records: batch,
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
logger.error(`Batch ${batchNumber} failed`, error as Error, {
|
|
759
|
-
recordCount: batch.length,
|
|
760
|
-
startIndex: i,
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
// Continue processing remaining batches
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
logger.info('Batch processing complete', results);
|
|
768
|
-
|
|
769
|
-
// Save failed batches for manual review or retry
|
|
770
|
-
if (results.errors.length > 0) {
|
|
771
|
-
await saveFailedBatches(results.errors);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
return results;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
async function saveFailedBatches(errors: any[]) {
|
|
778
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
779
|
-
const failedRecords = errors.flatMap(e => e.records);
|
|
780
|
-
|
|
781
|
-
await s3Target.writeFile(
|
|
782
|
-
`s3://failed-batches/failed-${timestamp}.json`,
|
|
783
|
-
JSON.stringify({ errors, records: failedRecords }, null, 2)
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
---
|
|
789
|
-
|
|
790
|
-
## Performance Optimization
|
|
791
|
-
|
|
792
|
-
### Optimization Checklist
|
|
793
|
-
|
|
794
|
-
```typescript
|
|
795
|
-
// 1. Use appropriate batch sizes
|
|
796
|
-
const optimalBatchSize = calculateOptimalBatchSize(records.length);
|
|
797
|
-
|
|
798
|
-
// 2. Parallel processing with concurrency limit
|
|
799
|
-
const maxConcurrency = 5;
|
|
800
|
-
await processFilesInParallel(files, maxConcurrency);
|
|
801
|
-
|
|
802
|
-
// 3. Minimize data source round trips
|
|
803
|
-
const files = await dataSource.listFiles({ prefix: 'inventory/' });
|
|
804
|
-
// Download all file metadata in one call
|
|
805
|
-
|
|
806
|
-
// 4. Use streaming for large files
|
|
807
|
-
if (fileSize > 10 * 1024 * 1024) {
|
|
808
|
-
await processWithStreaming(file);
|
|
809
|
-
} else {
|
|
810
|
-
await processBuffered(file);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// 5. Cache parsed results
|
|
814
|
-
const parseCache = new Map<string, any[]>();
|
|
815
|
-
if (parseCache.has(file.path)) {
|
|
816
|
-
return parseCache.get(file.path);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// 6. Efficient state lookups
|
|
820
|
-
const processedFiles = new Set(await stateService.getProcessedFiles());
|
|
821
|
-
for (const file of files) {
|
|
822
|
-
if (processedFiles.has(file.path)) continue;
|
|
823
|
-
// Process file
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// 7. Avoid redundant validations
|
|
827
|
-
// Validate once before processing, not on every record
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
---
|
|
831
|
-
|
|
832
|
-
## Production Patterns
|
|
833
|
-
|
|
834
|
-
### Complete Ingestion Pipeline
|
|
835
|
-
|
|
836
|
-
```typescript
|
|
837
|
-
import {
|
|
838
|
-
S3DataSource,
|
|
839
|
-
CSVParserService,
|
|
840
|
-
UniversalMapper,
|
|
841
|
-
StateService,
|
|
842
|
-
VersoriKVAdapter,
|
|
843
|
-
createClient,
|
|
844
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
845
|
-
|
|
846
|
-
async function productionIngestionPipeline(ctx: any) {
|
|
847
|
-
// 1. Initialize services
|
|
848
|
-
const { log, openKv } = ctx;
|
|
849
|
-
const client = await createClient(ctx); // Auto-detects Versori context
|
|
850
|
-
const s3Source = new S3DataSource(
|
|
851
|
-
{
|
|
852
|
-
/* config */
|
|
853
|
-
},
|
|
854
|
-
log
|
|
855
|
-
);
|
|
856
|
-
const csvParser = new CSVParserService();
|
|
857
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
858
|
-
const stateService = new StateService(new VersoriKVAdapter(openKv(':project:')));
|
|
859
|
-
|
|
860
|
-
// 2. List files
|
|
861
|
-
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
862
|
-
log.info('Files found', { count: files.length });
|
|
863
|
-
|
|
864
|
-
// 3. Filter and validate
|
|
865
|
-
const validFiles = files.filter(file => {
|
|
866
|
-
// Size check
|
|
867
|
-
if (file.size > 100 * 1024 * 1024) {
|
|
868
|
-
log.warn('File too large, skipping', { file: file.name, size: file.size });
|
|
869
|
-
return false;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// Name pattern check
|
|
873
|
-
if (!file.name.endsWith('.csv')) {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
return true;
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
// 4. Process files
|
|
881
|
-
for (const file of validFiles) {
|
|
882
|
-
const stateKey = `processed:${file.etag}`;
|
|
883
|
-
|
|
884
|
-
// Check state
|
|
885
|
-
if (await stateService.isFileProcessed(kvAdapter as any, stateKey)) {
|
|
886
|
-
log.info('File already processed', { file: file.name });
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
try {
|
|
891
|
-
// Download and parse
|
|
892
|
-
const content = await s3Source.downloadFile(file.path);
|
|
893
|
-
const records = await csvParser.parse(content as string);
|
|
894
|
-
|
|
895
|
-
// Map fields
|
|
896
|
-
const mappingResult = await mapper.map(records);
|
|
897
|
-
if (!mappingResult.success) {
|
|
898
|
-
throw new Error(`Mapping failed: ${JSON.stringify(mappingResult.errors)}`);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Send to Fluent with retry
|
|
902
|
-
await processWithRetry(async () => {
|
|
903
|
-
await client.sendBatch(jobId, { entities: mappingResult.data });
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
// Mark as processed
|
|
907
|
-
await stateService.updateSyncState(
|
|
908
|
-
kvAdapter as any,
|
|
909
|
-
[
|
|
910
|
-
{
|
|
911
|
-
fileName: file.name,
|
|
912
|
-
recordCount: records.length,
|
|
913
|
-
processedAt: new Date().toISOString(),
|
|
914
|
-
},
|
|
915
|
-
],
|
|
916
|
-
'file-processing'
|
|
917
|
-
);
|
|
918
|
-
|
|
919
|
-
// Archive file
|
|
920
|
-
await s3Source.moveFile(file.path, file.path.replace('inventory/', 'processed/'));
|
|
921
|
-
|
|
922
|
-
log.info('File processed successfully', { file: file.name, records: records.length });
|
|
923
|
-
} catch (error) {
|
|
924
|
-
log.error('Failed to process file', { file: file.path, error: (error as Error).message });
|
|
925
|
-
// Continue with next file
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
---
|
|
932
|
-
|
|
933
|
-
## Next Steps
|
|
934
|
-
|
|
935
|
-
**Explore advanced topics** → [Module 5: Advanced Topics](./data-sources-05-advanced-topics.md)
|
|
936
|
-
|
|
937
|
-
**Quick reference** → [Quick Reference](../data-sources-quick-reference.md)
|
|
938
|
-
|
|
939
|
-
---
|
|
940
|
-
|
|
941
|
-
[← Back to SFTP Operations](./data-sources-03-sftp-operations.md) | [Next: Advanced Topics →](./data-sources-05-advanced-topics.md) | [↑ Back to Guide](../data-sources-readme.md)
|
|
1
|
+
# Module 4: File Processing Patterns
|
|
2
|
+
|
|
3
|
+
Master file processing strategies - streaming vs buffering, batch processing, state management, validation, and error recovery.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Streaming vs Buffering](#streaming-vs-buffering)
|
|
8
|
+
- [Batch Processing](#batch-processing)
|
|
9
|
+
- [State Management](#state-management)
|
|
10
|
+
- [Format-Specific Processing](#format-specific-processing)
|
|
11
|
+
- [Validation Strategies](#validation-strategies)
|
|
12
|
+
- [Error Recovery](#error-recovery)
|
|
13
|
+
- [Performance Optimization](#performance-optimization)
|
|
14
|
+
- [Production Patterns](#production-patterns)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Streaming vs Buffering
|
|
19
|
+
|
|
20
|
+
### When to Use Each Strategy
|
|
21
|
+
|
|
22
|
+
| Strategy | Best For | File Size | Memory Usage | Processing Speed |
|
|
23
|
+
| --------------------- | ------------------------------- | --------- | ---------------------------- | ---------------- |
|
|
24
|
+
| **Buffering** | Small files, simple processing | < 10MB | High (entire file in memory) | Fastest |
|
|
25
|
+
| **Streaming** | Large files, memory-constrained | > 10MB | Low (chunks processed) | Moderate |
|
|
26
|
+
| **Chunked Streaming** | Very large files | > 100MB | Very low (fixed chunk size) | Slowest |
|
|
27
|
+
|
|
28
|
+
### Buffering Pattern (Default)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Load entire file into memory (simple, fast for small files)
|
|
32
|
+
const csvContent = await s3Source.downloadFile('inventory.csv');
|
|
33
|
+
// content is entire file in memory (string or Buffer)
|
|
34
|
+
|
|
35
|
+
const records = await csvParser.parse(csvContent);
|
|
36
|
+
// Process all records at once
|
|
37
|
+
for (const record of records) {
|
|
38
|
+
await processRecord(record);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Pros**:
|
|
43
|
+
|
|
44
|
+
- ✅ Simple API
|
|
45
|
+
- ✅ Fastest for small files (<10MB)
|
|
46
|
+
- ✅ Easy to debug (full content available)
|
|
47
|
+
|
|
48
|
+
**Cons**:
|
|
49
|
+
|
|
50
|
+
- ❌ High memory usage for large files
|
|
51
|
+
- ❌ Can cause OOM (Out of Memory) errors
|
|
52
|
+
- ❌ Slow initial load for large files
|
|
53
|
+
|
|
54
|
+
### Chunked Processing Pattern
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Process large file in chunks to minimize memory usage
|
|
58
|
+
async function processLargeFile(dataSource: S3DataSource, key: string) {
|
|
59
|
+
// Download file
|
|
60
|
+
const content = await dataSource.downloadFile(key, { encoding: 'binary' });
|
|
61
|
+
|
|
62
|
+
// Process in 1MB chunks
|
|
63
|
+
const chunkSize = 1024 * 1024; // 1MB
|
|
64
|
+
let offset = 0;
|
|
65
|
+
|
|
66
|
+
while (offset < (content as Buffer).length) {
|
|
67
|
+
const chunk = (content as Buffer).slice(offset, offset + chunkSize);
|
|
68
|
+
|
|
69
|
+
// Process chunk
|
|
70
|
+
await processChunk(chunk);
|
|
71
|
+
|
|
72
|
+
offset += chunkSize;
|
|
73
|
+
|
|
74
|
+
// Optional: Log progress
|
|
75
|
+
const progress = ((offset / (content as Buffer).length) * 100).toFixed(2);
|
|
76
|
+
logger.info(`Processing... ${progress}%`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function processChunk(chunk: Buffer) {
|
|
81
|
+
// Your chunk processing logic
|
|
82
|
+
const chunkRecords = parseChunk(chunk);
|
|
83
|
+
await sendToFluent(chunkRecords);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Pros**:
|
|
88
|
+
|
|
89
|
+
- ✅ Lower memory usage (fixed chunk size)
|
|
90
|
+
- ✅ Progress tracking
|
|
91
|
+
- ✅ Can handle larger files (up to 100MB)
|
|
92
|
+
|
|
93
|
+
**Cons**:
|
|
94
|
+
|
|
95
|
+
- ❌ More complex code
|
|
96
|
+
- ❌ Still loads full file initially
|
|
97
|
+
- ❌ Not suitable for very large files (GB+)
|
|
98
|
+
|
|
99
|
+
### CSV Streaming Pattern
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { CSVParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
103
|
+
import { Readable } from 'stream';
|
|
104
|
+
|
|
105
|
+
// Stream large CSV file line-by-line
|
|
106
|
+
async function streamLargeCSV(dataSource: S3DataSource, key: string) {
|
|
107
|
+
const csvContent = await dataSource.downloadFile(key);
|
|
108
|
+
const csvParser = new CSVParserService();
|
|
109
|
+
|
|
110
|
+
// Parse CSV (returns array of records)
|
|
111
|
+
const records = await csvParser.parse(csvContent as string);
|
|
112
|
+
|
|
113
|
+
// Process in batches
|
|
114
|
+
const batchSize = 100;
|
|
115
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
116
|
+
const batch = records.slice(i, i + batchSize);
|
|
117
|
+
await processBatch(batch);
|
|
118
|
+
|
|
119
|
+
// Optional: Add delay between batches to avoid rate limiting
|
|
120
|
+
await sleep(100);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sleep(ms: number): Promise<void> {
|
|
125
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Pros**:
|
|
130
|
+
|
|
131
|
+
- ✅ Batch processing for better throughput
|
|
132
|
+
- ✅ Rate limiting control
|
|
133
|
+
- ✅ Suitable for medium-large files
|
|
134
|
+
|
|
135
|
+
**Decision Matrix**:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
File Size Decision:
|
|
139
|
+
< 10MB → Buffer entire file (simplest)
|
|
140
|
+
10-100MB → Chunked processing (balanced)
|
|
141
|
+
> 100MB → Stream with batching (lowest memory)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Batch Processing
|
|
147
|
+
|
|
148
|
+
### Batch Size Optimization
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Find optimal batch size based on file size and record count
|
|
152
|
+
function calculateOptimalBatchSize(totalRecords: number): number {
|
|
153
|
+
if (totalRecords < 100) return totalRecords; // Small: 1 batch
|
|
154
|
+
if (totalRecords < 1000) return 100; // Medium: 100 per batch
|
|
155
|
+
if (totalRecords < 10000) return 250; // Large: 250 per batch
|
|
156
|
+
return 500; // Very large: 500 per batch
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Process records in optimal batches
|
|
160
|
+
async function processInBatches(records: any[], client: FluentClient) {
|
|
161
|
+
const batchSize = calculateOptimalBatchSize(records.length);
|
|
162
|
+
|
|
163
|
+
logger.info('Starting batch processing', {
|
|
164
|
+
totalRecords: records.length,
|
|
165
|
+
batchSize,
|
|
166
|
+
estimatedBatches: Math.ceil(records.length / batchSize),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
170
|
+
const batch = records.slice(i, i + batchSize);
|
|
171
|
+
const batchNumber = Math.floor(i / batchSize) + 1;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await client.sendBatch(jobId, { entities: batch });
|
|
175
|
+
logger.info(`Batch ${batchNumber} sent successfully`, {
|
|
176
|
+
recordCount: batch.length,
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
logger.error(`Batch ${batchNumber} failed`, error as Error, {
|
|
180
|
+
recordCount: batch.length,
|
|
181
|
+
startIndex: i,
|
|
182
|
+
});
|
|
183
|
+
// Continue with next batch (or throw based on strategy)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Parallel Batch Processing
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// Process multiple files in parallel with concurrency limit
|
|
193
|
+
async function processMultipleFiles(
|
|
194
|
+
dataSource: DataSource,
|
|
195
|
+
files: string[],
|
|
196
|
+
maxConcurrency: number = 5
|
|
197
|
+
) {
|
|
198
|
+
const results = [];
|
|
199
|
+
|
|
200
|
+
// Process files with concurrency control
|
|
201
|
+
for (let i = 0; i < files.length; i += maxConcurrency) {
|
|
202
|
+
const batch = files.slice(i, i + maxConcurrency);
|
|
203
|
+
|
|
204
|
+
// Process batch in parallel
|
|
205
|
+
const batchResults = await Promise.allSettled(batch.map(file => processFile(dataSource, file)));
|
|
206
|
+
|
|
207
|
+
results.push(...batchResults);
|
|
208
|
+
|
|
209
|
+
// Log progress
|
|
210
|
+
const processed = i + batch.length;
|
|
211
|
+
const total = files.length;
|
|
212
|
+
logger.info(`Progress: ${processed}/${total} files processed`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Analyze results
|
|
216
|
+
const successful = results.filter(r => r.status === 'fulfilled').length;
|
|
217
|
+
const failed = results.filter(r => r.status === 'rejected').length;
|
|
218
|
+
|
|
219
|
+
logger.info('Batch processing complete', { successful, failed, total: files.length });
|
|
220
|
+
|
|
221
|
+
return results;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function processFile(dataSource: any, file: string) {
|
|
225
|
+
const content = await dataSource.downloadFile(file);
|
|
226
|
+
const records = parseContent(content);
|
|
227
|
+
await sendToFluent(records);
|
|
228
|
+
return { file, recordCount: records.length };
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Concurrency Guidelines**:
|
|
233
|
+
|
|
234
|
+
- Local processing: 10-20 concurrent files
|
|
235
|
+
- Cloud serverless (Lambda, Versori): 3-5 concurrent files
|
|
236
|
+
- SFTP servers: 1-3 concurrent connections (avoid overwhelming vendor)
|
|
237
|
+
- S3: 10+ (S3 scales automatically)
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## State Management
|
|
242
|
+
|
|
243
|
+
### Preventing Duplicate Processing
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { StateService, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
247
|
+
|
|
248
|
+
async function processWithStateManagement(dataSource: DataSource, stateService: StateService) {
|
|
249
|
+
const files = await dataSource.listFiles({ prefix: 'inventory/' });
|
|
250
|
+
|
|
251
|
+
for (const file of files) {
|
|
252
|
+
// Create unique state key (file path + last modified timestamp)
|
|
253
|
+
const stateKey = `processed:${file.path}:${file.lastModified.toISOString()}`;
|
|
254
|
+
|
|
255
|
+
// Check if already processed
|
|
256
|
+
const processed = await stateService.isFileProcessed(kvAdapter as any, stateKey);
|
|
257
|
+
|
|
258
|
+
if (processed) {
|
|
259
|
+
logger.info('File already processed, skipping', {
|
|
260
|
+
file: file.name,
|
|
261
|
+
lastModified: file.lastModified,
|
|
262
|
+
});
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
// Process file
|
|
268
|
+
const content = await dataSource.downloadFile(file.path);
|
|
269
|
+
const records = parseContent(content);
|
|
270
|
+
await sendToFluent(records);
|
|
271
|
+
|
|
272
|
+
// Mark as processed
|
|
273
|
+
await stateService.updateSyncState(
|
|
274
|
+
kvAdapter as any,
|
|
275
|
+
[
|
|
276
|
+
{
|
|
277
|
+
fileName: file.name,
|
|
278
|
+
filePath: file.path,
|
|
279
|
+
fileSize: file.size,
|
|
280
|
+
recordCount: records.length,
|
|
281
|
+
processedAt: new Date().toISOString(),
|
|
282
|
+
success: true,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
'file-processing'
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
logger.info('File processed successfully', { file: file.name });
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logger.error('Failed to process file', error as Error, { file: file.path });
|
|
291
|
+
|
|
292
|
+
// Optional: Mark as failed (for retry logic)
|
|
293
|
+
await stateService.updateSyncState(
|
|
294
|
+
kvAdapter as any,
|
|
295
|
+
[
|
|
296
|
+
{
|
|
297
|
+
fileName: file.name,
|
|
298
|
+
processedAt: new Date().toISOString(),
|
|
299
|
+
success: false,
|
|
300
|
+
error: (error as Error).message,
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
'file-processing'
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### State Key Strategies
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Strategy 1: File path + modification time (recommended)
|
|
314
|
+
const stateKey = `processed:${file.path}:${file.lastModified.toISOString()}`;
|
|
315
|
+
// Pros: Handles file updates (new lastModified = new processing)
|
|
316
|
+
// Cons: Same file content re-processed if touched
|
|
317
|
+
|
|
318
|
+
// Strategy 2: File ETag (S3 only)
|
|
319
|
+
const stateKey = `processed:${file.etag}`;
|
|
320
|
+
// Pros: Content-based (same content = same ETag)
|
|
321
|
+
// Cons: Only works with S3
|
|
322
|
+
|
|
323
|
+
// Strategy 3: File name only (simple)
|
|
324
|
+
const stateKey = `processed:${file.name}`;
|
|
325
|
+
// Pros: Simplest
|
|
326
|
+
// Cons: Doesn't handle file updates
|
|
327
|
+
|
|
328
|
+
// Strategy 4: Content hash (most accurate)
|
|
329
|
+
const contentHash = crypto.createHash('md5').update(content).digest('hex');
|
|
330
|
+
const stateKey = `processed:${contentHash}`;
|
|
331
|
+
// Pros: Truly content-based
|
|
332
|
+
// Cons: Requires downloading file first (slow)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Versori KV State Management
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { VersoriKVAdapter, StateService } from '@fluentcommerce/fc-connect-sdk';
|
|
339
|
+
// ✅ CORRECT: Access openKv from Versori context
|
|
340
|
+
// import { openKv } from '@versori/run'; // ❌ WRONG - Not a direct export
|
|
341
|
+
|
|
342
|
+
// In Versori workflow handler:
|
|
343
|
+
const { openKv } = ctx;
|
|
344
|
+
const kvStore = openKv(':project:');
|
|
345
|
+
const kvAdapter = new VersoriKVAdapter(kvStore);
|
|
346
|
+
const stateService = new StateService(kvAdapter);
|
|
347
|
+
|
|
348
|
+
// Use in Versori webhook
|
|
349
|
+
export const processInventory = webhook('inventory-sync', async (ctx) => {
|
|
350
|
+
const client = await createClient(ctx); // Auto-detects Versori context
|
|
351
|
+
const s3Source = new S3DataSource(
|
|
352
|
+
{
|
|
353
|
+
/* config */
|
|
354
|
+
},
|
|
355
|
+
log
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
359
|
+
|
|
360
|
+
for (const file of files) {
|
|
361
|
+
const stateKey = `processed:${file.etag}`;
|
|
362
|
+
|
|
363
|
+
if (await stateService.isFileProcessed(kvAdapter as any, stateKey)) {
|
|
364
|
+
log.info('File already processed', { file: file.name });
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Process file
|
|
369
|
+
const content = await s3Source.downloadFile(file.path);
|
|
370
|
+
await processContent(content, client);
|
|
371
|
+
|
|
372
|
+
// Mark as processed (persisted in Versori KV)
|
|
373
|
+
await stateService.updateSyncState(
|
|
374
|
+
kvAdapter as any,
|
|
375
|
+
[
|
|
376
|
+
{
|
|
377
|
+
fileName: file.name,
|
|
378
|
+
processedAt: new Date().toISOString(),
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
'file-processing'
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Format-Specific Processing
|
|
390
|
+
|
|
391
|
+
### CSV Processing
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
import { CSVParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
395
|
+
|
|
396
|
+
async function processCSV(dataSource: DataSource, file: string) {
|
|
397
|
+
const csvParser = new CSVParserService();
|
|
398
|
+
|
|
399
|
+
// Download CSV
|
|
400
|
+
const content = await dataSource.downloadFile(file);
|
|
401
|
+
|
|
402
|
+
// Parse with validation
|
|
403
|
+
const records = await csvParser.parse(content as string, {
|
|
404
|
+
columns: true, // Use first row as headers
|
|
405
|
+
skip_empty_lines: true, // Skip empty rows
|
|
406
|
+
trim: true, // Trim whitespace
|
|
407
|
+
cast: true, // Auto-cast types (numbers, booleans)
|
|
408
|
+
cast_date: false, // Don't auto-cast dates (handle manually)
|
|
409
|
+
relax_column_count: false, // Enforce consistent column count
|
|
410
|
+
on_record: (record, context) => {
|
|
411
|
+
// Validate each record during parsing
|
|
412
|
+
if (!record.sku || !record.quantity) {
|
|
413
|
+
throw new Error(`Missing required fields at line ${context.lines}`);
|
|
414
|
+
}
|
|
415
|
+
return record;
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
logger.info('CSV parsed successfully', { recordCount: records.length });
|
|
420
|
+
|
|
421
|
+
return records;
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### JSON Processing
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
async function processJSON(dataSource: DataSource, file: string) {
|
|
429
|
+
const content = await dataSource.downloadFile(file);
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
const data = JSON.parse(content as string);
|
|
433
|
+
|
|
434
|
+
// Handle different JSON structures
|
|
435
|
+
let records: any[];
|
|
436
|
+
|
|
437
|
+
if (Array.isArray(data)) {
|
|
438
|
+
// Array of records
|
|
439
|
+
records = data;
|
|
440
|
+
} else if (data.records && Array.isArray(data.records)) {
|
|
441
|
+
// Wrapped array: { "records": [...] }
|
|
442
|
+
records = data.records;
|
|
443
|
+
} else if (data.data && Array.isArray(data.data)) {
|
|
444
|
+
// Wrapped array: { "data": [...] }
|
|
445
|
+
records = data.data;
|
|
446
|
+
} else {
|
|
447
|
+
// Single record
|
|
448
|
+
records = [data];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
logger.info('JSON parsed successfully', { recordCount: records.length });
|
|
452
|
+
|
|
453
|
+
return records;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
throw new Error(`Invalid JSON in file ${file}: ${(error as Error).message}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### JSON Lines (JSONL) Processing
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
async function processJSONL(dataSource: DataSource, file: string) {
|
|
464
|
+
const content = await dataSource.downloadFile(file);
|
|
465
|
+
const lines = (content as string).split('\n').filter(line => line.trim());
|
|
466
|
+
|
|
467
|
+
const records = [];
|
|
468
|
+
const errors = [];
|
|
469
|
+
|
|
470
|
+
for (let i = 0; i < lines.length; i++) {
|
|
471
|
+
try {
|
|
472
|
+
const record = JSON.parse(lines[i]);
|
|
473
|
+
records.push(record);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
errors.push({
|
|
476
|
+
line: i + 1,
|
|
477
|
+
content: lines[i],
|
|
478
|
+
error: (error as Error).message,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (errors.length > 0) {
|
|
484
|
+
logger.warn('JSONL parsing errors', { errorCount: errors.length, errors });
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
logger.info('JSONL parsed successfully', {
|
|
488
|
+
totalLines: lines.length,
|
|
489
|
+
recordCount: records.length,
|
|
490
|
+
errorCount: errors.length,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
return records;
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Parquet Processing
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import { ParquetParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
501
|
+
|
|
502
|
+
async function processParquet(dataSource: DataSource, file: string) {
|
|
503
|
+
const parquetParser = new ParquetParserService(logger);
|
|
504
|
+
|
|
505
|
+
// Download Parquet file
|
|
506
|
+
const buffer = await dataSource.downloadFile(file, { encoding: 'binary' });
|
|
507
|
+
|
|
508
|
+
// Parse Parquet
|
|
509
|
+
const records = await parquetParser.parse(buffer as Buffer);
|
|
510
|
+
|
|
511
|
+
logger.info('Parquet parsed successfully', {
|
|
512
|
+
recordCount: records.length,
|
|
513
|
+
fileSize: (buffer as Buffer).length,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
return records;
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Validation Strategies
|
|
523
|
+
|
|
524
|
+
### Pre-Processing Validation
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
async function validateBeforeProcessing(file: FileMetadata): Promise<boolean> {
|
|
528
|
+
const validationRules = [
|
|
529
|
+
// Rule 1: File size check
|
|
530
|
+
() => {
|
|
531
|
+
const maxSize = 100 * 1024 * 1024; // 100MB
|
|
532
|
+
if (file.size > maxSize) {
|
|
533
|
+
logger.error('File too large', { size: file.size, maxSize });
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
return true;
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
// Rule 2: File name pattern check
|
|
540
|
+
() => {
|
|
541
|
+
const pattern = /^inventory_\d{8}\.csv$/;
|
|
542
|
+
if (!pattern.test(file.name)) {
|
|
543
|
+
logger.error('Invalid file name pattern', { name: file.name, expected: pattern });
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
return true;
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
// Rule 3: File age check (not too old)
|
|
550
|
+
() => {
|
|
551
|
+
const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
552
|
+
const age = Date.now() - file.lastModified.getTime();
|
|
553
|
+
if (age > maxAge) {
|
|
554
|
+
logger.warn('File too old', {
|
|
555
|
+
name: file.name,
|
|
556
|
+
age: Math.floor(age / (24 * 60 * 60 * 1000)) + ' days',
|
|
557
|
+
});
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
return true;
|
|
561
|
+
},
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
// Run all validation rules
|
|
565
|
+
for (const rule of validationRules) {
|
|
566
|
+
if (!rule()) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Use in processing pipeline
|
|
575
|
+
async function processWithValidation(dataSource: DataSource) {
|
|
576
|
+
const files = await dataSource.listFiles();
|
|
577
|
+
|
|
578
|
+
for (const file of files) {
|
|
579
|
+
// Validate before downloading
|
|
580
|
+
if (!(await validateBeforeProcessing(file))) {
|
|
581
|
+
logger.warn('File failed validation, skipping', { file: file.name });
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Process validated file
|
|
586
|
+
await processFile(dataSource, file.path);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Record-Level Validation
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
interface ValidationRule {
|
|
595
|
+
field: string;
|
|
596
|
+
rule: (value: any) => boolean;
|
|
597
|
+
message: string;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const validationRules: ValidationRule[] = [
|
|
601
|
+
{
|
|
602
|
+
field: 'sku',
|
|
603
|
+
rule: value => typeof value === 'string' && value.length > 0,
|
|
604
|
+
message: 'SKU must be non-empty string',
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
field: 'quantity',
|
|
608
|
+
rule: value => typeof value === 'number' && value >= 0,
|
|
609
|
+
message: 'Quantity must be non-negative number',
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
field: 'price',
|
|
613
|
+
rule: value => typeof value === 'number' && value > 0,
|
|
614
|
+
message: 'Price must be positive number',
|
|
615
|
+
},
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
function validateRecord(record: any, index: number): { valid: boolean; errors: string[] } {
|
|
619
|
+
const errors: string[] = [];
|
|
620
|
+
|
|
621
|
+
for (const rule of validationRules) {
|
|
622
|
+
if (!rule.rule(record[rule.field])) {
|
|
623
|
+
errors.push(`Record ${index}: ${rule.message} (value: ${record[rule.field]})`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return { valid: errors.length === 0, errors };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Use in processing
|
|
631
|
+
async function processWithRecordValidation(records: any[]) {
|
|
632
|
+
const validRecords = [];
|
|
633
|
+
const invalidRecords = [];
|
|
634
|
+
|
|
635
|
+
for (let i = 0; i < records.length; i++) {
|
|
636
|
+
const validation = validateRecord(records[i], i + 1);
|
|
637
|
+
|
|
638
|
+
if (validation.valid) {
|
|
639
|
+
validRecords.push(records[i]);
|
|
640
|
+
} else {
|
|
641
|
+
invalidRecords.push({ record: records[i], errors: validation.errors });
|
|
642
|
+
logger.warn('Invalid record', { index: i + 1, errors: validation.errors });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
logger.info('Validation complete', {
|
|
647
|
+
total: records.length,
|
|
648
|
+
valid: validRecords.length,
|
|
649
|
+
invalid: invalidRecords.length,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Process only valid records
|
|
653
|
+
if (validRecords.length > 0) {
|
|
654
|
+
await sendToFluent(validRecords);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Report invalid records
|
|
658
|
+
if (invalidRecords.length > 0) {
|
|
659
|
+
await reportInvalidRecords(invalidRecords);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Error Recovery
|
|
667
|
+
|
|
668
|
+
> **For comprehensive error handling patterns:** See [File Operations Error Handling Guide](../data-sources-file-operations-error-handling.md) for detailed coverage of connection errors, discovery errors, reading errors, parsing errors, troubleshooting flowcharts, and production checklists for S3 and SFTP operations.
|
|
669
|
+
|
|
670
|
+
### Retry Strategy
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
async function processWithRetry<T>(
|
|
674
|
+
fn: () => Promise<T>,
|
|
675
|
+
options = {
|
|
676
|
+
maxRetries: 3,
|
|
677
|
+
initialDelay: 1000,
|
|
678
|
+
maxDelay: 30000,
|
|
679
|
+
backoffMultiplier: 2,
|
|
680
|
+
}
|
|
681
|
+
): Promise<T> {
|
|
682
|
+
let lastError: Error;
|
|
683
|
+
|
|
684
|
+
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
685
|
+
try {
|
|
686
|
+
return await fn();
|
|
687
|
+
} catch (error: any) {
|
|
688
|
+
lastError = error;
|
|
689
|
+
|
|
690
|
+
// Don't retry client errors (4xx)
|
|
691
|
+
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (attempt < options.maxRetries) {
|
|
696
|
+
const delay = Math.min(
|
|
697
|
+
options.initialDelay * Math.pow(options.backoffMultiplier, attempt),
|
|
698
|
+
options.maxDelay
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
logger.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`, {
|
|
702
|
+
error: error.message,
|
|
703
|
+
retriesLeft: options.maxRetries - attempt,
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
await sleep(delay);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
throw new Error(
|
|
712
|
+
`Operation failed after ${options.maxRetries + 1} attempts: ${lastError!.message}`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Use in file processing
|
|
717
|
+
async function processFileWithRetry(dataSource: DataSource, file: string) {
|
|
718
|
+
return processWithRetry(async () => {
|
|
719
|
+
const content = await dataSource.downloadFile(file);
|
|
720
|
+
const records = parseContent(content);
|
|
721
|
+
await sendToFluent(records);
|
|
722
|
+
return records.length;
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Partial Failure Handling
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
async function processBatchWithPartialFailure(
|
|
731
|
+
records: any[],
|
|
732
|
+
client: FluentClient,
|
|
733
|
+
batchSize: number = 100
|
|
734
|
+
) {
|
|
735
|
+
const results = {
|
|
736
|
+
successful: 0,
|
|
737
|
+
failed: 0,
|
|
738
|
+
errors: [] as Array<{ batch: number; error: string; records: any[] }>,
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
742
|
+
const batch = records.slice(i, i + batchSize);
|
|
743
|
+
const batchNumber = Math.floor(i / batchSize) + 1;
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
await client.sendBatch(jobId, { entities: batch });
|
|
747
|
+
results.successful += batch.length;
|
|
748
|
+
|
|
749
|
+
logger.info(`Batch ${batchNumber} succeeded`, { recordCount: batch.length });
|
|
750
|
+
} catch (error) {
|
|
751
|
+
results.failed += batch.length;
|
|
752
|
+
results.errors.push({
|
|
753
|
+
batch: batchNumber,
|
|
754
|
+
error: (error as Error).message,
|
|
755
|
+
records: batch,
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
logger.error(`Batch ${batchNumber} failed`, error as Error, {
|
|
759
|
+
recordCount: batch.length,
|
|
760
|
+
startIndex: i,
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Continue processing remaining batches
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
logger.info('Batch processing complete', results);
|
|
768
|
+
|
|
769
|
+
// Save failed batches for manual review or retry
|
|
770
|
+
if (results.errors.length > 0) {
|
|
771
|
+
await saveFailedBatches(results.errors);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return results;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async function saveFailedBatches(errors: any[]) {
|
|
778
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
779
|
+
const failedRecords = errors.flatMap(e => e.records);
|
|
780
|
+
|
|
781
|
+
await s3Target.writeFile(
|
|
782
|
+
`s3://failed-batches/failed-${timestamp}.json`,
|
|
783
|
+
JSON.stringify({ errors, records: failedRecords }, null, 2)
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## Performance Optimization
|
|
791
|
+
|
|
792
|
+
### Optimization Checklist
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
// 1. Use appropriate batch sizes
|
|
796
|
+
const optimalBatchSize = calculateOptimalBatchSize(records.length);
|
|
797
|
+
|
|
798
|
+
// 2. Parallel processing with concurrency limit
|
|
799
|
+
const maxConcurrency = 5;
|
|
800
|
+
await processFilesInParallel(files, maxConcurrency);
|
|
801
|
+
|
|
802
|
+
// 3. Minimize data source round trips
|
|
803
|
+
const files = await dataSource.listFiles({ prefix: 'inventory/' });
|
|
804
|
+
// Download all file metadata in one call
|
|
805
|
+
|
|
806
|
+
// 4. Use streaming for large files
|
|
807
|
+
if (fileSize > 10 * 1024 * 1024) {
|
|
808
|
+
await processWithStreaming(file);
|
|
809
|
+
} else {
|
|
810
|
+
await processBuffered(file);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// 5. Cache parsed results
|
|
814
|
+
const parseCache = new Map<string, any[]>();
|
|
815
|
+
if (parseCache.has(file.path)) {
|
|
816
|
+
return parseCache.get(file.path);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// 6. Efficient state lookups
|
|
820
|
+
const processedFiles = new Set(await stateService.getProcessedFiles());
|
|
821
|
+
for (const file of files) {
|
|
822
|
+
if (processedFiles.has(file.path)) continue;
|
|
823
|
+
// Process file
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// 7. Avoid redundant validations
|
|
827
|
+
// Validate once before processing, not on every record
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## Production Patterns
|
|
833
|
+
|
|
834
|
+
### Complete Ingestion Pipeline
|
|
835
|
+
|
|
836
|
+
```typescript
|
|
837
|
+
import {
|
|
838
|
+
S3DataSource,
|
|
839
|
+
CSVParserService,
|
|
840
|
+
UniversalMapper,
|
|
841
|
+
StateService,
|
|
842
|
+
VersoriKVAdapter,
|
|
843
|
+
createClient,
|
|
844
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
845
|
+
|
|
846
|
+
async function productionIngestionPipeline(ctx: any) {
|
|
847
|
+
// 1. Initialize services
|
|
848
|
+
const { log, openKv } = ctx;
|
|
849
|
+
const client = await createClient(ctx); // Auto-detects Versori context
|
|
850
|
+
const s3Source = new S3DataSource(
|
|
851
|
+
{
|
|
852
|
+
/* config */
|
|
853
|
+
},
|
|
854
|
+
log
|
|
855
|
+
);
|
|
856
|
+
const csvParser = new CSVParserService();
|
|
857
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
858
|
+
const stateService = new StateService(new VersoriKVAdapter(openKv(':project:')));
|
|
859
|
+
|
|
860
|
+
// 2. List files
|
|
861
|
+
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
862
|
+
log.info('Files found', { count: files.length });
|
|
863
|
+
|
|
864
|
+
// 3. Filter and validate
|
|
865
|
+
const validFiles = files.filter(file => {
|
|
866
|
+
// Size check
|
|
867
|
+
if (file.size > 100 * 1024 * 1024) {
|
|
868
|
+
log.warn('File too large, skipping', { file: file.name, size: file.size });
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Name pattern check
|
|
873
|
+
if (!file.name.endsWith('.csv')) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return true;
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// 4. Process files
|
|
881
|
+
for (const file of validFiles) {
|
|
882
|
+
const stateKey = `processed:${file.etag}`;
|
|
883
|
+
|
|
884
|
+
// Check state
|
|
885
|
+
if (await stateService.isFileProcessed(kvAdapter as any, stateKey)) {
|
|
886
|
+
log.info('File already processed', { file: file.name });
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
try {
|
|
891
|
+
// Download and parse
|
|
892
|
+
const content = await s3Source.downloadFile(file.path);
|
|
893
|
+
const records = await csvParser.parse(content as string);
|
|
894
|
+
|
|
895
|
+
// Map fields
|
|
896
|
+
const mappingResult = await mapper.map(records);
|
|
897
|
+
if (!mappingResult.success) {
|
|
898
|
+
throw new Error(`Mapping failed: ${JSON.stringify(mappingResult.errors)}`);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Send to Fluent with retry
|
|
902
|
+
await processWithRetry(async () => {
|
|
903
|
+
await client.sendBatch(jobId, { entities: mappingResult.data });
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
// Mark as processed
|
|
907
|
+
await stateService.updateSyncState(
|
|
908
|
+
kvAdapter as any,
|
|
909
|
+
[
|
|
910
|
+
{
|
|
911
|
+
fileName: file.name,
|
|
912
|
+
recordCount: records.length,
|
|
913
|
+
processedAt: new Date().toISOString(),
|
|
914
|
+
},
|
|
915
|
+
],
|
|
916
|
+
'file-processing'
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// Archive file
|
|
920
|
+
await s3Source.moveFile(file.path, file.path.replace('inventory/', 'processed/'));
|
|
921
|
+
|
|
922
|
+
log.info('File processed successfully', { file: file.name, records: records.length });
|
|
923
|
+
} catch (error) {
|
|
924
|
+
log.error('Failed to process file', { file: file.path, error: (error as Error).message });
|
|
925
|
+
// Continue with next file
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
933
|
+
## Next Steps
|
|
934
|
+
|
|
935
|
+
**Explore advanced topics** → [Module 5: Advanced Topics](./data-sources-05-advanced-topics.md)
|
|
936
|
+
|
|
937
|
+
**Quick reference** → [Quick Reference](../data-sources-quick-reference.md)
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
941
|
+
[← Back to SFTP Operations](./data-sources-03-sftp-operations.md) | [Next: Advanced Topics →](./data-sources-05-advanced-topics.md) | [↑ Back to Guide](../data-sources-readme.md)
|