@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,1302 +1,1302 @@
|
|
|
1
|
-
# Module 2: S3 Operations
|
|
2
|
-
|
|
3
|
-
Complete guide to S3DataSource - presigned URL architecture, file operations, streaming, and production patterns.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Installation](#installation)
|
|
8
|
-
- [Presigned URL Architecture](#presigned-url-architecture)
|
|
9
|
-
- [Configuration](#configuration)
|
|
10
|
-
- [File Operations](#file-operations)
|
|
11
|
-
- [Parquet Generation](#parquet-generation)
|
|
12
|
-
- [Streaming Patterns](#streaming-patterns)
|
|
13
|
-
- [Platform Integration](#platform-integration)
|
|
14
|
-
- [Production Patterns](#production-patterns)
|
|
15
|
-
- [TypeScript Types](#typescript-types)
|
|
16
|
-
- [Testing](#testing)
|
|
17
|
-
- [Troubleshooting](#troubleshooting)
|
|
18
|
-
- [Best Practices](#best-practices)
|
|
19
|
-
- [Limitations](#limitations)
|
|
20
|
-
- [API Reference](#api-reference)
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Installation
|
|
25
|
-
|
|
26
|
-
Install the SDK which includes S3DataSource and all dependencies:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install @fluentcommerce/fc-connect-sdk
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Dependencies**: The SDK uses AWS SDK v3 (^3.879.0) internally for presigned URL signature generation via `S3PresignService`, but `S3DataSource` itself uses native `fetch` for all S3 operations, making it compatible with Node.js, Deno, and Versori.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Presigned URL Architecture
|
|
37
|
-
|
|
38
|
-
### How S3DataSource Works
|
|
39
|
-
|
|
40
|
-
`S3DataSource` uses a **presigned-URL-only architecture** for universal platform compatibility:
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
┌──────────────────────────────────────────────────────────┐
|
|
44
|
-
│ S3DataSource (fc-connect-sdk) │
|
|
45
|
-
└──────────────────┬───────────────────────────────────────┘
|
|
46
|
-
│
|
|
47
|
-
▼
|
|
48
|
-
┌─────────────────────┐
|
|
49
|
-
│ S3PresignService │
|
|
50
|
-
│ (AWS SigV4 signing) │
|
|
51
|
-
└────────┬────────────┘
|
|
52
|
-
│
|
|
53
|
-
▼
|
|
54
|
-
Generate Presigned URL (1-hour expiry)
|
|
55
|
-
https://bucket.s3.region.amazonaws.com/key?
|
|
56
|
-
X-Amz-Algorithm=AWS4-HMAC-SHA256
|
|
57
|
-
&X-Amz-Credential=...
|
|
58
|
-
&X-Amz-Signature=...
|
|
59
|
-
│
|
|
60
|
-
▼
|
|
61
|
-
┌─────────────────────┐
|
|
62
|
-
│ Native fetch() │
|
|
63
|
-
│ (HTTP GET/PUT/...) │
|
|
64
|
-
└────────┬────────────┘
|
|
65
|
-
│
|
|
66
|
-
▼
|
|
67
|
-
┌─────────────────────┐
|
|
68
|
-
│ AWS S3 Bucket │
|
|
69
|
-
│ (validates sig) │
|
|
70
|
-
└─────────────────────┘
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**Key Benefits**:
|
|
74
|
-
|
|
75
|
-
1. **No AWS SDK bundling** - Smaller package size, faster cold starts
|
|
76
|
-
2. **Universal compatibility** - Works in Node.js, Deno, Versori without changes
|
|
77
|
-
3. **Simple HTTP** - Standard `fetch()` API, easy to debug
|
|
78
|
-
4. **Stateless** - No connection pooling or state management
|
|
79
|
-
5. **Proxy-friendly** - Works with corporate proxies and firewalls
|
|
80
|
-
|
|
81
|
-
### Signature Generation Process
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
// How presigned URLs are generated internally:
|
|
85
|
-
// 1. Create canonical request (method, path, query, headers)
|
|
86
|
-
// 2. Sign with AWS SigV4 using access key + secret key
|
|
87
|
-
// 3. Add signature to query string
|
|
88
|
-
// 4. URL valid for 1 hour (default)
|
|
89
|
-
|
|
90
|
-
// Example presigned GET URL:
|
|
91
|
-
https://my-bucket.s3.us-east-1.amazonaws.com/inventory/file.csv?
|
|
92
|
-
X-Amz-Algorithm=AWS4-HMAC-SHA256&
|
|
93
|
-
X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20250115%2Fus-east-1%2Fs3%2Faws4_request&
|
|
94
|
-
X-Amz-Date=20250115T120000Z&
|
|
95
|
-
X-Amz-Expires=3600&
|
|
96
|
-
X-Amz-SignedHeaders=host&
|
|
97
|
-
X-Amz-Signature=abc123...
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Security Notes**:
|
|
101
|
-
|
|
102
|
-
- Presigned URLs expire after 1 hour (3600 seconds)
|
|
103
|
-
- Anyone with the URL can access the file during expiry window
|
|
104
|
-
- Signatures are cryptographically secure (SHA256 HMAC)
|
|
105
|
-
- No credentials exposed in URL (only signature)
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## Configuration
|
|
110
|
-
|
|
111
|
-
### Basic Configuration
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
import {
|
|
115
|
-
S3DataSource,
|
|
116
|
-
createConsoleLogger,
|
|
117
|
-
toStructuredLogger,
|
|
118
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
119
|
-
|
|
120
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
121
|
-
logLevel: 'info',
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Minimal S3 configuration
|
|
125
|
-
const s3Source = new S3DataSource(
|
|
126
|
-
{
|
|
127
|
-
type: 'S3_CSV', // S3_JSON, S3_XML, S3_PARQUET
|
|
128
|
-
connectionId: 's3-minimal',
|
|
129
|
-
name: 'S3 Minimal',
|
|
130
|
-
s3Config: {
|
|
131
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
132
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
133
|
-
region: 'us-east-1',
|
|
134
|
-
bucket: 'my-bucket', // Required
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
logger
|
|
138
|
-
);
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Configuration Options
|
|
142
|
-
|
|
143
|
-
| Property | Required | Description | Default | Example |
|
|
144
|
-
| -------------------------- | -------- | ----------------------------------- | ----------- | ------------------------- |
|
|
145
|
-
| `type` | ✅ | Data source type | - | `'S3_CSV'` |
|
|
146
|
-
| `s3Config.accessKeyId` | ✅ | AWS access key ID | - | `'AKIAIOSFODNN7...'` |
|
|
147
|
-
| `s3Config.secretAccessKey` | ✅ | AWS secret access key | - | `'wJalrXUtnFEMI/...'` |
|
|
148
|
-
| `s3Config.region` | ✅ | AWS region | - | `'us-east-1'` |
|
|
149
|
-
| `s3Config.bucket` | ❌ | Default bucket name | - | `'inventory-data'` |
|
|
150
|
-
| `s3Config.sessionToken` | ❌ | STS session token (temporary creds) | - | `'FwoGZXIv...'` |
|
|
151
|
-
| `s3Config.endpoint` | ❌ | Custom S3 endpoint (for LocalStack) | AWS default | `'http://localhost:4566'` |
|
|
152
|
-
| `s3Config.maxAttempts` | ❌ | Max retry attempts | `3` | `5` |
|
|
153
|
-
|
|
154
|
-
### Enhanced Retry Logic
|
|
155
|
-
|
|
156
|
-
S3DataSource includes automatic retry with exponential backoff for all HTTP operations.
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
const s3Source = new S3DataSource(
|
|
160
|
-
{
|
|
161
|
-
type: 'S3_CSV',
|
|
162
|
-
connectionId: 's3-retry',
|
|
163
|
-
name: 'S3 Retry',
|
|
164
|
-
s3Config: {
|
|
165
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
166
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
167
|
-
region: 'us-east-1',
|
|
168
|
-
bucket: 'my-bucket',
|
|
169
|
-
maxAttempts: 5, // Default: 3, increase for unreliable networks
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
logger
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// All operations automatically retry on failure:
|
|
176
|
-
await s3Source.downloadFile('data.csv'); // Retries on 429, 500, 502, 503, 504, network errors
|
|
177
|
-
await s3Source.uploadFile('output.json', data); // Retries automatically
|
|
178
|
-
await s3Source.listFiles({ prefix: 'inventory/' }); // Retries automatically
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**Retry Behavior:**
|
|
182
|
-
|
|
183
|
-
| Attempt | Delay | Total Wait | Retryable Conditions |
|
|
184
|
-
| -------------------- | ------ | ---------- | -------------------------------------------- |
|
|
185
|
-
| 1 | 0ms | 0ms | HTTP 429, 500, 502, 503, 504, network errors |
|
|
186
|
-
| 2 | 500ms | 500ms | Same conditions |
|
|
187
|
-
| 3 (default max) | 1000ms | 1.5s | Same conditions |
|
|
188
|
-
| 4 (if maxAttempts=4) | 2000ms | 3.5s | Same conditions |
|
|
189
|
-
| 5 (if maxAttempts=5) | 4000ms | 7.5s | Same conditions |
|
|
190
|
-
|
|
191
|
-
**Backoff Formula:**
|
|
192
|
-
|
|
193
|
-
- Base delay: 500ms
|
|
194
|
-
- Factor: 2 (exponential)
|
|
195
|
-
- Max delay per attempt: 8000ms (8 seconds)
|
|
196
|
-
- Formula: `min(8000ms, 500ms * 2^(attempt-1))`
|
|
197
|
-
|
|
198
|
-
**What Gets Retried:**
|
|
199
|
-
|
|
200
|
-
- ✅ Rate limiting (429 Too Many Requests)
|
|
201
|
-
- ✅ Server errors (500, 502, 503, 504)
|
|
202
|
-
- ✅ Network failures (connection timeout, DNS errors)
|
|
203
|
-
- ✅ Transient S3 errors
|
|
204
|
-
|
|
205
|
-
**What Does NOT Get Retried:**
|
|
206
|
-
|
|
207
|
-
- ❌ Client errors (400, 403, 404) - permanent failures
|
|
208
|
-
- ❌ Authentication errors (401) - credential issues
|
|
209
|
-
- ❌ Successful responses (2xx)
|
|
210
|
-
|
|
211
|
-
**Logging:**
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// Retry attempts are automatically logged at WARN level
|
|
215
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
216
|
-
logLevel: 'debug',
|
|
217
|
-
});
|
|
218
|
-
const s3Source = new S3DataSource(config, logger);
|
|
219
|
-
|
|
220
|
-
await s3Source.downloadFile('data.csv');
|
|
221
|
-
// Logs on retry:
|
|
222
|
-
// - "S3 HTTP retry due to status" (for 429, 500, etc.)
|
|
223
|
-
// - "S3 HTTP retry due to network error" (for connection failures)
|
|
224
|
-
// - Includes: operationName, status, attempt, delay
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
**Production Recommendation:**
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
// For production, increase maxAttempts for better resilience
|
|
231
|
-
const productionConfig = {
|
|
232
|
-
type: 'S3_CSV',
|
|
233
|
-
s3Config: {
|
|
234
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
235
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
236
|
-
region: 'us-east-1',
|
|
237
|
-
bucket: 'production-bucket',
|
|
238
|
-
maxAttempts: 5, // 5 attempts = up to 7.5s of automatic retries
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Environment Variables Pattern
|
|
244
|
-
|
|
245
|
-
```bash
|
|
246
|
-
# .env file
|
|
247
|
-
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
|
248
|
-
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
249
|
-
AWS_REGION=us-east-1
|
|
250
|
-
AWS_SESSION_TOKEN=FwoGZXIv... # Optional
|
|
251
|
-
SOURCE_BUCKET=inventory-source
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
// Load from environment
|
|
256
|
-
const s3Source = new S3DataSource(
|
|
257
|
-
{
|
|
258
|
-
type: 'S3_CSV',
|
|
259
|
-
connectionId: 's3-env',
|
|
260
|
-
name: 'S3 Environment',
|
|
261
|
-
s3Config: {
|
|
262
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
263
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
264
|
-
region: process.env.AWS_REGION || 'us-east-1',
|
|
265
|
-
bucket: process.env.SOURCE_BUCKET,
|
|
266
|
-
sessionToken: process.env.AWS_SESSION_TOKEN, // Optional
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
logger
|
|
270
|
-
);
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Dual AWS Credentials (Source + Target)
|
|
274
|
-
|
|
275
|
-
Use separate AWS accounts for reading (source) and writing (target):
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
// Source S3 (read inventory from vendor's AWS account)
|
|
279
|
-
const sourceS3 = new S3DataSource(
|
|
280
|
-
{
|
|
281
|
-
type: 'S3_CSV',
|
|
282
|
-
connectionId: 's3-source-vendor',
|
|
283
|
-
name: 'S3 Source Vendor',
|
|
284
|
-
s3Config: {
|
|
285
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
286
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
287
|
-
region: process.env.AWS_REGION || 'us-east-1',
|
|
288
|
-
bucket: process.env.SOURCE_BUCKET,
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
logger
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
// Target S3 (write extraction results to our data lake)
|
|
295
|
-
const targetS3 = new S3DataSource(
|
|
296
|
-
{
|
|
297
|
-
type: 'S3_PARQUET',
|
|
298
|
-
connectionId: 's3-target-own',
|
|
299
|
-
name: 'S3 Target Own',
|
|
300
|
-
s3Config: {
|
|
301
|
-
accessKeyId: process.env.TARGET_AWS_ACCESS_KEY_ID!,
|
|
302
|
-
secretAccessKey: process.env.TARGET_AWS_SECRET_ACCESS_KEY!,
|
|
303
|
-
region: process.env.TARGET_AWS_REGION || 'us-west-2',
|
|
304
|
-
bucket: process.env.TARGET_BUCKET,
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
logger
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// Usage: Read from vendor, write to our data lake
|
|
311
|
-
const inventory = await sourceS3.downloadFile('s3://vendor-bucket/inventory.csv');
|
|
312
|
-
const processed = await processInventory(inventory);
|
|
313
|
-
await targetS3.uploadFile('s3://our-datalake/processed.parquet', processed);
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
---
|
|
317
|
-
|
|
318
|
-
## File Operations
|
|
319
|
-
|
|
320
|
-
### Listing Files
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
// List all files in bucket
|
|
324
|
-
const allFiles = await s3Source.listFiles();
|
|
325
|
-
|
|
326
|
-
// List files with prefix (folder-like structure)
|
|
327
|
-
const inventoryFiles = await s3Source.listFiles({
|
|
328
|
-
prefix: 'inventory/',
|
|
329
|
-
maxKeys: 100,
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// List with delimiter (folders)
|
|
333
|
-
const topLevel = await s3Source.listFiles({
|
|
334
|
-
prefix: 'data/',
|
|
335
|
-
delimiter: '/',
|
|
336
|
-
maxKeys: 1000,
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
// Process file metadata
|
|
340
|
-
for (const file of inventoryFiles) {
|
|
341
|
-
console.log({
|
|
342
|
-
path: file.path,
|
|
343
|
-
name: file.name,
|
|
344
|
-
size: file.size,
|
|
345
|
-
lastModified: file.lastModified,
|
|
346
|
-
contentType: file.contentType,
|
|
347
|
-
etag: file.etag,
|
|
348
|
-
bucket: file.bucket,
|
|
349
|
-
region: file.region,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
**List Options**:
|
|
355
|
-
|
|
356
|
-
| Option | Type | Description |
|
|
357
|
-
| ------------------- | -------- | -------------------------------------------------- |
|
|
358
|
-
| `prefix` | `string` | Filter files by prefix (e.g., `'inventory/'`) |
|
|
359
|
-
| `delimiter` | `string` | Delimiter for folder-like structure (e.g., `'/'`) |
|
|
360
|
-
| `maxKeys` | `number` | Maximum files to return (default: 1000, max: 1000) |
|
|
361
|
-
| `continuationToken` | `string` | Token for pagination (from previous response) |
|
|
362
|
-
|
|
363
|
-
**Example: List files modified in last 24 hours**
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
const files = await s3Source.listFiles({ prefix: 'incoming/' });
|
|
367
|
-
|
|
368
|
-
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
369
|
-
const recentFiles = files.filter(file => file.lastModified > yesterday);
|
|
370
|
-
|
|
371
|
-
console.log(`${recentFiles.length} files modified in last 24 hours`);
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Reading Files
|
|
375
|
-
|
|
376
|
-
```typescript
|
|
377
|
-
// Read text file (CSV, JSON, etc.)
|
|
378
|
-
const csvContent = await s3Source.downloadFile('inventory.csv');
|
|
379
|
-
// Returns: string
|
|
380
|
-
|
|
381
|
-
// Read binary file (Parquet, images, etc.)
|
|
382
|
-
const parquetData = await s3Source.downloadFile('data.parquet', { encoding: 'binary' });
|
|
383
|
-
// Returns: Buffer
|
|
384
|
-
|
|
385
|
-
// Read JSON with parsing
|
|
386
|
-
const jsonContent = await s3Source.downloadFile('config.json');
|
|
387
|
-
const config = JSON.parse(jsonContent as string);
|
|
388
|
-
|
|
389
|
-
// For existence checks, list by prefix and filter, or handle 404 on download
|
|
390
|
-
if (await s3Source.listFiles({ prefix: 'file.csv' }).then(files => files.length > 0)) {
|
|
391
|
-
const content = await s3Source.downloadFile('file.csv');
|
|
392
|
-
}
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
**Read Options**:
|
|
396
|
-
|
|
397
|
-
| Option | Type | Description | Default |
|
|
398
|
-
| ----------------- | ------------------------ | ----------------------- | -------- |
|
|
399
|
-
| `encoding` | `'utf8' \| 'binary'` | Text or binary encoding | `'utf8'` |
|
|
400
|
-
| `responseHeaders` | `Record<string, string>` | Custom HTTP headers | `{}` |
|
|
401
|
-
|
|
402
|
-
**Example: Read with error handling**
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
try {
|
|
406
|
-
const content = await s3Source.downloadFile('file.csv');
|
|
407
|
-
console.log('File read successfully');
|
|
408
|
-
} catch (error: any) {
|
|
409
|
-
if (error.context?.statusCode === 404) {
|
|
410
|
-
console.log('File not found');
|
|
411
|
-
} else if (error.context?.statusCode === 403) {
|
|
412
|
-
console.log('Access denied - check IAM permissions');
|
|
413
|
-
} else {
|
|
414
|
-
console.error('S3 error:', error.message);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### Writing Files
|
|
420
|
-
|
|
421
|
-
```typescript
|
|
422
|
-
// Write CSV file
|
|
423
|
-
await s3Source.uploadFile('output/results.csv', csvData, { contentType: 'text/csv' });
|
|
424
|
-
|
|
425
|
-
// Write JSON with metadata
|
|
426
|
-
await s3Source.uploadFile('output/results.json', JSON.stringify(data, null, 2), {
|
|
427
|
-
contentType: 'application/json',
|
|
428
|
-
metadata: {
|
|
429
|
-
'processed-date': new Date().toISOString(),
|
|
430
|
-
version: '1.0',
|
|
431
|
-
processor: 'fc-connect-sdk',
|
|
432
|
-
'record-count': data.length.toString(),
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// Write binary file (Parquet, images, etc.)
|
|
437
|
-
await s3Source.uploadFile('output/data.parquet', parquetBuffer, {
|
|
438
|
-
contentType: 'application/octet-stream',
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// Automatic content type detection from extension
|
|
442
|
-
await s3Source.uploadFile('file.csv', csvData);
|
|
443
|
-
// Automatically sets contentType: 'text/csv'
|
|
444
|
-
|
|
445
|
-
await s3Source.uploadFile('file.json', jsonData);
|
|
446
|
-
// Automatically sets contentType: 'application/json'
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
**Write Options**:
|
|
450
|
-
|
|
451
|
-
| Option | Type | Description |
|
|
452
|
-
| ----------------- | ------------------------ | -------------------------------------------------------- |
|
|
453
|
-
| `contentType` | `string` | MIME type (auto-detected from extension if not provided) |
|
|
454
|
-
| `metadata` | `Record<string, string>` | Custom S3 object metadata (key-value pairs) |
|
|
455
|
-
| `cacheControl` | `string` | HTTP Cache-Control header |
|
|
456
|
-
| `contentEncoding` | `string` | HTTP Content-Encoding header (e.g., `'gzip'`) |
|
|
457
|
-
|
|
458
|
-
### Deleting Files
|
|
459
|
-
|
|
460
|
-
```typescript
|
|
461
|
-
// Delete single file
|
|
462
|
-
await s3Source.deleteFile('temp/old-file.csv');
|
|
463
|
-
|
|
464
|
-
// Delete with error handling
|
|
465
|
-
try {
|
|
466
|
-
await s3Source.deleteFile('important-file.json');
|
|
467
|
-
console.log('File deleted successfully');
|
|
468
|
-
} catch (error: any) {
|
|
469
|
-
if (error.context?.statusCode === 404) {
|
|
470
|
-
console.log('File already deleted or does not exist');
|
|
471
|
-
} else {
|
|
472
|
-
console.error('Delete failed:', error.message);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Delete multiple files (batch)
|
|
477
|
-
const filesToDelete = [
|
|
478
|
-
's3://bucket/temp/file1.csv',
|
|
479
|
-
's3://bucket/temp/file2.csv',
|
|
480
|
-
's3://bucket/temp/file3.csv',
|
|
481
|
-
];
|
|
482
|
-
|
|
483
|
-
for (const file of filesToDelete) {
|
|
484
|
-
try {
|
|
485
|
-
await s3Source.deleteFile(file);
|
|
486
|
-
} catch (error) {
|
|
487
|
-
console.error(`Failed to delete ${file}:`, error);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
### Copying Files
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
// Copy within same bucket
|
|
496
|
-
await s3Source.copyFile('source/data.csv', 'archive/data.csv');
|
|
497
|
-
|
|
498
|
-
// Copy from different bucket
|
|
499
|
-
await s3Source.copyFile('file.json', 'backup/file.json', { sourceBucket: 'source-bucket' });
|
|
500
|
-
|
|
501
|
-
// Copy with metadata
|
|
502
|
-
await s3Source.copyFile('source.csv', 'archive/source.csv', {
|
|
503
|
-
metadata: {
|
|
504
|
-
'copied-from': 'source.csv',
|
|
505
|
-
'copy-date': new Date().toISOString(),
|
|
506
|
-
'archived-by': 'fc-connect-sdk',
|
|
507
|
-
},
|
|
508
|
-
});
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### Moving Files (Copy + Delete)
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
// Move file within bucket
|
|
515
|
-
await s3Source.moveFile('incoming/data.csv', 'processed/data.csv');
|
|
516
|
-
|
|
517
|
-
// Move to archive with date-based organization
|
|
518
|
-
const today = new Date().toISOString().split('T')[0];
|
|
519
|
-
await s3Source.moveFile('incoming/inventory.csv', `archive/${today}/inventory.csv`);
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
**IMPORTANT**: `moveFile()` is implemented as copy + delete, which is **NOT atomic**. If delete fails, the file exists in both locations. For critical operations:
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// Manual atomic-like move with verification
|
|
526
|
-
try {
|
|
527
|
-
// Copy
|
|
528
|
-
await s3Source.copyFile(sourcePath, targetPath);
|
|
529
|
-
|
|
530
|
-
// Verify copy succeeded
|
|
531
|
-
const exists = await s3Source.listFiles({ prefix: targetPath }).then(files => files.length > 0);
|
|
532
|
-
if (!exists) {
|
|
533
|
-
throw new Error('Copy verification failed');
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Delete original
|
|
537
|
-
await s3Source.deleteFile(sourcePath);
|
|
538
|
-
} catch (error) {
|
|
539
|
-
console.error('Move failed:', error);
|
|
540
|
-
// Cleanup: delete target if it exists
|
|
541
|
-
try {
|
|
542
|
-
await s3Source.deleteFile(targetPath);
|
|
543
|
-
} catch {}
|
|
544
|
-
}
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
### Checking File Existence
|
|
548
|
-
|
|
549
|
-
```typescript
|
|
550
|
-
// Use listFiles + filter or handle 404 on download to determine existence
|
|
551
|
-
if (await s3Source.listFiles({ prefix: 'config.json' }).then(files => files.length > 0)) {
|
|
552
|
-
const config = await s3Source.downloadFile('config.json');
|
|
553
|
-
} else {
|
|
554
|
-
console.log('Config file not found, using defaults');
|
|
555
|
-
const config = defaultConfig;
|
|
556
|
-
}
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
## Parquet Generation
|
|
562
|
-
|
|
563
|
-
`S3DataSource` includes built-in Parquet file generation for efficient data storage and analytics:
|
|
564
|
-
|
|
565
|
-
### Basic Parquet Generation
|
|
566
|
-
|
|
567
|
-
```typescript
|
|
568
|
-
import { S3DataSource, ParquetDataRecord } from '@fluentcommerce/fc-connect-sdk';
|
|
569
|
-
|
|
570
|
-
// Prepare data records
|
|
571
|
-
const records: ParquetDataRecord[] = [
|
|
572
|
-
{ id: 1, sku: 'PROD-001', quantity: 100, price: 29.99, available: true },
|
|
573
|
-
{ id: 2, sku: 'PROD-002', quantity: 200, price: 39.99, available: false },
|
|
574
|
-
{ id: 3, sku: 'PROD-003', quantity: 150, price: 49.99, available: true },
|
|
575
|
-
];
|
|
576
|
-
|
|
577
|
-
// Generate Parquet buffer
|
|
578
|
-
const parquetBuffer = await s3Source.writeParquetContent(records, {
|
|
579
|
-
compression: 'GZIP', // Options: UNCOMPRESSED, GZIP, SNAPPY, LZO, BROTLI
|
|
580
|
-
rowGroupSize: 50000, // Rows per group (default: 50000)
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
// Upload to S3
|
|
584
|
-
await s3Source.uploadFile('s3://bucket/output/inventory.parquet', parquetBuffer, {
|
|
585
|
-
contentType: 'application/octet-stream',
|
|
586
|
-
});
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
### Automatic Schema Inference
|
|
590
|
-
|
|
591
|
-
Parquet schema is automatically inferred from record structure:
|
|
592
|
-
|
|
593
|
-
```typescript
|
|
594
|
-
// JavaScript type → Parquet type mapping
|
|
595
|
-
const records = [
|
|
596
|
-
{
|
|
597
|
-
id: 1, // number (int) → INT64
|
|
598
|
-
sku: 'PROD-001', // string → UTF8
|
|
599
|
-
quantity: 100, // number (int) → INT64
|
|
600
|
-
price: 29.99, // number (float) → DOUBLE
|
|
601
|
-
available: true, // boolean → BOOLEAN
|
|
602
|
-
lastUpdated: new Date(), // Date → TIMESTAMP_MILLIS
|
|
603
|
-
metadata: { tags: ['new', 'hot'] }, // object → UTF8 (JSON serialized)
|
|
604
|
-
},
|
|
605
|
-
];
|
|
606
|
-
|
|
607
|
-
const buffer = await s3Source.writeParquetContent(records);
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
**Type Mapping**:
|
|
611
|
-
|
|
612
|
-
- `string` → UTF8
|
|
613
|
-
- `number` (integer) → INT64
|
|
614
|
-
- `number` (float/decimal) → DOUBLE
|
|
615
|
-
- `boolean` → BOOLEAN
|
|
616
|
-
- `Date` → TIMESTAMP_MILLIS
|
|
617
|
-
- `object` → UTF8 (JSON serialized)
|
|
618
|
-
- `null` / `undefined` → NULL
|
|
619
|
-
|
|
620
|
-
### Compression Options
|
|
621
|
-
|
|
622
|
-
```typescript
|
|
623
|
-
// No compression (fastest, largest file)
|
|
624
|
-
const uncompressed = await s3Source.writeParquetContent(records, {
|
|
625
|
-
compression: 'UNCOMPRESSED',
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
// GZIP compression (good balance, widely supported)
|
|
629
|
-
const gzipped = await s3Source.writeParquetContent(records, {
|
|
630
|
-
compression: 'GZIP',
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
// SNAPPY compression (fastest compression, good for analytics)
|
|
634
|
-
const snappy = await s3Source.writeParquetContent(records, {
|
|
635
|
-
compression: 'SNAPPY',
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
// BROTLI compression (best compression ratio)
|
|
639
|
-
const brotli = await s3Source.writeParquetContent(records, {
|
|
640
|
-
compression: 'BROTLI',
|
|
641
|
-
});
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
**Compression Comparison**:
|
|
645
|
-
|
|
646
|
-
| Algorithm | Speed | Compression Ratio | Use Case |
|
|
647
|
-
| ------------ | --------- | ----------------- | ----------------------------- |
|
|
648
|
-
| UNCOMPRESSED | Fastest | 1:1 | Small files, temp data |
|
|
649
|
-
| SNAPPY | Very fast | 2:1 to 3:1 | Analytics, real-time queries |
|
|
650
|
-
| GZIP | Medium | 4:1 to 6:1 | General purpose, wide support |
|
|
651
|
-
| BROTLI | Slowest | 5:1 to 8:1 | Long-term storage, archival |
|
|
652
|
-
|
|
653
|
-
### Complex Object Handling
|
|
654
|
-
|
|
655
|
-
```typescript
|
|
656
|
-
// Complex nested objects are automatically serialized to JSON
|
|
657
|
-
const records = [
|
|
658
|
-
{
|
|
659
|
-
id: 1,
|
|
660
|
-
productDetails: {
|
|
661
|
-
name: 'Product A',
|
|
662
|
-
category: 'Electronics',
|
|
663
|
-
attributes: { color: 'black', size: 'M' },
|
|
664
|
-
},
|
|
665
|
-
tags: ['new', 'featured', 'sale'],
|
|
666
|
-
timestamp: new Date(),
|
|
667
|
-
},
|
|
668
|
-
];
|
|
669
|
-
|
|
670
|
-
const buffer = await s3Source.writeParquetContent(records);
|
|
671
|
-
// productDetails and tags are stored as JSON strings in Parquet
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
### Empty Record Handling
|
|
675
|
-
|
|
676
|
-
```typescript
|
|
677
|
-
// Empty arrays return minimal valid Parquet file
|
|
678
|
-
const emptyBuffer = await s3Source.writeParquetContent([], {
|
|
679
|
-
compression: 'GZIP',
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// Still a valid Parquet file (can be opened by analytics tools)
|
|
683
|
-
await s3Source.uploadFile('s3://bucket/empty.parquet', emptyBuffer);
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
## Streaming Patterns
|
|
689
|
-
|
|
690
|
-
### Processing Large Files
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
// Download large file in chunks to minimize memory usage
|
|
694
|
-
async function processLargeFile(dataSource: S3DataSource, key: string) {
|
|
695
|
-
// Download file (S3DataSource buffers in memory)
|
|
696
|
-
const content = await dataSource.downloadFile(key, { encoding: 'binary' });
|
|
697
|
-
|
|
698
|
-
// Process in chunks
|
|
699
|
-
const chunkSize = 1024 * 1024; // 1MB chunks
|
|
700
|
-
let offset = 0;
|
|
701
|
-
|
|
702
|
-
while (offset < content.length) {
|
|
703
|
-
const chunk = (content as Buffer).slice(offset, offset + chunkSize);
|
|
704
|
-
await processChunk(chunk);
|
|
705
|
-
offset += chunkSize;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
async function processChunk(chunk: Buffer) {
|
|
710
|
-
console.log(`Processing ${chunk.length} bytes`);
|
|
711
|
-
// Your processing logic here
|
|
712
|
-
}
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
**Note**: For very large files (>100MB), consider using S3 Select or reading directly with AWS SDK if memory is constrained.
|
|
716
|
-
|
|
717
|
-
---
|
|
718
|
-
|
|
719
|
-
## Platform Integration
|
|
720
|
-
|
|
721
|
-
### Node.js
|
|
722
|
-
|
|
723
|
-
```typescript
|
|
724
|
-
import {
|
|
725
|
-
S3DataSource,
|
|
726
|
-
createConsoleLogger,
|
|
727
|
-
toStructuredLogger,
|
|
728
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
729
|
-
|
|
730
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
731
|
-
logLevel: 'info',
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
const s3Source = new S3DataSource(
|
|
735
|
-
{
|
|
736
|
-
type: 'S3_CSV',
|
|
737
|
-
s3Config: {
|
|
738
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
739
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
740
|
-
region: 'us-east-1',
|
|
741
|
-
bucket: 'my-bucket',
|
|
742
|
-
},
|
|
743
|
-
},
|
|
744
|
-
logger
|
|
745
|
-
);
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
### Deno
|
|
749
|
-
|
|
750
|
-
```typescript
|
|
751
|
-
import {
|
|
752
|
-
S3DataSource,
|
|
753
|
-
createConsoleLogger,
|
|
754
|
-
toStructuredLogger,
|
|
755
|
-
} from 'npm:@fluentcommerce/fc-connect-sdk@latest';
|
|
756
|
-
|
|
757
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
758
|
-
logLevel: 'info',
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
const s3Source = new S3DataSource(
|
|
762
|
-
{
|
|
763
|
-
type: 'S3_CSV',
|
|
764
|
-
s3Config: {
|
|
765
|
-
accessKeyId: Deno.env.get('AWS_ACCESS_KEY_ID')!,
|
|
766
|
-
secretAccessKey: Deno.env.get('AWS_SECRET_ACCESS_KEY')!,
|
|
767
|
-
region: 'us-east-1',
|
|
768
|
-
bucket: 'my-bucket',
|
|
769
|
-
},
|
|
770
|
-
},
|
|
771
|
-
logger
|
|
772
|
-
);
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
### Versori
|
|
776
|
-
|
|
777
|
-
```typescript
|
|
778
|
-
import {
|
|
779
|
-
S3DataSource,
|
|
780
|
-
createConsoleLogger,
|
|
781
|
-
toStructuredLogger,
|
|
782
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
783
|
-
import { http } from '@versori/run';
|
|
784
|
-
|
|
785
|
-
export const processFiles = http('s3-process', async ({ activation, log }) => {
|
|
786
|
-
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
787
|
-
logLevel: 'info',
|
|
788
|
-
});
|
|
789
|
-
|
|
790
|
-
const s3Source = new S3DataSource(
|
|
791
|
-
{
|
|
792
|
-
type: 'S3_CSV',
|
|
793
|
-
s3Config: {
|
|
794
|
-
accessKeyId: activation.env.AWS_ACCESS_KEY_ID,
|
|
795
|
-
secretAccessKey: activation.env.AWS_SECRET_ACCESS_KEY,
|
|
796
|
-
region: activation.env.AWS_REGION,
|
|
797
|
-
bucket: activation.env.S3_BUCKET,
|
|
798
|
-
},
|
|
799
|
-
},
|
|
800
|
-
logger
|
|
801
|
-
);
|
|
802
|
-
|
|
803
|
-
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
804
|
-
// Process files...
|
|
805
|
-
});
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
---
|
|
809
|
-
|
|
810
|
-
## Production Patterns
|
|
811
|
-
|
|
812
|
-
### Pattern 1: Inventory Ingestion from S3
|
|
813
|
-
|
|
814
|
-
```typescript
|
|
815
|
-
import {
|
|
816
|
-
S3DataSource,
|
|
817
|
-
CSVParserService,
|
|
818
|
-
UniversalMapper,
|
|
819
|
-
StateService,
|
|
820
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
821
|
-
|
|
822
|
-
async function ingestInventoryFromS3() {
|
|
823
|
-
const s3Source = new S3DataSource(
|
|
824
|
-
{
|
|
825
|
-
/* config */
|
|
826
|
-
},
|
|
827
|
-
logger
|
|
828
|
-
);
|
|
829
|
-
const csvParser = new CSVParserService();
|
|
830
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
831
|
-
const stateService = new StateService(kvAdapter);
|
|
832
|
-
|
|
833
|
-
// List inventory files
|
|
834
|
-
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
835
|
-
|
|
836
|
-
for (const file of files) {
|
|
837
|
-
// Check if already processed
|
|
838
|
-
const processed = await stateService.isFileProcessed(file.etag);
|
|
839
|
-
if (processed) {
|
|
840
|
-
console.log(`Skipping already processed file: ${file.name}`);
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
try {
|
|
845
|
-
// Download and parse CSV
|
|
846
|
-
const content = await s3Source.downloadFile(file.path);
|
|
847
|
-
const records = await csvParser.parse(content as string);
|
|
848
|
-
|
|
849
|
-
// Map fields
|
|
850
|
-
const result = await mapper.map(records);
|
|
851
|
-
if (!result.success) {
|
|
852
|
-
logger.error('Mapping failed', undefined, { errors: result.errors });
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Send to Fluent (using Batch API)
|
|
857
|
-
await sendToFluent(result.data);
|
|
858
|
-
|
|
859
|
-
// Mark as processed
|
|
860
|
-
await stateService.markFileProcessed(file.etag, {
|
|
861
|
-
fileName: file.name,
|
|
862
|
-
recordCount: records.length,
|
|
863
|
-
processedAt: new Date().toISOString(),
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
// Archive file
|
|
867
|
-
await s3Source.moveFile(file.path, file.path.replace('inventory/', 'processed/'));
|
|
868
|
-
|
|
869
|
-
logger.info('File processed successfully', { file: file.name });
|
|
870
|
-
} catch (error) {
|
|
871
|
-
logger.error('Failed to process file', error as Error, { file: file.path });
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
### Pattern 2: Data Extraction to S3
|
|
878
|
-
|
|
879
|
-
```typescript
|
|
880
|
-
async function extractInventoryToS3(client: FluentClient, targetS3: S3DataSource) {
|
|
881
|
-
// Query Fluent inventory
|
|
882
|
-
const inventory = await client.graphql({
|
|
883
|
-
query: `query GetInventory {
|
|
884
|
-
virtualPositions(first: 1000) {
|
|
885
|
-
edges {
|
|
886
|
-
node {
|
|
887
|
-
ref
|
|
888
|
-
productRef
|
|
889
|
-
groupRef
|
|
890
|
-
quantity
|
|
891
|
-
status
|
|
892
|
-
updatedOn
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}`,
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
const records = inventory.virtualPositions.edges.map(e => e.node);
|
|
900
|
-
|
|
901
|
-
// Generate Parquet
|
|
902
|
-
const parquetBuffer = await targetS3.writeParquetContent(records, {
|
|
903
|
-
compression: 'SNAPPY',
|
|
904
|
-
rowGroupSize: 50000,
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
// Upload with timestamp
|
|
908
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
909
|
-
const outputPath = `s3://extraction-bucket/inventory/${timestamp}/full-export.parquet`;
|
|
910
|
-
|
|
911
|
-
await targetS3.uploadFile(outputPath, parquetBuffer, {
|
|
912
|
-
contentType: 'application/octet-stream',
|
|
913
|
-
metadata: {
|
|
914
|
-
'extraction-timestamp': timestamp,
|
|
915
|
-
'record-count': records.length.toString(),
|
|
916
|
-
compression: 'SNAPPY',
|
|
917
|
-
},
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
logger.info('Extraction completed', { path: outputPath, records: records.length });
|
|
921
|
-
}
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
---
|
|
925
|
-
|
|
926
|
-
## TypeScript Types
|
|
927
|
-
|
|
928
|
-
```typescript
|
|
929
|
-
// S3 Configuration
|
|
930
|
-
interface S3Config {
|
|
931
|
-
accessKeyId: string;
|
|
932
|
-
secretAccessKey: string;
|
|
933
|
-
region: string;
|
|
934
|
-
sessionToken?: string;
|
|
935
|
-
bucket?: string;
|
|
936
|
-
endpoint?: string;
|
|
937
|
-
forcePathStyle?: boolean;
|
|
938
|
-
maxAttempts?: number;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// S3DataSource Configuration
|
|
942
|
-
interface S3DataSourceConfig {
|
|
943
|
-
type: 'S3_CSV' | 'S3_JSON' | 'S3_XML' | 'S3_PARQUET';
|
|
944
|
-
connectionId: string;
|
|
945
|
-
name: string;
|
|
946
|
-
s3Config: S3Config;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// File Metadata from S3DataSource
|
|
950
|
-
interface FileMetadata {
|
|
951
|
-
path: string;
|
|
952
|
-
name: string;
|
|
953
|
-
size: number;
|
|
954
|
-
lastModified: Date;
|
|
955
|
-
contentType?: string;
|
|
956
|
-
etag?: string;
|
|
957
|
-
bucket: string;
|
|
958
|
-
region: string;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// List Options
|
|
962
|
-
interface ListOptions {
|
|
963
|
-
prefix?: string;
|
|
964
|
-
delimiter?: string;
|
|
965
|
-
maxKeys?: number;
|
|
966
|
-
continuationToken?: string;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// Download Options
|
|
970
|
-
interface DownloadOptions {
|
|
971
|
-
encoding?: 'utf8' | 'binary';
|
|
972
|
-
responseHeaders?: Record<string, string>;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// Upload Options
|
|
976
|
-
interface UploadOptions {
|
|
977
|
-
contentType?: string;
|
|
978
|
-
metadata?: Record<string, string>;
|
|
979
|
-
cacheControl?: string;
|
|
980
|
-
contentEncoding?: string;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
// Copy Options
|
|
984
|
-
interface CopyOptions {
|
|
985
|
-
sourceBucket?: string;
|
|
986
|
-
metadata?: Record<string, string>;
|
|
987
|
-
metadataDirective?: 'COPY' | 'REPLACE';
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// Parquet Options
|
|
991
|
-
interface ParquetOptions {
|
|
992
|
-
compression?: 'UNCOMPRESSED' | 'GZIP' | 'SNAPPY' | 'LZO' | 'BROTLI';
|
|
993
|
-
rowGroupSize?: number;
|
|
994
|
-
schema?: Record<string, string>;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// Parquet Data Record
|
|
998
|
-
interface ParquetDataRecord {
|
|
999
|
-
[key: string]: string | number | boolean | Date | object | null;
|
|
1000
|
-
}
|
|
1001
|
-
```
|
|
1002
|
-
|
|
1003
|
-
---
|
|
1004
|
-
|
|
1005
|
-
## Testing
|
|
1006
|
-
|
|
1007
|
-
The SDK includes test utilities for validating S3 operations:
|
|
1008
|
-
|
|
1009
|
-
```typescript
|
|
1010
|
-
import {
|
|
1011
|
-
S3SDKTester,
|
|
1012
|
-
S3PresignedTester,
|
|
1013
|
-
S3ComparisonTester
|
|
1014
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
1015
|
-
|
|
1016
|
-
// Test SDK implementation (if using S3Service)
|
|
1017
|
-
const sdkTester = new S3SDKTester(config);
|
|
1018
|
-
const sdkResults = await sdkTester.testAll('test-bucket');
|
|
1019
|
-
|
|
1020
|
-
// Test Presigned implementation
|
|
1021
|
-
const presignedTester = new S3PresignedTester(config, fetch);
|
|
1022
|
-
const presignedResults = await presignedTester.testAll('test-bucket');
|
|
1023
|
-
|
|
1024
|
-
// Compare both implementations
|
|
1025
|
-
const comparer = new S3ComparisonTester();
|
|
1026
|
-
const comparison = await comparer.compareImplementations(
|
|
1027
|
-
config,
|
|
1028
|
-
'test-bucket'
|
|
1029
|
-
);
|
|
1030
|
-
console.log('Match rate:', comparison.summary.matchRate + '%');
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
**Note**: For `S3DataSource`, you typically don't need these testers since it only uses presigned URLs. Use these if you're testing `S3Service` with dual mode support.
|
|
1034
|
-
|
|
1035
|
-
### Basic Connection Test
|
|
1036
|
-
|
|
1037
|
-
```typescript
|
|
1038
|
-
// Simple connectivity test
|
|
1039
|
-
const isValid = await s3Source.validateConnection();
|
|
1040
|
-
if (!isValid) {
|
|
1041
|
-
throw new Error('S3 connection failed - check credentials and region');
|
|
1042
|
-
}
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
---
|
|
1046
|
-
|
|
1047
|
-
## Troubleshooting
|
|
1048
|
-
|
|
1049
|
-
### Common Issues and Solutions
|
|
1050
|
-
|
|
1051
|
-
#### "S3DataSource requires explicit bucket configuration"
|
|
1052
|
-
|
|
1053
|
-
```typescript
|
|
1054
|
-
// ❌ Wrong - missing bucket
|
|
1055
|
-
const dataSource = new S3DataSource({
|
|
1056
|
-
type: 'S3_CSV',
|
|
1057
|
-
connectionId: 's3-test',
|
|
1058
|
-
name: 'S3 Test',
|
|
1059
|
-
s3Config: {
|
|
1060
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1061
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1062
|
-
region: 'us-east-1'
|
|
1063
|
-
// Missing: bucket
|
|
1064
|
-
}
|
|
1065
|
-
}, logger);
|
|
1066
|
-
|
|
1067
|
-
// ✅ Correct - includes bucket
|
|
1068
|
-
const dataSource = new S3DataSource({
|
|
1069
|
-
type: 'S3_CSV',
|
|
1070
|
-
connectionId: 's3-test',
|
|
1071
|
-
name: 'S3 Test',
|
|
1072
|
-
s3Config: {
|
|
1073
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1074
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1075
|
-
region: 'us-east-1',
|
|
1076
|
-
bucket: 'my-bucket' // Required
|
|
1077
|
-
}
|
|
1078
|
-
}, logger);
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
#### Region Mismatch Errors
|
|
1082
|
-
|
|
1083
|
-
```typescript
|
|
1084
|
-
// Ensure your client region matches the bucket region
|
|
1085
|
-
const dataSource = new S3DataSource({
|
|
1086
|
-
type: 'S3_CSV',
|
|
1087
|
-
connectionId: 's3-test',
|
|
1088
|
-
name: 'S3 Test',
|
|
1089
|
-
s3Config: {
|
|
1090
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1091
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1092
|
-
region: 'us-west-2', // Must match bucket region
|
|
1093
|
-
bucket: 'my-bucket'
|
|
1094
|
-
}
|
|
1095
|
-
}, logger);
|
|
1096
|
-
```
|
|
1097
|
-
|
|
1098
|
-
#### Need Multipart Upload Support
|
|
1099
|
-
|
|
1100
|
-
```typescript
|
|
1101
|
-
// S3DataSource does not support multipart upload
|
|
1102
|
-
// For files > 100MB requiring multipart, use S3Service instead:
|
|
1103
|
-
import { S3Service } from '@fluentcommerce/fc-connect-sdk';
|
|
1104
|
-
|
|
1105
|
-
const s3 = new S3Service({
|
|
1106
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1107
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1108
|
-
region: 'us-east-1'
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
// Now multipart works
|
|
1112
|
-
if (s3.hasAdvancedFeatures()) {
|
|
1113
|
-
await s3.uploadLargeFile('bucket', 'large-file.bin', buffer, {
|
|
1114
|
-
partSize: 10 * 1024 * 1024
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
```
|
|
1118
|
-
|
|
1119
|
-
#### Content Type Detection
|
|
1120
|
-
|
|
1121
|
-
The S3DataSource automatically detects content types based on file extensions:
|
|
1122
|
-
|
|
1123
|
-
```typescript
|
|
1124
|
-
// Automatic content type detection
|
|
1125
|
-
const contentTypes = {
|
|
1126
|
-
'.csv': 'text/csv',
|
|
1127
|
-
'.json': 'application/json',
|
|
1128
|
-
'.xml': 'application/xml',
|
|
1129
|
-
'.txt': 'text/plain',
|
|
1130
|
-
'.parquet': 'application/octet-stream',
|
|
1131
|
-
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
1132
|
-
'.xls': 'application/vnd.ms-excel',
|
|
1133
|
-
'.pdf': 'application/pdf',
|
|
1134
|
-
'.zip': 'application/zip',
|
|
1135
|
-
'.gz': 'application/gzip'
|
|
1136
|
-
};
|
|
1137
|
-
|
|
1138
|
-
// Files are automatically assigned correct content type
|
|
1139
|
-
await s3DataSource.uploadFile('data.csv', csvContent); // text/csv
|
|
1140
|
-
await s3DataSource.uploadFile('config.json', jsonContent); // application/json
|
|
1141
|
-
```
|
|
1142
|
-
|
|
1143
|
-
### When to Use S3DataSource vs S3Service
|
|
1144
|
-
|
|
1145
|
-
| Use Case | Recommended Class | Reason |
|
|
1146
|
-
|----------|------------------|---------|
|
|
1147
|
-
| Standard file operations (read, write, delete) | `S3DataSource` | Simpler API, universal compatibility |
|
|
1148
|
-
| Node.js, Deno, or Versori connectors | `S3DataSource` | Works everywhere with presigned URLs |
|
|
1149
|
-
| Files < 100MB | `S3DataSource` | Efficient for standard file sizes |
|
|
1150
|
-
| Need metadata and content type support | `S3DataSource` | Full support via presigned URL headers |
|
|
1151
|
-
| Files > 100MB requiring multipart upload | `S3Service` | Has `uploadLargeFile()` method |
|
|
1152
|
-
| Need S3 Select (query CSV/JSON) | `S3Service` | Has `selectObjectContent()` method |
|
|
1153
|
-
| Need advanced AWS SDK features | `S3Service` | Direct SDK access |
|
|
1154
|
-
| Testing both implementations | `S3Service` | Has dual mode support |
|
|
1155
|
-
|
|
1156
|
-
### Architecture Comparison
|
|
1157
|
-
|
|
1158
|
-
**S3DataSource** (Recommended for most use cases):
|
|
1159
|
-
- ✅ Universal: Works in Node.js, Deno, Versori
|
|
1160
|
-
- ✅ Simple: Presigned URLs only, no mode switching
|
|
1161
|
-
- ✅ Lightweight: No AWS SDK bundled in application
|
|
1162
|
-
- ✅ Consistent: Same behavior everywhere
|
|
1163
|
-
- ❌ No multipart upload
|
|
1164
|
-
- ❌ No S3 Select
|
|
1165
|
-
|
|
1166
|
-
**S3Service** (For advanced features):
|
|
1167
|
-
- ✅ Multipart upload for large files
|
|
1168
|
-
- ✅ S3 Select query support
|
|
1169
|
-
- ✅ Direct AWS SDK access
|
|
1170
|
-
- ✅ Dual mode (SDK or presigned)
|
|
1171
|
-
- ❌ More complex API
|
|
1172
|
-
- ❌ Larger bundle size (includes AWS SDK)
|
|
1173
|
-
|
|
1174
|
-
---
|
|
1175
|
-
|
|
1176
|
-
## Best Practices
|
|
1177
|
-
|
|
1178
|
-
### 1. Choose the Right Class
|
|
1179
|
-
|
|
1180
|
-
- Use `S3DataSource` for standard file operations (most common)
|
|
1181
|
-
- Use `S3Service` only when you need multipart upload or S3 Select
|
|
1182
|
-
|
|
1183
|
-
### 2. Handle Large Files Properly
|
|
1184
|
-
|
|
1185
|
-
- `S3DataSource` works well for files up to 50MB
|
|
1186
|
-
- For files > 50MB, process in chunks to avoid memory issues
|
|
1187
|
-
- For files > 100MB requiring multipart, switch to `S3Service`
|
|
1188
|
-
|
|
1189
|
-
### 3. Implement Proper Error Handling
|
|
1190
|
-
|
|
1191
|
-
- Always catch and handle `DataSourceError`
|
|
1192
|
-
- Check `error.context.statusCode` for specific S3 errors (404, 403, etc.)
|
|
1193
|
-
- Built-in retry logic handles transient failures automatically
|
|
1194
|
-
- Log errors with context for debugging
|
|
1195
|
-
|
|
1196
|
-
### 4. Optimize List Operations
|
|
1197
|
-
|
|
1198
|
-
- Use `prefix` parameter to limit results to specific paths
|
|
1199
|
-
- Use `delimiter` for folder-like organization
|
|
1200
|
-
- Set appropriate `maxKeys` to control page size
|
|
1201
|
-
- Implement pagination with `continuationToken` for large buckets
|
|
1202
|
-
|
|
1203
|
-
### 5. Security Best Practices
|
|
1204
|
-
|
|
1205
|
-
- Never hardcode credentials in source code
|
|
1206
|
-
- Use environment variables for AWS credentials
|
|
1207
|
-
- Implement least-privilege IAM policies
|
|
1208
|
-
- Use separate credentials for source and target operations
|
|
1209
|
-
- Leverage session tokens for temporary credentials
|
|
1210
|
-
|
|
1211
|
-
### 6. Performance Optimization
|
|
1212
|
-
|
|
1213
|
-
- Implement parallel uploads/downloads with concurrency control (5-10 concurrent)
|
|
1214
|
-
- Use `encoding: 'binary'` for non-text files
|
|
1215
|
-
- Process files in chunks for very large datasets
|
|
1216
|
-
- Batch operations to reduce HTTP overhead
|
|
1217
|
-
|
|
1218
|
-
### 7. Data Organization
|
|
1219
|
-
|
|
1220
|
-
- Use prefixes to organize files (e.g., `inventory/2024/01/15/file.csv`)
|
|
1221
|
-
- Set appropriate content types - `S3DataSource` auto-detects from extensions
|
|
1222
|
-
- Add metadata to track processing status and timestamps
|
|
1223
|
-
- Clean up temporary files after processing
|
|
1224
|
-
|
|
1225
|
-
### 8. Platform Integration
|
|
1226
|
-
|
|
1227
|
-
- `S3DataSource` works identically in Node.js, Deno, and Versori
|
|
1228
|
-
- Optionally pass custom `httpClient` to constructor for connection pooling
|
|
1229
|
-
- Use structured logging to track operations across platforms
|
|
1230
|
-
|
|
1231
|
-
---
|
|
1232
|
-
|
|
1233
|
-
## Limitations
|
|
1234
|
-
|
|
1235
|
-
### S3DataSource Limitations
|
|
1236
|
-
|
|
1237
|
-
**What S3DataSource Does NOT Support**:
|
|
1238
|
-
- ❌ **Multipart Upload**: Cannot upload files > 5GB (S3 limit for single PUT)
|
|
1239
|
-
- ❌ **S3 Select**: Cannot query CSV/JSON files without downloading
|
|
1240
|
-
- ❌ **IAM Role Authentication**: Requires explicit credentials for presigned URLs
|
|
1241
|
-
- ❌ **Advanced AWS SDK Features**: No direct SDK access
|
|
1242
|
-
|
|
1243
|
-
**Design Trade-offs**:
|
|
1244
|
-
- **Presigned URL Expiry**: URLs expire after 1 hour (default) - sufficient for most operations
|
|
1245
|
-
- **XML Parsing**: List operations parse S3 XML responses (minimal overhead)
|
|
1246
|
-
- **Memory Usage**: Loads entire files into memory - process in chunks for files > 50MB
|
|
1247
|
-
- **URL Length Limits**: Presigned URLs limited to ~8KB (rarely an issue)
|
|
1248
|
-
|
|
1249
|
-
**When These Limitations Matter**:
|
|
1250
|
-
- Files > 100MB → Use `S3Service` with multipart upload
|
|
1251
|
-
- Need to query CSV/Parquet → Use `S3Service` with S3 Select
|
|
1252
|
-
- Running on AWS EC2/Lambda with IAM roles → Use `S3Service` to leverage IAM roles
|
|
1253
|
-
|
|
1254
|
-
### General Limitations
|
|
1255
|
-
|
|
1256
|
-
- All operations load files into memory (Buffer or string)
|
|
1257
|
-
- For very large files (> 100MB), implement chunked processing
|
|
1258
|
-
- Presigned URLs generated at request time (small latency overhead)
|
|
1259
|
-
- Built-in retry logic handles transient failures (no manual retry needed)
|
|
1260
|
-
|
|
1261
|
-
---
|
|
1262
|
-
|
|
1263
|
-
## API Reference
|
|
1264
|
-
|
|
1265
|
-
### S3DataSource Constructor
|
|
1266
|
-
|
|
1267
|
-
```typescript
|
|
1268
|
-
new S3DataSource(config: S3DataSourceConfig, logger?: StructuredLogger, httpClient?: typeof fetch)
|
|
1269
|
-
```
|
|
1270
|
-
|
|
1271
|
-
**Parameters**:
|
|
1272
|
-
|
|
1273
|
-
- `config`: S3 data source configuration
|
|
1274
|
-
- `logger`: Optional logging service
|
|
1275
|
-
- `httpClient`: Optional custom fetch implementation (defaults to global `fetch`)
|
|
1276
|
-
|
|
1277
|
-
### Methods
|
|
1278
|
-
|
|
1279
|
-
| Method | Parameters | Returns | Description |
|
|
1280
|
-
| ----------------------- | ---------------------------------- | --------------------------- | -------------------------------- |
|
|
1281
|
-
| `listFiles()` | `options?` | `Promise<FileMetadata[]>` | List files in bucket |
|
|
1282
|
-
| `downloadFile()` | `path, options?` | `Promise<string \| Buffer>` | Read file content |
|
|
1283
|
-
| `uploadFile()` | `path, content, options?` | `Promise<void>` | Write file to S3 |
|
|
1284
|
-
| `deleteFile()` | `path` | `Promise<void>` | Delete file from S3 |
|
|
1285
|
-
| `copyFile()` | `sourcePath, targetPath, options?` | `Promise<void>` | Copy file within/between buckets |
|
|
1286
|
-
| `moveFile()` | `sourcePath, targetPath` | `Promise<void>` | Move file (copy + delete) |
|
|
1287
|
-
| `validateConnection()` | - | `Promise<boolean>` | Test S3 connectivity |
|
|
1288
|
-
| `writeParquetContent()` | `records, options?` | `Promise<Buffer>` | Generate Parquet buffer |
|
|
1289
|
-
|
|
1290
|
-
---
|
|
1291
|
-
|
|
1292
|
-
## Next Steps
|
|
1293
|
-
|
|
1294
|
-
**Master SFTP operations** → [Module 3: SFTP Operations](./data-sources-03-sftp-operations.md)
|
|
1295
|
-
|
|
1296
|
-
**Learn file processing patterns** → [Module 4: File Processing Patterns](./data-sources-04-file-patterns.md)
|
|
1297
|
-
|
|
1298
|
-
**Explore advanced topics** → [Module 5: Advanced Topics](./data-sources-05-advanced-topics.md)
|
|
1299
|
-
|
|
1300
|
-
---
|
|
1301
|
-
|
|
1302
|
-
[← Back to Foundations](./data-sources-01-foundations.md) | [Next: SFTP Operations →](./data-sources-03-sftp-operations.md) | [↑ Back to Guide](../data-sources-readme.md)
|
|
1
|
+
# Module 2: S3 Operations
|
|
2
|
+
|
|
3
|
+
Complete guide to S3DataSource - presigned URL architecture, file operations, streaming, and production patterns.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Presigned URL Architecture](#presigned-url-architecture)
|
|
9
|
+
- [Configuration](#configuration)
|
|
10
|
+
- [File Operations](#file-operations)
|
|
11
|
+
- [Parquet Generation](#parquet-generation)
|
|
12
|
+
- [Streaming Patterns](#streaming-patterns)
|
|
13
|
+
- [Platform Integration](#platform-integration)
|
|
14
|
+
- [Production Patterns](#production-patterns)
|
|
15
|
+
- [TypeScript Types](#typescript-types)
|
|
16
|
+
- [Testing](#testing)
|
|
17
|
+
- [Troubleshooting](#troubleshooting)
|
|
18
|
+
- [Best Practices](#best-practices)
|
|
19
|
+
- [Limitations](#limitations)
|
|
20
|
+
- [API Reference](#api-reference)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Install the SDK which includes S3DataSource and all dependencies:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @fluentcommerce/fc-connect-sdk
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Dependencies**: The SDK uses AWS SDK v3 (^3.879.0) internally for presigned URL signature generation via `S3PresignService`, but `S3DataSource` itself uses native `fetch` for all S3 operations, making it compatible with Node.js, Deno, and Versori.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Presigned URL Architecture
|
|
37
|
+
|
|
38
|
+
### How S3DataSource Works
|
|
39
|
+
|
|
40
|
+
`S3DataSource` uses a **presigned-URL-only architecture** for universal platform compatibility:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
┌──────────────────────────────────────────────────────────┐
|
|
44
|
+
│ S3DataSource (fc-connect-sdk) │
|
|
45
|
+
└──────────────────┬───────────────────────────────────────┘
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
┌─────────────────────┐
|
|
49
|
+
│ S3PresignService │
|
|
50
|
+
│ (AWS SigV4 signing) │
|
|
51
|
+
└────────┬────────────┘
|
|
52
|
+
│
|
|
53
|
+
▼
|
|
54
|
+
Generate Presigned URL (1-hour expiry)
|
|
55
|
+
https://bucket.s3.region.amazonaws.com/key?
|
|
56
|
+
X-Amz-Algorithm=AWS4-HMAC-SHA256
|
|
57
|
+
&X-Amz-Credential=...
|
|
58
|
+
&X-Amz-Signature=...
|
|
59
|
+
│
|
|
60
|
+
▼
|
|
61
|
+
┌─────────────────────┐
|
|
62
|
+
│ Native fetch() │
|
|
63
|
+
│ (HTTP GET/PUT/...) │
|
|
64
|
+
└────────┬────────────┘
|
|
65
|
+
│
|
|
66
|
+
▼
|
|
67
|
+
┌─────────────────────┐
|
|
68
|
+
│ AWS S3 Bucket │
|
|
69
|
+
│ (validates sig) │
|
|
70
|
+
└─────────────────────┘
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Key Benefits**:
|
|
74
|
+
|
|
75
|
+
1. **No AWS SDK bundling** - Smaller package size, faster cold starts
|
|
76
|
+
2. **Universal compatibility** - Works in Node.js, Deno, Versori without changes
|
|
77
|
+
3. **Simple HTTP** - Standard `fetch()` API, easy to debug
|
|
78
|
+
4. **Stateless** - No connection pooling or state management
|
|
79
|
+
5. **Proxy-friendly** - Works with corporate proxies and firewalls
|
|
80
|
+
|
|
81
|
+
### Signature Generation Process
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// How presigned URLs are generated internally:
|
|
85
|
+
// 1. Create canonical request (method, path, query, headers)
|
|
86
|
+
// 2. Sign with AWS SigV4 using access key + secret key
|
|
87
|
+
// 3. Add signature to query string
|
|
88
|
+
// 4. URL valid for 1 hour (default)
|
|
89
|
+
|
|
90
|
+
// Example presigned GET URL:
|
|
91
|
+
https://my-bucket.s3.us-east-1.amazonaws.com/inventory/file.csv?
|
|
92
|
+
X-Amz-Algorithm=AWS4-HMAC-SHA256&
|
|
93
|
+
X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20250115%2Fus-east-1%2Fs3%2Faws4_request&
|
|
94
|
+
X-Amz-Date=20250115T120000Z&
|
|
95
|
+
X-Amz-Expires=3600&
|
|
96
|
+
X-Amz-SignedHeaders=host&
|
|
97
|
+
X-Amz-Signature=abc123...
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Security Notes**:
|
|
101
|
+
|
|
102
|
+
- Presigned URLs expire after 1 hour (3600 seconds)
|
|
103
|
+
- Anyone with the URL can access the file during expiry window
|
|
104
|
+
- Signatures are cryptographically secure (SHA256 HMAC)
|
|
105
|
+
- No credentials exposed in URL (only signature)
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
### Basic Configuration
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import {
|
|
115
|
+
S3DataSource,
|
|
116
|
+
createConsoleLogger,
|
|
117
|
+
toStructuredLogger,
|
|
118
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
119
|
+
|
|
120
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
121
|
+
logLevel: 'info',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Minimal S3 configuration
|
|
125
|
+
const s3Source = new S3DataSource(
|
|
126
|
+
{
|
|
127
|
+
type: 'S3_CSV', // S3_JSON, S3_XML, S3_PARQUET
|
|
128
|
+
connectionId: 's3-minimal',
|
|
129
|
+
name: 'S3 Minimal',
|
|
130
|
+
s3Config: {
|
|
131
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
132
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
133
|
+
region: 'us-east-1',
|
|
134
|
+
bucket: 'my-bucket', // Required
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
logger
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Configuration Options
|
|
142
|
+
|
|
143
|
+
| Property | Required | Description | Default | Example |
|
|
144
|
+
| -------------------------- | -------- | ----------------------------------- | ----------- | ------------------------- |
|
|
145
|
+
| `type` | ✅ | Data source type | - | `'S3_CSV'` |
|
|
146
|
+
| `s3Config.accessKeyId` | ✅ | AWS access key ID | - | `'AKIAIOSFODNN7...'` |
|
|
147
|
+
| `s3Config.secretAccessKey` | ✅ | AWS secret access key | - | `'wJalrXUtnFEMI/...'` |
|
|
148
|
+
| `s3Config.region` | ✅ | AWS region | - | `'us-east-1'` |
|
|
149
|
+
| `s3Config.bucket` | ❌ | Default bucket name | - | `'inventory-data'` |
|
|
150
|
+
| `s3Config.sessionToken` | ❌ | STS session token (temporary creds) | - | `'FwoGZXIv...'` |
|
|
151
|
+
| `s3Config.endpoint` | ❌ | Custom S3 endpoint (for LocalStack) | AWS default | `'http://localhost:4566'` |
|
|
152
|
+
| `s3Config.maxAttempts` | ❌ | Max retry attempts | `3` | `5` |
|
|
153
|
+
|
|
154
|
+
### Enhanced Retry Logic
|
|
155
|
+
|
|
156
|
+
S3DataSource includes automatic retry with exponential backoff for all HTTP operations.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const s3Source = new S3DataSource(
|
|
160
|
+
{
|
|
161
|
+
type: 'S3_CSV',
|
|
162
|
+
connectionId: 's3-retry',
|
|
163
|
+
name: 'S3 Retry',
|
|
164
|
+
s3Config: {
|
|
165
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
166
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
167
|
+
region: 'us-east-1',
|
|
168
|
+
bucket: 'my-bucket',
|
|
169
|
+
maxAttempts: 5, // Default: 3, increase for unreliable networks
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
logger
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// All operations automatically retry on failure:
|
|
176
|
+
await s3Source.downloadFile('data.csv'); // Retries on 429, 500, 502, 503, 504, network errors
|
|
177
|
+
await s3Source.uploadFile('output.json', data); // Retries automatically
|
|
178
|
+
await s3Source.listFiles({ prefix: 'inventory/' }); // Retries automatically
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Retry Behavior:**
|
|
182
|
+
|
|
183
|
+
| Attempt | Delay | Total Wait | Retryable Conditions |
|
|
184
|
+
| -------------------- | ------ | ---------- | -------------------------------------------- |
|
|
185
|
+
| 1 | 0ms | 0ms | HTTP 429, 500, 502, 503, 504, network errors |
|
|
186
|
+
| 2 | 500ms | 500ms | Same conditions |
|
|
187
|
+
| 3 (default max) | 1000ms | 1.5s | Same conditions |
|
|
188
|
+
| 4 (if maxAttempts=4) | 2000ms | 3.5s | Same conditions |
|
|
189
|
+
| 5 (if maxAttempts=5) | 4000ms | 7.5s | Same conditions |
|
|
190
|
+
|
|
191
|
+
**Backoff Formula:**
|
|
192
|
+
|
|
193
|
+
- Base delay: 500ms
|
|
194
|
+
- Factor: 2 (exponential)
|
|
195
|
+
- Max delay per attempt: 8000ms (8 seconds)
|
|
196
|
+
- Formula: `min(8000ms, 500ms * 2^(attempt-1))`
|
|
197
|
+
|
|
198
|
+
**What Gets Retried:**
|
|
199
|
+
|
|
200
|
+
- ✅ Rate limiting (429 Too Many Requests)
|
|
201
|
+
- ✅ Server errors (500, 502, 503, 504)
|
|
202
|
+
- ✅ Network failures (connection timeout, DNS errors)
|
|
203
|
+
- ✅ Transient S3 errors
|
|
204
|
+
|
|
205
|
+
**What Does NOT Get Retried:**
|
|
206
|
+
|
|
207
|
+
- ❌ Client errors (400, 403, 404) - permanent failures
|
|
208
|
+
- ❌ Authentication errors (401) - credential issues
|
|
209
|
+
- ❌ Successful responses (2xx)
|
|
210
|
+
|
|
211
|
+
**Logging:**
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Retry attempts are automatically logged at WARN level
|
|
215
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
216
|
+
logLevel: 'debug',
|
|
217
|
+
});
|
|
218
|
+
const s3Source = new S3DataSource(config, logger);
|
|
219
|
+
|
|
220
|
+
await s3Source.downloadFile('data.csv');
|
|
221
|
+
// Logs on retry:
|
|
222
|
+
// - "S3 HTTP retry due to status" (for 429, 500, etc.)
|
|
223
|
+
// - "S3 HTTP retry due to network error" (for connection failures)
|
|
224
|
+
// - Includes: operationName, status, attempt, delay
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Production Recommendation:**
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// For production, increase maxAttempts for better resilience
|
|
231
|
+
const productionConfig = {
|
|
232
|
+
type: 'S3_CSV',
|
|
233
|
+
s3Config: {
|
|
234
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
235
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
236
|
+
region: 'us-east-1',
|
|
237
|
+
bucket: 'production-bucket',
|
|
238
|
+
maxAttempts: 5, // 5 attempts = up to 7.5s of automatic retries
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Environment Variables Pattern
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# .env file
|
|
247
|
+
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
|
248
|
+
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
249
|
+
AWS_REGION=us-east-1
|
|
250
|
+
AWS_SESSION_TOKEN=FwoGZXIv... # Optional
|
|
251
|
+
SOURCE_BUCKET=inventory-source
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// Load from environment
|
|
256
|
+
const s3Source = new S3DataSource(
|
|
257
|
+
{
|
|
258
|
+
type: 'S3_CSV',
|
|
259
|
+
connectionId: 's3-env',
|
|
260
|
+
name: 'S3 Environment',
|
|
261
|
+
s3Config: {
|
|
262
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
263
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
264
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
265
|
+
bucket: process.env.SOURCE_BUCKET,
|
|
266
|
+
sessionToken: process.env.AWS_SESSION_TOKEN, // Optional
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
logger
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Dual AWS Credentials (Source + Target)
|
|
274
|
+
|
|
275
|
+
Use separate AWS accounts for reading (source) and writing (target):
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Source S3 (read inventory from vendor's AWS account)
|
|
279
|
+
const sourceS3 = new S3DataSource(
|
|
280
|
+
{
|
|
281
|
+
type: 'S3_CSV',
|
|
282
|
+
connectionId: 's3-source-vendor',
|
|
283
|
+
name: 'S3 Source Vendor',
|
|
284
|
+
s3Config: {
|
|
285
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
286
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
287
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
288
|
+
bucket: process.env.SOURCE_BUCKET,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
logger
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Target S3 (write extraction results to our data lake)
|
|
295
|
+
const targetS3 = new S3DataSource(
|
|
296
|
+
{
|
|
297
|
+
type: 'S3_PARQUET',
|
|
298
|
+
connectionId: 's3-target-own',
|
|
299
|
+
name: 'S3 Target Own',
|
|
300
|
+
s3Config: {
|
|
301
|
+
accessKeyId: process.env.TARGET_AWS_ACCESS_KEY_ID!,
|
|
302
|
+
secretAccessKey: process.env.TARGET_AWS_SECRET_ACCESS_KEY!,
|
|
303
|
+
region: process.env.TARGET_AWS_REGION || 'us-west-2',
|
|
304
|
+
bucket: process.env.TARGET_BUCKET,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
logger
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Usage: Read from vendor, write to our data lake
|
|
311
|
+
const inventory = await sourceS3.downloadFile('s3://vendor-bucket/inventory.csv');
|
|
312
|
+
const processed = await processInventory(inventory);
|
|
313
|
+
await targetS3.uploadFile('s3://our-datalake/processed.parquet', processed);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## File Operations
|
|
319
|
+
|
|
320
|
+
### Listing Files
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// List all files in bucket
|
|
324
|
+
const allFiles = await s3Source.listFiles();
|
|
325
|
+
|
|
326
|
+
// List files with prefix (folder-like structure)
|
|
327
|
+
const inventoryFiles = await s3Source.listFiles({
|
|
328
|
+
prefix: 'inventory/',
|
|
329
|
+
maxKeys: 100,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// List with delimiter (folders)
|
|
333
|
+
const topLevel = await s3Source.listFiles({
|
|
334
|
+
prefix: 'data/',
|
|
335
|
+
delimiter: '/',
|
|
336
|
+
maxKeys: 1000,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Process file metadata
|
|
340
|
+
for (const file of inventoryFiles) {
|
|
341
|
+
console.log({
|
|
342
|
+
path: file.path,
|
|
343
|
+
name: file.name,
|
|
344
|
+
size: file.size,
|
|
345
|
+
lastModified: file.lastModified,
|
|
346
|
+
contentType: file.contentType,
|
|
347
|
+
etag: file.etag,
|
|
348
|
+
bucket: file.bucket,
|
|
349
|
+
region: file.region,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**List Options**:
|
|
355
|
+
|
|
356
|
+
| Option | Type | Description |
|
|
357
|
+
| ------------------- | -------- | -------------------------------------------------- |
|
|
358
|
+
| `prefix` | `string` | Filter files by prefix (e.g., `'inventory/'`) |
|
|
359
|
+
| `delimiter` | `string` | Delimiter for folder-like structure (e.g., `'/'`) |
|
|
360
|
+
| `maxKeys` | `number` | Maximum files to return (default: 1000, max: 1000) |
|
|
361
|
+
| `continuationToken` | `string` | Token for pagination (from previous response) |
|
|
362
|
+
|
|
363
|
+
**Example: List files modified in last 24 hours**
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
const files = await s3Source.listFiles({ prefix: 'incoming/' });
|
|
367
|
+
|
|
368
|
+
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
369
|
+
const recentFiles = files.filter(file => file.lastModified > yesterday);
|
|
370
|
+
|
|
371
|
+
console.log(`${recentFiles.length} files modified in last 24 hours`);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Reading Files
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// Read text file (CSV, JSON, etc.)
|
|
378
|
+
const csvContent = await s3Source.downloadFile('inventory.csv');
|
|
379
|
+
// Returns: string
|
|
380
|
+
|
|
381
|
+
// Read binary file (Parquet, images, etc.)
|
|
382
|
+
const parquetData = await s3Source.downloadFile('data.parquet', { encoding: 'binary' });
|
|
383
|
+
// Returns: Buffer
|
|
384
|
+
|
|
385
|
+
// Read JSON with parsing
|
|
386
|
+
const jsonContent = await s3Source.downloadFile('config.json');
|
|
387
|
+
const config = JSON.parse(jsonContent as string);
|
|
388
|
+
|
|
389
|
+
// For existence checks, list by prefix and filter, or handle 404 on download
|
|
390
|
+
if (await s3Source.listFiles({ prefix: 'file.csv' }).then(files => files.length > 0)) {
|
|
391
|
+
const content = await s3Source.downloadFile('file.csv');
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Read Options**:
|
|
396
|
+
|
|
397
|
+
| Option | Type | Description | Default |
|
|
398
|
+
| ----------------- | ------------------------ | ----------------------- | -------- |
|
|
399
|
+
| `encoding` | `'utf8' \| 'binary'` | Text or binary encoding | `'utf8'` |
|
|
400
|
+
| `responseHeaders` | `Record<string, string>` | Custom HTTP headers | `{}` |
|
|
401
|
+
|
|
402
|
+
**Example: Read with error handling**
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
try {
|
|
406
|
+
const content = await s3Source.downloadFile('file.csv');
|
|
407
|
+
console.log('File read successfully');
|
|
408
|
+
} catch (error: any) {
|
|
409
|
+
if (error.context?.statusCode === 404) {
|
|
410
|
+
console.log('File not found');
|
|
411
|
+
} else if (error.context?.statusCode === 403) {
|
|
412
|
+
console.log('Access denied - check IAM permissions');
|
|
413
|
+
} else {
|
|
414
|
+
console.error('S3 error:', error.message);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Writing Files
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// Write CSV file
|
|
423
|
+
await s3Source.uploadFile('output/results.csv', csvData, { contentType: 'text/csv' });
|
|
424
|
+
|
|
425
|
+
// Write JSON with metadata
|
|
426
|
+
await s3Source.uploadFile('output/results.json', JSON.stringify(data, null, 2), {
|
|
427
|
+
contentType: 'application/json',
|
|
428
|
+
metadata: {
|
|
429
|
+
'processed-date': new Date().toISOString(),
|
|
430
|
+
version: '1.0',
|
|
431
|
+
processor: 'fc-connect-sdk',
|
|
432
|
+
'record-count': data.length.toString(),
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Write binary file (Parquet, images, etc.)
|
|
437
|
+
await s3Source.uploadFile('output/data.parquet', parquetBuffer, {
|
|
438
|
+
contentType: 'application/octet-stream',
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Automatic content type detection from extension
|
|
442
|
+
await s3Source.uploadFile('file.csv', csvData);
|
|
443
|
+
// Automatically sets contentType: 'text/csv'
|
|
444
|
+
|
|
445
|
+
await s3Source.uploadFile('file.json', jsonData);
|
|
446
|
+
// Automatically sets contentType: 'application/json'
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Write Options**:
|
|
450
|
+
|
|
451
|
+
| Option | Type | Description |
|
|
452
|
+
| ----------------- | ------------------------ | -------------------------------------------------------- |
|
|
453
|
+
| `contentType` | `string` | MIME type (auto-detected from extension if not provided) |
|
|
454
|
+
| `metadata` | `Record<string, string>` | Custom S3 object metadata (key-value pairs) |
|
|
455
|
+
| `cacheControl` | `string` | HTTP Cache-Control header |
|
|
456
|
+
| `contentEncoding` | `string` | HTTP Content-Encoding header (e.g., `'gzip'`) |
|
|
457
|
+
|
|
458
|
+
### Deleting Files
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// Delete single file
|
|
462
|
+
await s3Source.deleteFile('temp/old-file.csv');
|
|
463
|
+
|
|
464
|
+
// Delete with error handling
|
|
465
|
+
try {
|
|
466
|
+
await s3Source.deleteFile('important-file.json');
|
|
467
|
+
console.log('File deleted successfully');
|
|
468
|
+
} catch (error: any) {
|
|
469
|
+
if (error.context?.statusCode === 404) {
|
|
470
|
+
console.log('File already deleted or does not exist');
|
|
471
|
+
} else {
|
|
472
|
+
console.error('Delete failed:', error.message);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Delete multiple files (batch)
|
|
477
|
+
const filesToDelete = [
|
|
478
|
+
's3://bucket/temp/file1.csv',
|
|
479
|
+
's3://bucket/temp/file2.csv',
|
|
480
|
+
's3://bucket/temp/file3.csv',
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
for (const file of filesToDelete) {
|
|
484
|
+
try {
|
|
485
|
+
await s3Source.deleteFile(file);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error(`Failed to delete ${file}:`, error);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Copying Files
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// Copy within same bucket
|
|
496
|
+
await s3Source.copyFile('source/data.csv', 'archive/data.csv');
|
|
497
|
+
|
|
498
|
+
// Copy from different bucket
|
|
499
|
+
await s3Source.copyFile('file.json', 'backup/file.json', { sourceBucket: 'source-bucket' });
|
|
500
|
+
|
|
501
|
+
// Copy with metadata
|
|
502
|
+
await s3Source.copyFile('source.csv', 'archive/source.csv', {
|
|
503
|
+
metadata: {
|
|
504
|
+
'copied-from': 'source.csv',
|
|
505
|
+
'copy-date': new Date().toISOString(),
|
|
506
|
+
'archived-by': 'fc-connect-sdk',
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Moving Files (Copy + Delete)
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// Move file within bucket
|
|
515
|
+
await s3Source.moveFile('incoming/data.csv', 'processed/data.csv');
|
|
516
|
+
|
|
517
|
+
// Move to archive with date-based organization
|
|
518
|
+
const today = new Date().toISOString().split('T')[0];
|
|
519
|
+
await s3Source.moveFile('incoming/inventory.csv', `archive/${today}/inventory.csv`);
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**IMPORTANT**: `moveFile()` is implemented as copy + delete, which is **NOT atomic**. If delete fails, the file exists in both locations. For critical operations:
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// Manual atomic-like move with verification
|
|
526
|
+
try {
|
|
527
|
+
// Copy
|
|
528
|
+
await s3Source.copyFile(sourcePath, targetPath);
|
|
529
|
+
|
|
530
|
+
// Verify copy succeeded
|
|
531
|
+
const exists = await s3Source.listFiles({ prefix: targetPath }).then(files => files.length > 0);
|
|
532
|
+
if (!exists) {
|
|
533
|
+
throw new Error('Copy verification failed');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Delete original
|
|
537
|
+
await s3Source.deleteFile(sourcePath);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error('Move failed:', error);
|
|
540
|
+
// Cleanup: delete target if it exists
|
|
541
|
+
try {
|
|
542
|
+
await s3Source.deleteFile(targetPath);
|
|
543
|
+
} catch {}
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Checking File Existence
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
// Use listFiles + filter or handle 404 on download to determine existence
|
|
551
|
+
if (await s3Source.listFiles({ prefix: 'config.json' }).then(files => files.length > 0)) {
|
|
552
|
+
const config = await s3Source.downloadFile('config.json');
|
|
553
|
+
} else {
|
|
554
|
+
console.log('Config file not found, using defaults');
|
|
555
|
+
const config = defaultConfig;
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Parquet Generation
|
|
562
|
+
|
|
563
|
+
`S3DataSource` includes built-in Parquet file generation for efficient data storage and analytics:
|
|
564
|
+
|
|
565
|
+
### Basic Parquet Generation
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
import { S3DataSource, ParquetDataRecord } from '@fluentcommerce/fc-connect-sdk';
|
|
569
|
+
|
|
570
|
+
// Prepare data records
|
|
571
|
+
const records: ParquetDataRecord[] = [
|
|
572
|
+
{ id: 1, sku: 'PROD-001', quantity: 100, price: 29.99, available: true },
|
|
573
|
+
{ id: 2, sku: 'PROD-002', quantity: 200, price: 39.99, available: false },
|
|
574
|
+
{ id: 3, sku: 'PROD-003', quantity: 150, price: 49.99, available: true },
|
|
575
|
+
];
|
|
576
|
+
|
|
577
|
+
// Generate Parquet buffer
|
|
578
|
+
const parquetBuffer = await s3Source.writeParquetContent(records, {
|
|
579
|
+
compression: 'GZIP', // Options: UNCOMPRESSED, GZIP, SNAPPY, LZO, BROTLI
|
|
580
|
+
rowGroupSize: 50000, // Rows per group (default: 50000)
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Upload to S3
|
|
584
|
+
await s3Source.uploadFile('s3://bucket/output/inventory.parquet', parquetBuffer, {
|
|
585
|
+
contentType: 'application/octet-stream',
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Automatic Schema Inference
|
|
590
|
+
|
|
591
|
+
Parquet schema is automatically inferred from record structure:
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// JavaScript type → Parquet type mapping
|
|
595
|
+
const records = [
|
|
596
|
+
{
|
|
597
|
+
id: 1, // number (int) → INT64
|
|
598
|
+
sku: 'PROD-001', // string → UTF8
|
|
599
|
+
quantity: 100, // number (int) → INT64
|
|
600
|
+
price: 29.99, // number (float) → DOUBLE
|
|
601
|
+
available: true, // boolean → BOOLEAN
|
|
602
|
+
lastUpdated: new Date(), // Date → TIMESTAMP_MILLIS
|
|
603
|
+
metadata: { tags: ['new', 'hot'] }, // object → UTF8 (JSON serialized)
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
const buffer = await s3Source.writeParquetContent(records);
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Type Mapping**:
|
|
611
|
+
|
|
612
|
+
- `string` → UTF8
|
|
613
|
+
- `number` (integer) → INT64
|
|
614
|
+
- `number` (float/decimal) → DOUBLE
|
|
615
|
+
- `boolean` → BOOLEAN
|
|
616
|
+
- `Date` → TIMESTAMP_MILLIS
|
|
617
|
+
- `object` → UTF8 (JSON serialized)
|
|
618
|
+
- `null` / `undefined` → NULL
|
|
619
|
+
|
|
620
|
+
### Compression Options
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
// No compression (fastest, largest file)
|
|
624
|
+
const uncompressed = await s3Source.writeParquetContent(records, {
|
|
625
|
+
compression: 'UNCOMPRESSED',
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// GZIP compression (good balance, widely supported)
|
|
629
|
+
const gzipped = await s3Source.writeParquetContent(records, {
|
|
630
|
+
compression: 'GZIP',
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// SNAPPY compression (fastest compression, good for analytics)
|
|
634
|
+
const snappy = await s3Source.writeParquetContent(records, {
|
|
635
|
+
compression: 'SNAPPY',
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// BROTLI compression (best compression ratio)
|
|
639
|
+
const brotli = await s3Source.writeParquetContent(records, {
|
|
640
|
+
compression: 'BROTLI',
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**Compression Comparison**:
|
|
645
|
+
|
|
646
|
+
| Algorithm | Speed | Compression Ratio | Use Case |
|
|
647
|
+
| ------------ | --------- | ----------------- | ----------------------------- |
|
|
648
|
+
| UNCOMPRESSED | Fastest | 1:1 | Small files, temp data |
|
|
649
|
+
| SNAPPY | Very fast | 2:1 to 3:1 | Analytics, real-time queries |
|
|
650
|
+
| GZIP | Medium | 4:1 to 6:1 | General purpose, wide support |
|
|
651
|
+
| BROTLI | Slowest | 5:1 to 8:1 | Long-term storage, archival |
|
|
652
|
+
|
|
653
|
+
### Complex Object Handling
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
// Complex nested objects are automatically serialized to JSON
|
|
657
|
+
const records = [
|
|
658
|
+
{
|
|
659
|
+
id: 1,
|
|
660
|
+
productDetails: {
|
|
661
|
+
name: 'Product A',
|
|
662
|
+
category: 'Electronics',
|
|
663
|
+
attributes: { color: 'black', size: 'M' },
|
|
664
|
+
},
|
|
665
|
+
tags: ['new', 'featured', 'sale'],
|
|
666
|
+
timestamp: new Date(),
|
|
667
|
+
},
|
|
668
|
+
];
|
|
669
|
+
|
|
670
|
+
const buffer = await s3Source.writeParquetContent(records);
|
|
671
|
+
// productDetails and tags are stored as JSON strings in Parquet
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Empty Record Handling
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
// Empty arrays return minimal valid Parquet file
|
|
678
|
+
const emptyBuffer = await s3Source.writeParquetContent([], {
|
|
679
|
+
compression: 'GZIP',
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Still a valid Parquet file (can be opened by analytics tools)
|
|
683
|
+
await s3Source.uploadFile('s3://bucket/empty.parquet', emptyBuffer);
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
---
|
|
687
|
+
|
|
688
|
+
## Streaming Patterns
|
|
689
|
+
|
|
690
|
+
### Processing Large Files
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
// Download large file in chunks to minimize memory usage
|
|
694
|
+
async function processLargeFile(dataSource: S3DataSource, key: string) {
|
|
695
|
+
// Download file (S3DataSource buffers in memory)
|
|
696
|
+
const content = await dataSource.downloadFile(key, { encoding: 'binary' });
|
|
697
|
+
|
|
698
|
+
// Process in chunks
|
|
699
|
+
const chunkSize = 1024 * 1024; // 1MB chunks
|
|
700
|
+
let offset = 0;
|
|
701
|
+
|
|
702
|
+
while (offset < content.length) {
|
|
703
|
+
const chunk = (content as Buffer).slice(offset, offset + chunkSize);
|
|
704
|
+
await processChunk(chunk);
|
|
705
|
+
offset += chunkSize;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function processChunk(chunk: Buffer) {
|
|
710
|
+
console.log(`Processing ${chunk.length} bytes`);
|
|
711
|
+
// Your processing logic here
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
**Note**: For very large files (>100MB), consider using S3 Select or reading directly with AWS SDK if memory is constrained.
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Platform Integration
|
|
720
|
+
|
|
721
|
+
### Node.js
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
import {
|
|
725
|
+
S3DataSource,
|
|
726
|
+
createConsoleLogger,
|
|
727
|
+
toStructuredLogger,
|
|
728
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
729
|
+
|
|
730
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
731
|
+
logLevel: 'info',
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const s3Source = new S3DataSource(
|
|
735
|
+
{
|
|
736
|
+
type: 'S3_CSV',
|
|
737
|
+
s3Config: {
|
|
738
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
739
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
740
|
+
region: 'us-east-1',
|
|
741
|
+
bucket: 'my-bucket',
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
logger
|
|
745
|
+
);
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### Deno
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
import {
|
|
752
|
+
S3DataSource,
|
|
753
|
+
createConsoleLogger,
|
|
754
|
+
toStructuredLogger,
|
|
755
|
+
} from 'npm:@fluentcommerce/fc-connect-sdk@latest';
|
|
756
|
+
|
|
757
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
758
|
+
logLevel: 'info',
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
const s3Source = new S3DataSource(
|
|
762
|
+
{
|
|
763
|
+
type: 'S3_CSV',
|
|
764
|
+
s3Config: {
|
|
765
|
+
accessKeyId: Deno.env.get('AWS_ACCESS_KEY_ID')!,
|
|
766
|
+
secretAccessKey: Deno.env.get('AWS_SECRET_ACCESS_KEY')!,
|
|
767
|
+
region: 'us-east-1',
|
|
768
|
+
bucket: 'my-bucket',
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
logger
|
|
772
|
+
);
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Versori
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
import {
|
|
779
|
+
S3DataSource,
|
|
780
|
+
createConsoleLogger,
|
|
781
|
+
toStructuredLogger,
|
|
782
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
783
|
+
import { http } from '@versori/run';
|
|
784
|
+
|
|
785
|
+
export const processFiles = http('s3-process', async ({ activation, log }) => {
|
|
786
|
+
const logger = toStructuredLogger(createConsoleLogger(), {
|
|
787
|
+
logLevel: 'info',
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const s3Source = new S3DataSource(
|
|
791
|
+
{
|
|
792
|
+
type: 'S3_CSV',
|
|
793
|
+
s3Config: {
|
|
794
|
+
accessKeyId: activation.env.AWS_ACCESS_KEY_ID,
|
|
795
|
+
secretAccessKey: activation.env.AWS_SECRET_ACCESS_KEY,
|
|
796
|
+
region: activation.env.AWS_REGION,
|
|
797
|
+
bucket: activation.env.S3_BUCKET,
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
logger
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
804
|
+
// Process files...
|
|
805
|
+
});
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## Production Patterns
|
|
811
|
+
|
|
812
|
+
### Pattern 1: Inventory Ingestion from S3
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
import {
|
|
816
|
+
S3DataSource,
|
|
817
|
+
CSVParserService,
|
|
818
|
+
UniversalMapper,
|
|
819
|
+
StateService,
|
|
820
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
821
|
+
|
|
822
|
+
async function ingestInventoryFromS3() {
|
|
823
|
+
const s3Source = new S3DataSource(
|
|
824
|
+
{
|
|
825
|
+
/* config */
|
|
826
|
+
},
|
|
827
|
+
logger
|
|
828
|
+
);
|
|
829
|
+
const csvParser = new CSVParserService();
|
|
830
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
831
|
+
const stateService = new StateService(kvAdapter);
|
|
832
|
+
|
|
833
|
+
// List inventory files
|
|
834
|
+
const files = await s3Source.listFiles({ prefix: 'inventory/' });
|
|
835
|
+
|
|
836
|
+
for (const file of files) {
|
|
837
|
+
// Check if already processed
|
|
838
|
+
const processed = await stateService.isFileProcessed(file.etag);
|
|
839
|
+
if (processed) {
|
|
840
|
+
console.log(`Skipping already processed file: ${file.name}`);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
try {
|
|
845
|
+
// Download and parse CSV
|
|
846
|
+
const content = await s3Source.downloadFile(file.path);
|
|
847
|
+
const records = await csvParser.parse(content as string);
|
|
848
|
+
|
|
849
|
+
// Map fields
|
|
850
|
+
const result = await mapper.map(records);
|
|
851
|
+
if (!result.success) {
|
|
852
|
+
logger.error('Mapping failed', undefined, { errors: result.errors });
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Send to Fluent (using Batch API)
|
|
857
|
+
await sendToFluent(result.data);
|
|
858
|
+
|
|
859
|
+
// Mark as processed
|
|
860
|
+
await stateService.markFileProcessed(file.etag, {
|
|
861
|
+
fileName: file.name,
|
|
862
|
+
recordCount: records.length,
|
|
863
|
+
processedAt: new Date().toISOString(),
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
// Archive file
|
|
867
|
+
await s3Source.moveFile(file.path, file.path.replace('inventory/', 'processed/'));
|
|
868
|
+
|
|
869
|
+
logger.info('File processed successfully', { file: file.name });
|
|
870
|
+
} catch (error) {
|
|
871
|
+
logger.error('Failed to process file', error as Error, { file: file.path });
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### Pattern 2: Data Extraction to S3
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
async function extractInventoryToS3(client: FluentClient, targetS3: S3DataSource) {
|
|
881
|
+
// Query Fluent inventory
|
|
882
|
+
const inventory = await client.graphql({
|
|
883
|
+
query: `query GetInventory {
|
|
884
|
+
virtualPositions(first: 1000) {
|
|
885
|
+
edges {
|
|
886
|
+
node {
|
|
887
|
+
ref
|
|
888
|
+
productRef
|
|
889
|
+
groupRef
|
|
890
|
+
quantity
|
|
891
|
+
status
|
|
892
|
+
updatedOn
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}`,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const records = inventory.virtualPositions.edges.map(e => e.node);
|
|
900
|
+
|
|
901
|
+
// Generate Parquet
|
|
902
|
+
const parquetBuffer = await targetS3.writeParquetContent(records, {
|
|
903
|
+
compression: 'SNAPPY',
|
|
904
|
+
rowGroupSize: 50000,
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Upload with timestamp
|
|
908
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
909
|
+
const outputPath = `s3://extraction-bucket/inventory/${timestamp}/full-export.parquet`;
|
|
910
|
+
|
|
911
|
+
await targetS3.uploadFile(outputPath, parquetBuffer, {
|
|
912
|
+
contentType: 'application/octet-stream',
|
|
913
|
+
metadata: {
|
|
914
|
+
'extraction-timestamp': timestamp,
|
|
915
|
+
'record-count': records.length.toString(),
|
|
916
|
+
compression: 'SNAPPY',
|
|
917
|
+
},
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
logger.info('Extraction completed', { path: outputPath, records: records.length });
|
|
921
|
+
}
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## TypeScript Types
|
|
927
|
+
|
|
928
|
+
```typescript
|
|
929
|
+
// S3 Configuration
|
|
930
|
+
interface S3Config {
|
|
931
|
+
accessKeyId: string;
|
|
932
|
+
secretAccessKey: string;
|
|
933
|
+
region: string;
|
|
934
|
+
sessionToken?: string;
|
|
935
|
+
bucket?: string;
|
|
936
|
+
endpoint?: string;
|
|
937
|
+
forcePathStyle?: boolean;
|
|
938
|
+
maxAttempts?: number;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// S3DataSource Configuration
|
|
942
|
+
interface S3DataSourceConfig {
|
|
943
|
+
type: 'S3_CSV' | 'S3_JSON' | 'S3_XML' | 'S3_PARQUET';
|
|
944
|
+
connectionId: string;
|
|
945
|
+
name: string;
|
|
946
|
+
s3Config: S3Config;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// File Metadata from S3DataSource
|
|
950
|
+
interface FileMetadata {
|
|
951
|
+
path: string;
|
|
952
|
+
name: string;
|
|
953
|
+
size: number;
|
|
954
|
+
lastModified: Date;
|
|
955
|
+
contentType?: string;
|
|
956
|
+
etag?: string;
|
|
957
|
+
bucket: string;
|
|
958
|
+
region: string;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// List Options
|
|
962
|
+
interface ListOptions {
|
|
963
|
+
prefix?: string;
|
|
964
|
+
delimiter?: string;
|
|
965
|
+
maxKeys?: number;
|
|
966
|
+
continuationToken?: string;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Download Options
|
|
970
|
+
interface DownloadOptions {
|
|
971
|
+
encoding?: 'utf8' | 'binary';
|
|
972
|
+
responseHeaders?: Record<string, string>;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Upload Options
|
|
976
|
+
interface UploadOptions {
|
|
977
|
+
contentType?: string;
|
|
978
|
+
metadata?: Record<string, string>;
|
|
979
|
+
cacheControl?: string;
|
|
980
|
+
contentEncoding?: string;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Copy Options
|
|
984
|
+
interface CopyOptions {
|
|
985
|
+
sourceBucket?: string;
|
|
986
|
+
metadata?: Record<string, string>;
|
|
987
|
+
metadataDirective?: 'COPY' | 'REPLACE';
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Parquet Options
|
|
991
|
+
interface ParquetOptions {
|
|
992
|
+
compression?: 'UNCOMPRESSED' | 'GZIP' | 'SNAPPY' | 'LZO' | 'BROTLI';
|
|
993
|
+
rowGroupSize?: number;
|
|
994
|
+
schema?: Record<string, string>;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Parquet Data Record
|
|
998
|
+
interface ParquetDataRecord {
|
|
999
|
+
[key: string]: string | number | boolean | Date | object | null;
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
---
|
|
1004
|
+
|
|
1005
|
+
## Testing
|
|
1006
|
+
|
|
1007
|
+
The SDK includes test utilities for validating S3 operations:
|
|
1008
|
+
|
|
1009
|
+
```typescript
|
|
1010
|
+
import {
|
|
1011
|
+
S3SDKTester,
|
|
1012
|
+
S3PresignedTester,
|
|
1013
|
+
S3ComparisonTester
|
|
1014
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
1015
|
+
|
|
1016
|
+
// Test SDK implementation (if using S3Service)
|
|
1017
|
+
const sdkTester = new S3SDKTester(config);
|
|
1018
|
+
const sdkResults = await sdkTester.testAll('test-bucket');
|
|
1019
|
+
|
|
1020
|
+
// Test Presigned implementation
|
|
1021
|
+
const presignedTester = new S3PresignedTester(config, fetch);
|
|
1022
|
+
const presignedResults = await presignedTester.testAll('test-bucket');
|
|
1023
|
+
|
|
1024
|
+
// Compare both implementations
|
|
1025
|
+
const comparer = new S3ComparisonTester();
|
|
1026
|
+
const comparison = await comparer.compareImplementations(
|
|
1027
|
+
config,
|
|
1028
|
+
'test-bucket'
|
|
1029
|
+
);
|
|
1030
|
+
console.log('Match rate:', comparison.summary.matchRate + '%');
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
**Note**: For `S3DataSource`, you typically don't need these testers since it only uses presigned URLs. Use these if you're testing `S3Service` with dual mode support.
|
|
1034
|
+
|
|
1035
|
+
### Basic Connection Test
|
|
1036
|
+
|
|
1037
|
+
```typescript
|
|
1038
|
+
// Simple connectivity test
|
|
1039
|
+
const isValid = await s3Source.validateConnection();
|
|
1040
|
+
if (!isValid) {
|
|
1041
|
+
throw new Error('S3 connection failed - check credentials and region');
|
|
1042
|
+
}
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
## Troubleshooting
|
|
1048
|
+
|
|
1049
|
+
### Common Issues and Solutions
|
|
1050
|
+
|
|
1051
|
+
#### "S3DataSource requires explicit bucket configuration"
|
|
1052
|
+
|
|
1053
|
+
```typescript
|
|
1054
|
+
// ❌ Wrong - missing bucket
|
|
1055
|
+
const dataSource = new S3DataSource({
|
|
1056
|
+
type: 'S3_CSV',
|
|
1057
|
+
connectionId: 's3-test',
|
|
1058
|
+
name: 'S3 Test',
|
|
1059
|
+
s3Config: {
|
|
1060
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1061
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1062
|
+
region: 'us-east-1'
|
|
1063
|
+
// Missing: bucket
|
|
1064
|
+
}
|
|
1065
|
+
}, logger);
|
|
1066
|
+
|
|
1067
|
+
// ✅ Correct - includes bucket
|
|
1068
|
+
const dataSource = new S3DataSource({
|
|
1069
|
+
type: 'S3_CSV',
|
|
1070
|
+
connectionId: 's3-test',
|
|
1071
|
+
name: 'S3 Test',
|
|
1072
|
+
s3Config: {
|
|
1073
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1074
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1075
|
+
region: 'us-east-1',
|
|
1076
|
+
bucket: 'my-bucket' // Required
|
|
1077
|
+
}
|
|
1078
|
+
}, logger);
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
#### Region Mismatch Errors
|
|
1082
|
+
|
|
1083
|
+
```typescript
|
|
1084
|
+
// Ensure your client region matches the bucket region
|
|
1085
|
+
const dataSource = new S3DataSource({
|
|
1086
|
+
type: 'S3_CSV',
|
|
1087
|
+
connectionId: 's3-test',
|
|
1088
|
+
name: 'S3 Test',
|
|
1089
|
+
s3Config: {
|
|
1090
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1091
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1092
|
+
region: 'us-west-2', // Must match bucket region
|
|
1093
|
+
bucket: 'my-bucket'
|
|
1094
|
+
}
|
|
1095
|
+
}, logger);
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
#### Need Multipart Upload Support
|
|
1099
|
+
|
|
1100
|
+
```typescript
|
|
1101
|
+
// S3DataSource does not support multipart upload
|
|
1102
|
+
// For files > 100MB requiring multipart, use S3Service instead:
|
|
1103
|
+
import { S3Service } from '@fluentcommerce/fc-connect-sdk';
|
|
1104
|
+
|
|
1105
|
+
const s3 = new S3Service({
|
|
1106
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
1107
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
1108
|
+
region: 'us-east-1'
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// Now multipart works
|
|
1112
|
+
if (s3.hasAdvancedFeatures()) {
|
|
1113
|
+
await s3.uploadLargeFile('bucket', 'large-file.bin', buffer, {
|
|
1114
|
+
partSize: 10 * 1024 * 1024
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
#### Content Type Detection
|
|
1120
|
+
|
|
1121
|
+
The S3DataSource automatically detects content types based on file extensions:
|
|
1122
|
+
|
|
1123
|
+
```typescript
|
|
1124
|
+
// Automatic content type detection
|
|
1125
|
+
const contentTypes = {
|
|
1126
|
+
'.csv': 'text/csv',
|
|
1127
|
+
'.json': 'application/json',
|
|
1128
|
+
'.xml': 'application/xml',
|
|
1129
|
+
'.txt': 'text/plain',
|
|
1130
|
+
'.parquet': 'application/octet-stream',
|
|
1131
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
1132
|
+
'.xls': 'application/vnd.ms-excel',
|
|
1133
|
+
'.pdf': 'application/pdf',
|
|
1134
|
+
'.zip': 'application/zip',
|
|
1135
|
+
'.gz': 'application/gzip'
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
// Files are automatically assigned correct content type
|
|
1139
|
+
await s3DataSource.uploadFile('data.csv', csvContent); // text/csv
|
|
1140
|
+
await s3DataSource.uploadFile('config.json', jsonContent); // application/json
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
### When to Use S3DataSource vs S3Service
|
|
1144
|
+
|
|
1145
|
+
| Use Case | Recommended Class | Reason |
|
|
1146
|
+
|----------|------------------|---------|
|
|
1147
|
+
| Standard file operations (read, write, delete) | `S3DataSource` | Simpler API, universal compatibility |
|
|
1148
|
+
| Node.js, Deno, or Versori connectors | `S3DataSource` | Works everywhere with presigned URLs |
|
|
1149
|
+
| Files < 100MB | `S3DataSource` | Efficient for standard file sizes |
|
|
1150
|
+
| Need metadata and content type support | `S3DataSource` | Full support via presigned URL headers |
|
|
1151
|
+
| Files > 100MB requiring multipart upload | `S3Service` | Has `uploadLargeFile()` method |
|
|
1152
|
+
| Need S3 Select (query CSV/JSON) | `S3Service` | Has `selectObjectContent()` method |
|
|
1153
|
+
| Need advanced AWS SDK features | `S3Service` | Direct SDK access |
|
|
1154
|
+
| Testing both implementations | `S3Service` | Has dual mode support |
|
|
1155
|
+
|
|
1156
|
+
### Architecture Comparison
|
|
1157
|
+
|
|
1158
|
+
**S3DataSource** (Recommended for most use cases):
|
|
1159
|
+
- ✅ Universal: Works in Node.js, Deno, Versori
|
|
1160
|
+
- ✅ Simple: Presigned URLs only, no mode switching
|
|
1161
|
+
- ✅ Lightweight: No AWS SDK bundled in application
|
|
1162
|
+
- ✅ Consistent: Same behavior everywhere
|
|
1163
|
+
- ❌ No multipart upload
|
|
1164
|
+
- ❌ No S3 Select
|
|
1165
|
+
|
|
1166
|
+
**S3Service** (For advanced features):
|
|
1167
|
+
- ✅ Multipart upload for large files
|
|
1168
|
+
- ✅ S3 Select query support
|
|
1169
|
+
- ✅ Direct AWS SDK access
|
|
1170
|
+
- ✅ Dual mode (SDK or presigned)
|
|
1171
|
+
- ❌ More complex API
|
|
1172
|
+
- ❌ Larger bundle size (includes AWS SDK)
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
## Best Practices
|
|
1177
|
+
|
|
1178
|
+
### 1. Choose the Right Class
|
|
1179
|
+
|
|
1180
|
+
- Use `S3DataSource` for standard file operations (most common)
|
|
1181
|
+
- Use `S3Service` only when you need multipart upload or S3 Select
|
|
1182
|
+
|
|
1183
|
+
### 2. Handle Large Files Properly
|
|
1184
|
+
|
|
1185
|
+
- `S3DataSource` works well for files up to 50MB
|
|
1186
|
+
- For files > 50MB, process in chunks to avoid memory issues
|
|
1187
|
+
- For files > 100MB requiring multipart, switch to `S3Service`
|
|
1188
|
+
|
|
1189
|
+
### 3. Implement Proper Error Handling
|
|
1190
|
+
|
|
1191
|
+
- Always catch and handle `DataSourceError`
|
|
1192
|
+
- Check `error.context.statusCode` for specific S3 errors (404, 403, etc.)
|
|
1193
|
+
- Built-in retry logic handles transient failures automatically
|
|
1194
|
+
- Log errors with context for debugging
|
|
1195
|
+
|
|
1196
|
+
### 4. Optimize List Operations
|
|
1197
|
+
|
|
1198
|
+
- Use `prefix` parameter to limit results to specific paths
|
|
1199
|
+
- Use `delimiter` for folder-like organization
|
|
1200
|
+
- Set appropriate `maxKeys` to control page size
|
|
1201
|
+
- Implement pagination with `continuationToken` for large buckets
|
|
1202
|
+
|
|
1203
|
+
### 5. Security Best Practices
|
|
1204
|
+
|
|
1205
|
+
- Never hardcode credentials in source code
|
|
1206
|
+
- Use environment variables for AWS credentials
|
|
1207
|
+
- Implement least-privilege IAM policies
|
|
1208
|
+
- Use separate credentials for source and target operations
|
|
1209
|
+
- Leverage session tokens for temporary credentials
|
|
1210
|
+
|
|
1211
|
+
### 6. Performance Optimization
|
|
1212
|
+
|
|
1213
|
+
- Implement parallel uploads/downloads with concurrency control (5-10 concurrent)
|
|
1214
|
+
- Use `encoding: 'binary'` for non-text files
|
|
1215
|
+
- Process files in chunks for very large datasets
|
|
1216
|
+
- Batch operations to reduce HTTP overhead
|
|
1217
|
+
|
|
1218
|
+
### 7. Data Organization
|
|
1219
|
+
|
|
1220
|
+
- Use prefixes to organize files (e.g., `inventory/2024/01/15/file.csv`)
|
|
1221
|
+
- Set appropriate content types - `S3DataSource` auto-detects from extensions
|
|
1222
|
+
- Add metadata to track processing status and timestamps
|
|
1223
|
+
- Clean up temporary files after processing
|
|
1224
|
+
|
|
1225
|
+
### 8. Platform Integration
|
|
1226
|
+
|
|
1227
|
+
- `S3DataSource` works identically in Node.js, Deno, and Versori
|
|
1228
|
+
- Optionally pass custom `httpClient` to constructor for connection pooling
|
|
1229
|
+
- Use structured logging to track operations across platforms
|
|
1230
|
+
|
|
1231
|
+
---
|
|
1232
|
+
|
|
1233
|
+
## Limitations
|
|
1234
|
+
|
|
1235
|
+
### S3DataSource Limitations
|
|
1236
|
+
|
|
1237
|
+
**What S3DataSource Does NOT Support**:
|
|
1238
|
+
- ❌ **Multipart Upload**: Cannot upload files > 5GB (S3 limit for single PUT)
|
|
1239
|
+
- ❌ **S3 Select**: Cannot query CSV/JSON files without downloading
|
|
1240
|
+
- ❌ **IAM Role Authentication**: Requires explicit credentials for presigned URLs
|
|
1241
|
+
- ❌ **Advanced AWS SDK Features**: No direct SDK access
|
|
1242
|
+
|
|
1243
|
+
**Design Trade-offs**:
|
|
1244
|
+
- **Presigned URL Expiry**: URLs expire after 1 hour (default) - sufficient for most operations
|
|
1245
|
+
- **XML Parsing**: List operations parse S3 XML responses (minimal overhead)
|
|
1246
|
+
- **Memory Usage**: Loads entire files into memory - process in chunks for files > 50MB
|
|
1247
|
+
- **URL Length Limits**: Presigned URLs limited to ~8KB (rarely an issue)
|
|
1248
|
+
|
|
1249
|
+
**When These Limitations Matter**:
|
|
1250
|
+
- Files > 100MB → Use `S3Service` with multipart upload
|
|
1251
|
+
- Need to query CSV/Parquet → Use `S3Service` with S3 Select
|
|
1252
|
+
- Running on AWS EC2/Lambda with IAM roles → Use `S3Service` to leverage IAM roles
|
|
1253
|
+
|
|
1254
|
+
### General Limitations
|
|
1255
|
+
|
|
1256
|
+
- All operations load files into memory (Buffer or string)
|
|
1257
|
+
- For very large files (> 100MB), implement chunked processing
|
|
1258
|
+
- Presigned URLs generated at request time (small latency overhead)
|
|
1259
|
+
- Built-in retry logic handles transient failures (no manual retry needed)
|
|
1260
|
+
|
|
1261
|
+
---
|
|
1262
|
+
|
|
1263
|
+
## API Reference
|
|
1264
|
+
|
|
1265
|
+
### S3DataSource Constructor
|
|
1266
|
+
|
|
1267
|
+
```typescript
|
|
1268
|
+
new S3DataSource(config: S3DataSourceConfig, logger?: StructuredLogger, httpClient?: typeof fetch)
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
**Parameters**:
|
|
1272
|
+
|
|
1273
|
+
- `config`: S3 data source configuration
|
|
1274
|
+
- `logger`: Optional logging service
|
|
1275
|
+
- `httpClient`: Optional custom fetch implementation (defaults to global `fetch`)
|
|
1276
|
+
|
|
1277
|
+
### Methods
|
|
1278
|
+
|
|
1279
|
+
| Method | Parameters | Returns | Description |
|
|
1280
|
+
| ----------------------- | ---------------------------------- | --------------------------- | -------------------------------- |
|
|
1281
|
+
| `listFiles()` | `options?` | `Promise<FileMetadata[]>` | List files in bucket |
|
|
1282
|
+
| `downloadFile()` | `path, options?` | `Promise<string \| Buffer>` | Read file content |
|
|
1283
|
+
| `uploadFile()` | `path, content, options?` | `Promise<void>` | Write file to S3 |
|
|
1284
|
+
| `deleteFile()` | `path` | `Promise<void>` | Delete file from S3 |
|
|
1285
|
+
| `copyFile()` | `sourcePath, targetPath, options?` | `Promise<void>` | Copy file within/between buckets |
|
|
1286
|
+
| `moveFile()` | `sourcePath, targetPath` | `Promise<void>` | Move file (copy + delete) |
|
|
1287
|
+
| `validateConnection()` | - | `Promise<boolean>` | Test S3 connectivity |
|
|
1288
|
+
| `writeParquetContent()` | `records, options?` | `Promise<Buffer>` | Generate Parquet buffer |
|
|
1289
|
+
|
|
1290
|
+
---
|
|
1291
|
+
|
|
1292
|
+
## Next Steps
|
|
1293
|
+
|
|
1294
|
+
**Master SFTP operations** → [Module 3: SFTP Operations](./data-sources-03-sftp-operations.md)
|
|
1295
|
+
|
|
1296
|
+
**Learn file processing patterns** → [Module 4: File Processing Patterns](./data-sources-04-file-patterns.md)
|
|
1297
|
+
|
|
1298
|
+
**Explore advanced topics** → [Module 5: Advanced Topics](./data-sources-05-advanced-topics.md)
|
|
1299
|
+
|
|
1300
|
+
---
|
|
1301
|
+
|
|
1302
|
+
[← Back to Foundations](./data-sources-01-foundations.md) | [Next: SFTP Operations →](./data-sources-03-sftp-operations.md) | [↑ Back to Guide](../data-sources-readme.md)
|