@fluentcommerce/fc-connect-sdk 0.1.54 → 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 +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md
CHANGED
|
@@ -1,963 +1,963 @@
|
|
|
1
|
-
# Module 4: SFTP Operations
|
|
2
|
-
|
|
3
|
-
**Level:** Intermediate
|
|
4
|
-
**Estimated Time:** 25 minutes
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
Complete guide to SFTP-specific file operations, including connection management, directory operations, and SFTP-specific features.
|
|
9
|
-
|
|
10
|
-
## Learning Objectives
|
|
11
|
-
|
|
12
|
-
By the end of this module, you will:
|
|
13
|
-
|
|
14
|
-
- ✅ Master all SFTP file operations
|
|
15
|
-
- ✅ Understand connection pooling and lifecycle
|
|
16
|
-
- ✅ Learn directory creation and path handling
|
|
17
|
-
- ✅ Implement secure authentication (password and key-based)
|
|
18
|
-
- ✅ Handle SFTP-specific errors
|
|
19
|
-
|
|
20
|
-
## SFTP Concepts
|
|
21
|
-
|
|
22
|
-
### Connection Lifecycle
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
import {
|
|
26
|
-
SftpDataSource,
|
|
27
|
-
createConsoleLogger,
|
|
28
|
-
toStructuredLogger
|
|
29
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
30
|
-
|
|
31
|
-
const logger = createConsoleLogger();
|
|
32
|
-
|
|
33
|
-
// Create data source (connection established on first operation)
|
|
34
|
-
const sftp = new SftpDataSource(
|
|
35
|
-
{
|
|
36
|
-
settings: {
|
|
37
|
-
remotePath: '/data/incoming',
|
|
38
|
-
filePattern: '*.csv',
|
|
39
|
-
host: process.env.SFTP_HOST!,
|
|
40
|
-
port: 22,
|
|
41
|
-
username: process.env.SFTP_USERNAME!,
|
|
42
|
-
password: process.env.SFTP_PASSWORD!,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
logger
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
// Use the connection
|
|
50
|
-
const files = await sftp.listFiles();
|
|
51
|
-
} finally {
|
|
52
|
-
// CRITICAL: Always dispose to close connection
|
|
53
|
-
await sftp.dispose();
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Path Conventions
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// ✅ CORRECT: Absolute paths with leading slash
|
|
61
|
-
const file = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
62
|
-
|
|
63
|
-
// ❌ WRONG: Relative paths without leading slash
|
|
64
|
-
const file = await sftp.downloadFile('data/incoming/orders.csv'); // May fail
|
|
65
|
-
|
|
66
|
-
// remotePath in config provides base path
|
|
67
|
-
settings: {
|
|
68
|
-
remotePath: '/data/incoming', // Base path
|
|
69
|
-
filePattern: '*.csv'
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## All SFTP Operations
|
|
74
|
-
|
|
75
|
-
### List Files
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
const sftp = new SftpDataSource(
|
|
79
|
-
{
|
|
80
|
-
settings: {
|
|
81
|
-
remotePath: '/data/incoming',
|
|
82
|
-
filePattern: '*.csv', // Pattern specified in config
|
|
83
|
-
host: process.env.SFTP_HOST!,
|
|
84
|
-
username: process.env.SFTP_USERNAME!,
|
|
85
|
-
password: process.env.SFTP_PASSWORD!,
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
logger
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
// List files matching pattern from remotePath
|
|
93
|
-
const files = await sftp.listFiles();
|
|
94
|
-
|
|
95
|
-
files.forEach(file => {
|
|
96
|
-
console.log(`Name: ${file.name}`);
|
|
97
|
-
console.log(`Path: ${file.path}`);
|
|
98
|
-
console.log(`Size: ${file.size} bytes`);
|
|
99
|
-
});
|
|
100
|
-
} finally {
|
|
101
|
-
await sftp.dispose();
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Download Files
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
try {
|
|
109
|
-
const content = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
110
|
-
console.log(`Downloaded ${content.length} bytes`);
|
|
111
|
-
|
|
112
|
-
// Process content
|
|
113
|
-
const lines = content.split('\n');
|
|
114
|
-
console.log(`File has ${lines.length} lines`);
|
|
115
|
-
} catch (error: any) {
|
|
116
|
-
if (error.message.includes('No such file')) {
|
|
117
|
-
console.error('File does not exist');
|
|
118
|
-
} else {
|
|
119
|
-
console.error('Download failed:', error);
|
|
120
|
-
}
|
|
121
|
-
} finally {
|
|
122
|
-
await sftp.dispose();
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### Upload Files
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
try {
|
|
130
|
-
const data = JSON.stringify({ status: 'processed' });
|
|
131
|
-
|
|
132
|
-
await sftp.uploadFile('/data/outgoing/results.json', data);
|
|
133
|
-
console.log('Upload successful');
|
|
134
|
-
|
|
135
|
-
// Upload buffer
|
|
136
|
-
const buffer = Buffer.from('Hello, SFTP!');
|
|
137
|
-
await sftp.uploadFile('/data/messages/greeting.txt', buffer);
|
|
138
|
-
} finally {
|
|
139
|
-
await sftp.dispose();
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
**Options parameter** (optional):
|
|
144
|
-
```typescript
|
|
145
|
-
await sftp.uploadFile('/data/outgoing/results.json', data, {
|
|
146
|
-
overwrite: true, // Overwrite existing file (default: false)
|
|
147
|
-
createDirectories: true, // Create parent directories (default: false)
|
|
148
|
-
permissions: '0644', // File permissions (default: server default)
|
|
149
|
-
encoding: 'utf8' // Content encoding (default: 'utf8')
|
|
150
|
-
});
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Move Files
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
try {
|
|
157
|
-
// Move/rename file (default: overwrite = false)
|
|
158
|
-
await sftp.moveFile('/data/incoming/orders.csv', '/data/processed/orders.csv');
|
|
159
|
-
|
|
160
|
-
// Move with overwrite flag
|
|
161
|
-
await sftp.moveFile(
|
|
162
|
-
'/data/incoming/inventory.csv',
|
|
163
|
-
'/archive/2025/01/15/inventory.csv',
|
|
164
|
-
true // Overwrite if destination exists
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
console.log('Files moved successfully');
|
|
168
|
-
} finally {
|
|
169
|
-
await sftp.dispose();
|
|
170
|
-
}
|
|
171
|
-
````
|
|
172
|
-
|
|
173
|
-
### Copy Files
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
try {
|
|
177
|
-
// Copy file (default: overwrite = false)
|
|
178
|
-
await sftp.copyFile('/data/important/config.json', '/backup/config.json');
|
|
179
|
-
|
|
180
|
-
// Copy with overwrite
|
|
181
|
-
await sftp.copyFile('/data/source.json', '/backup/target.json', true);
|
|
182
|
-
|
|
183
|
-
console.log('File copied successfully');
|
|
184
|
-
} finally {
|
|
185
|
-
await sftp.dispose();
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Delete Files
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
try {
|
|
193
|
-
// Delete file
|
|
194
|
-
await sftp.deleteFile('/data/temp/old-file.csv');
|
|
195
|
-
|
|
196
|
-
// Delete with existence check
|
|
197
|
-
if (await sftp.fileExists('/data/archive/old-data.csv')) {
|
|
198
|
-
await sftp.deleteFile('/data/archive/old-data.csv');
|
|
199
|
-
console.log('File deleted');
|
|
200
|
-
}
|
|
201
|
-
} finally {
|
|
202
|
-
await sftp.dispose();
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### Check File Existence
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
try {
|
|
210
|
-
const exists = await sftp.fileExists('/data/incoming/orders.csv');
|
|
211
|
-
console.log(`File exists: ${exists}`);
|
|
212
|
-
|
|
213
|
-
// Conditional processing
|
|
214
|
-
if (exists) {
|
|
215
|
-
const content = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
216
|
-
// Process file
|
|
217
|
-
}
|
|
218
|
-
} finally {
|
|
219
|
-
await sftp.dispose();
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
## Authentication Methods
|
|
224
|
-
|
|
225
|
-
SFTP supports multiple authentication methods. How you access credentials depends on your deployment context.
|
|
226
|
-
|
|
227
|
-
### Credential Access by Runtime
|
|
228
|
-
|
|
229
|
-
| Runtime | Method | Location |
|
|
230
|
-
|---------|--------|----------|
|
|
231
|
-
| **Versori** | `activation.connections` (recommended) | Connection UI |
|
|
232
|
-
| **Versori** | `credentials().get()` (alternative) | Connection UI |
|
|
233
|
-
| **Standalone** | Environment variables | `.env` file |
|
|
234
|
-
|
|
235
|
-
### Versori Platform: Connection-Based Auth (Recommended)
|
|
236
|
-
|
|
237
|
-
**Best practice for Versori platform** - credentials are already decoded and type-safe:
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
import { schedule, http } from '@versori/run';
|
|
241
|
-
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
242
|
-
|
|
243
|
-
export const sftpWorkflow = schedule("sftp-sync", "0 * * * *")
|
|
244
|
-
.then(http("process", { connection: "fluent_commerce" }, async (ctx) => {
|
|
245
|
-
const { activation, log } = ctx;
|
|
246
|
-
|
|
247
|
-
// Access ALL connections from activation
|
|
248
|
-
const allConnections = activation.connections || [];
|
|
249
|
-
|
|
250
|
-
// Find SFTP connection by name
|
|
251
|
-
const sftpConnection = allConnections.find(c => c.name === 'versori_ftp_server');
|
|
252
|
-
|
|
253
|
-
if (!sftpConnection) {
|
|
254
|
-
throw new Error(
|
|
255
|
-
`SFTP connection not found. Available: ${allConnections.map(c => c.name).join(', ')}`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Extract credentials (already decoded - no Buffer needed!)
|
|
260
|
-
const sftpCred = sftpConnection.credentials[0]?.credential;
|
|
261
|
-
|
|
262
|
-
if (!sftpCred?.data?.basicAuth) {
|
|
263
|
-
throw new Error('SFTP connection missing Basic Authentication configuration');
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const sftpUsername = sftpCred.data.basicAuth.username;
|
|
267
|
-
const sftpPassword = sftpCred.data.basicAuth.password;
|
|
268
|
-
const sftpHost = sftpConnection.baseUrl || 'sftp.example.com';
|
|
269
|
-
|
|
270
|
-
// Create SFTP data source
|
|
271
|
-
const sftp = new SftpDataSource(
|
|
272
|
-
{
|
|
273
|
-
settings: {
|
|
274
|
-
host: sftpHost,
|
|
275
|
-
port: 22,
|
|
276
|
-
username: sftpUsername,
|
|
277
|
-
password: sftpPassword,
|
|
278
|
-
remotePath: '/data',
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
log
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// Use SFTP
|
|
285
|
-
const files = await sftp.listFiles();
|
|
286
|
-
return { success: true, fileCount: files.length };
|
|
287
|
-
}));
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
**Why this is recommended:**
|
|
291
|
-
- ✅ Credentials already decoded (no Buffer.from needed)
|
|
292
|
-
- ✅ Works in any task type (fn, http, webhook)
|
|
293
|
-
- ✅ Type-safe access
|
|
294
|
-
- ✅ Better error messages
|
|
295
|
-
- ✅ Supports multiple connections
|
|
296
|
-
|
|
297
|
-
### Versori Platform: credentials().get() (Alternative)
|
|
298
|
-
|
|
299
|
-
**Use when working in fn() tasks** where connectionVariables aren't available:
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
import { schedule, fn } from '@versori/run';
|
|
303
|
-
import { Buffer } from 'node:buffer'; // Required for Deno runtime!
|
|
304
|
-
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
305
|
-
|
|
306
|
-
export const sftpWorkflow = schedule("sftp-sync", "0 * * * *")
|
|
307
|
-
.then(fn("get-files", async (ctx) => {
|
|
308
|
-
const { log } = ctx;
|
|
309
|
-
|
|
310
|
-
// Retrieve credentials from connection configuration
|
|
311
|
-
const sftpCred = await ctx.credentials().getAccessToken('SFTP');
|
|
312
|
-
|
|
313
|
-
if (!sftpCred?.accessToken) {
|
|
314
|
-
throw new Error('No SFTP credentials found in connection configuration');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Decode base64 accessToken to get "username:password"
|
|
318
|
-
const rawBasicAuth = Buffer.from(sftpCred.accessToken, 'base64').toString('utf-8');
|
|
319
|
-
|
|
320
|
-
// Split on ':' to extract username and password
|
|
321
|
-
const [sftpUsername, sftpPassword] = rawBasicAuth.split(':');
|
|
322
|
-
|
|
323
|
-
if (!sftpUsername || !sftpPassword) {
|
|
324
|
-
throw new Error('Invalid SFTP credential format - expected username:password');
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Create SFTP data source
|
|
328
|
-
const sftp = new SftpDataSource(
|
|
329
|
-
{
|
|
330
|
-
settings: {
|
|
331
|
-
host: 'sftp.example.com',
|
|
332
|
-
port: 22,
|
|
333
|
-
username: sftpUsername,
|
|
334
|
-
password: sftpPassword,
|
|
335
|
-
remotePath: '/data',
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
log
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
const files = await sftp.listFiles();
|
|
342
|
-
return { success: true, fileCount: files.length };
|
|
343
|
-
}));
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
**Limitations:**
|
|
347
|
-
- ⚠️ Requires manual base64 decoding
|
|
348
|
-
- ⚠️ Only works in fn() tasks
|
|
349
|
-
- ⚠️ More error-prone (string splitting)
|
|
350
|
-
|
|
351
|
-
### Standalone: Password Authentication
|
|
352
|
-
|
|
353
|
-
**For Node.js/Deno standalone scripts:**
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
import { SftpDataSource, createConsoleLogger } from '@fluentcommerce/fc-connect-sdk';
|
|
357
|
-
|
|
358
|
-
const logger = createConsoleLogger();
|
|
359
|
-
|
|
360
|
-
const sftp = new SftpDataSource(
|
|
361
|
-
{
|
|
362
|
-
settings: {
|
|
363
|
-
host: process.env.SFTP_HOST!,
|
|
364
|
-
port: 22,
|
|
365
|
-
username: process.env.SFTP_USERNAME!,
|
|
366
|
-
password: process.env.SFTP_PASSWORD!,
|
|
367
|
-
remotePath: '/data',
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
logger
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
const files = await sftp.listFiles();
|
|
375
|
-
console.log(`Found ${files.length} files`);
|
|
376
|
-
} finally {
|
|
377
|
-
await sftp.dispose();
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**Environment variables (.env):**
|
|
382
|
-
```bash
|
|
383
|
-
SFTP_HOST=sftp.example.com
|
|
384
|
-
SFTP_USERNAME=myuser
|
|
385
|
-
SFTP_PASSWORD=mypassword
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### Standalone: Private Key Authentication
|
|
389
|
-
|
|
390
|
-
**For key-based authentication in standalone scripts:**
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
import * as fs from 'fs';
|
|
394
|
-
import { SftpDataSource, createConsoleLogger } from '@fluentcommerce/fc-connect-sdk';
|
|
395
|
-
|
|
396
|
-
const logger = createConsoleLogger();
|
|
397
|
-
const privateKey = fs.readFileSync('/path/to/private-key.pem', 'utf-8');
|
|
398
|
-
|
|
399
|
-
const sftp = new SftpDataSource(
|
|
400
|
-
{
|
|
401
|
-
settings: {
|
|
402
|
-
host: 'sftp.example.com',
|
|
403
|
-
port: 22,
|
|
404
|
-
username: 'myuser',
|
|
405
|
-
privateKey: privateKey,
|
|
406
|
-
passphrase: process.env.KEY_PASSPHRASE, // Optional if key is encrypted
|
|
407
|
-
remotePath: '/data',
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
logger
|
|
411
|
-
);
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### Security Best Practices
|
|
415
|
-
|
|
416
|
-
**For all authentication methods:**
|
|
417
|
-
|
|
418
|
-
```typescript
|
|
419
|
-
// ✅ GOOD: Never log credentials
|
|
420
|
-
log.info('SFTP configuration', {
|
|
421
|
-
host: sftpHost,
|
|
422
|
-
username: sftpUsername,
|
|
423
|
-
hasPassword: !!sftpPassword,
|
|
424
|
-
passwordLength: sftpPassword.length // Log length, not value
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// ❌ BAD: Logging credentials
|
|
428
|
-
log.info('SFTP config', { username, password }); // Don't do this!
|
|
429
|
-
|
|
430
|
-
// ✅ GOOD: Validate credentials before use
|
|
431
|
-
if (!sftpUsername || !sftpPassword) {
|
|
432
|
-
throw new Error('Missing SFTP credentials');
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (sftpPassword.length < 8) {
|
|
436
|
-
log.warn('SFTP password seems short', { length: sftpPassword.length });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// ✅ GOOD: Centralized credential management
|
|
440
|
-
// Store credentials in Versori Connections (platform) or .env files (standalone)
|
|
441
|
-
// Never hardcode credentials in code
|
|
442
|
-
|
|
443
|
-
// ❌ BAD: Hardcoded credentials
|
|
444
|
-
const username = 'hardcoded-user'; // Don't do this!
|
|
445
|
-
const password = 'hardcoded-pass'; // Don't do this!
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
**See also:** [SFTP Credential Access & Security Guide](../../../02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md) for complete credential retrieval patterns.
|
|
449
|
-
|
|
450
|
-
## SFTP-Specific Patterns
|
|
451
|
-
|
|
452
|
-
### Multi-Folder Processing
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
const folders = ['orders', 'inventory', 'products'];
|
|
456
|
-
|
|
457
|
-
for (const folder of folders) {
|
|
458
|
-
const sftp = new SftpDataSource(
|
|
459
|
-
{
|
|
460
|
-
settings: {
|
|
461
|
-
remotePath: `/data/${folder}`,
|
|
462
|
-
filePattern: '*.csv',
|
|
463
|
-
host: process.env.SFTP_HOST!,
|
|
464
|
-
username: process.env.SFTP_USERNAME!,
|
|
465
|
-
password: process.env.SFTP_PASSWORD!,
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
logger
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
const files = await sftp.listFiles();
|
|
473
|
-
console.log(`Processing ${files.length} files from ${folder}`);
|
|
474
|
-
|
|
475
|
-
for (const file of files) {
|
|
476
|
-
const content = await sftp.downloadFile(file.path);
|
|
477
|
-
await processByType(folder, content);
|
|
478
|
-
|
|
479
|
-
const archivePath = `/archive/${folder}/${file.name}`;
|
|
480
|
-
await sftp.moveFile(file.path, archivePath);
|
|
481
|
-
}
|
|
482
|
-
} finally {
|
|
483
|
-
await sftp.dispose();
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Date-Based Archive
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
function getDateBasedPath(filename: string, basePath: string): string {
|
|
492
|
-
const now = new Date();
|
|
493
|
-
const year = now.getFullYear();
|
|
494
|
-
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
495
|
-
const day = String(now.getDate()).padStart(2, '0');
|
|
496
|
-
|
|
497
|
-
return `${basePath}/${year}/${month}/${day}/${filename}`;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
const files = await sftp.listFiles();
|
|
502
|
-
|
|
503
|
-
for (const file of files) {
|
|
504
|
-
const content = await sftp.downloadFile(file.path);
|
|
505
|
-
await processData(content);
|
|
506
|
-
|
|
507
|
-
const archivePath = getDateBasedPath(file.name, '/archive');
|
|
508
|
-
await sftp.moveFile(file.path, archivePath);
|
|
509
|
-
// Result: /archive/2025/01/15/orders.csv
|
|
510
|
-
}
|
|
511
|
-
} finally {
|
|
512
|
-
await sftp.dispose();
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### Batch Processing with Connection Reuse
|
|
517
|
-
|
|
518
|
-
```typescript
|
|
519
|
-
const sftp = new SftpDataSource(
|
|
520
|
-
{
|
|
521
|
-
settings: {
|
|
522
|
-
remotePath: '/data/incoming',
|
|
523
|
-
filePattern: '*.csv',
|
|
524
|
-
host: process.env.SFTP_HOST!,
|
|
525
|
-
username: process.env.SFTP_USERNAME!,
|
|
526
|
-
password: process.env.SFTP_PASSWORD!,
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
logger
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
try {
|
|
533
|
-
const files = await sftp.listFiles();
|
|
534
|
-
|
|
535
|
-
// Process all files using same connection
|
|
536
|
-
for (const file of files) {
|
|
537
|
-
try {
|
|
538
|
-
const content = await sftp.downloadFile(file.path);
|
|
539
|
-
const result = await processData(content);
|
|
540
|
-
|
|
541
|
-
await sftp.uploadFile(`/data/outgoing/${file.name.replace('.csv', '.json', JSON.stringify(result))}`
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
const today = new Date().toISOString().split('T')[0];
|
|
545
|
-
await sftp.moveFile(file.path, `/archive/${today}/${file.name}`);
|
|
546
|
-
|
|
547
|
-
console.log(`Processed ${file.name}`);
|
|
548
|
-
} catch (error) {
|
|
549
|
-
console.error(`Failed to process ${file.name}:`, error);
|
|
550
|
-
// Continue with next file
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
} finally {
|
|
554
|
-
// Connection closed once after processing all files
|
|
555
|
-
await sftp.dispose();
|
|
556
|
-
}
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
## Error Handling
|
|
560
|
-
|
|
561
|
-
### Common SFTP Errors
|
|
562
|
-
|
|
563
|
-
```typescript
|
|
564
|
-
try {
|
|
565
|
-
await sftp.downloadFile('/data/missing-file.csv');
|
|
566
|
-
} catch (error: any) {
|
|
567
|
-
const message = error.message.toLowerCase();
|
|
568
|
-
|
|
569
|
-
if (message.includes('no such file')) {
|
|
570
|
-
console.error('File does not exist');
|
|
571
|
-
} else if (message.includes('permission denied')) {
|
|
572
|
-
console.error('Permission denied - check SFTP permissions');
|
|
573
|
-
} else if (message.includes('connection')) {
|
|
574
|
-
console.error('Connection error - check host/port/credentials');
|
|
575
|
-
} else if (message.includes('authentication')) {
|
|
576
|
-
console.error('Authentication failed - check username/password');
|
|
577
|
-
} else if (message.includes('timeout')) {
|
|
578
|
-
console.error('Operation timed out - check network connectivity');
|
|
579
|
-
} else {
|
|
580
|
-
console.error('SFTP error:', error.message);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
### Connection Retry Logic
|
|
586
|
-
|
|
587
|
-
```typescript
|
|
588
|
-
async function createSftpWithRetry(
|
|
589
|
-
config: any,
|
|
590
|
-
logger: Logger,
|
|
591
|
-
maxRetries = 3
|
|
592
|
-
): Promise<SftpDataSource> {
|
|
593
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
594
|
-
try {
|
|
595
|
-
const sftp = new SftpDataSource(config, logger);
|
|
596
|
-
|
|
597
|
-
// Test connection
|
|
598
|
-
await sftp.fileExists('/'); // Simple operation to verify connection
|
|
599
|
-
|
|
600
|
-
return sftp;
|
|
601
|
-
} catch (error: any) {
|
|
602
|
-
if (attempt === maxRetries) throw error;
|
|
603
|
-
|
|
604
|
-
const delay = Math.pow(2, attempt) * 1000;
|
|
605
|
-
console.log(`Connection retry ${attempt}/${maxRetries} after ${delay}ms`);
|
|
606
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
throw new Error('Should not reach here');
|
|
610
|
-
}
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
## Performance Tips
|
|
614
|
-
|
|
615
|
-
### Connection Pooling
|
|
616
|
-
|
|
617
|
-
The SDK handles connection pooling automatically, but you should:
|
|
618
|
-
|
|
619
|
-
```typescript
|
|
620
|
-
// ✅ GOOD: Reuse connection for multiple operations
|
|
621
|
-
const sftp = new SftpDataSource(config, logger);
|
|
622
|
-
try {
|
|
623
|
-
await sftp.downloadFile('/file1.csv');
|
|
624
|
-
await sftp.downloadFile('/file2.csv');
|
|
625
|
-
await sftp.downloadFile('/file3.csv');
|
|
626
|
-
} finally {
|
|
627
|
-
await sftp.dispose(); // Close once
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// ❌ BAD: Create/dispose for each operation
|
|
631
|
-
for (const file of files) {
|
|
632
|
-
const sftp = new SftpDataSource(config, logger);
|
|
633
|
-
await sftp.downloadFile(file.path);
|
|
634
|
-
await sftp.dispose(); // Too many connections!
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Parallel Operations
|
|
639
|
-
|
|
640
|
-
```typescript
|
|
641
|
-
// Process files in parallel (with connection limit)
|
|
642
|
-
const sftp = new SftpDataSource(config, logger);
|
|
643
|
-
|
|
644
|
-
try {
|
|
645
|
-
const files = await sftp.listFiles();
|
|
646
|
-
|
|
647
|
-
// Limit concurrency to avoid overwhelming server
|
|
648
|
-
const concurrencyLimit = 5;
|
|
649
|
-
|
|
650
|
-
for (let i = 0; i < files.length; i += concurrencyLimit) {
|
|
651
|
-
const batch = files.slice(i, i + concurrencyLimit);
|
|
652
|
-
|
|
653
|
-
await Promise.all(
|
|
654
|
-
batch.map(async file => {
|
|
655
|
-
const content = await sftp.downloadFile(file.path);
|
|
656
|
-
return processData(content);
|
|
657
|
-
})
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
} finally {
|
|
661
|
-
await sftp.dispose();
|
|
662
|
-
}
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
## Directory Operations
|
|
666
|
-
|
|
667
|
-
### Create Directory
|
|
668
|
-
|
|
669
|
-
```typescript
|
|
670
|
-
try {
|
|
671
|
-
// Create directory (non-recursive)
|
|
672
|
-
await sftp.createDirectory('/data/incoming');
|
|
673
|
-
|
|
674
|
-
// Create directory recursively (creates parent directories if needed)
|
|
675
|
-
await sftp.createDirectory('/data/archive/2025/01/15', true);
|
|
676
|
-
|
|
677
|
-
// Create with specific permissions
|
|
678
|
-
await sftp.createDirectory('/data/public', true, '0755');
|
|
679
|
-
} finally {
|
|
680
|
-
await sftp.dispose();
|
|
681
|
-
}
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
### Check Directory Existence
|
|
685
|
-
|
|
686
|
-
```typescript
|
|
687
|
-
try {
|
|
688
|
-
const exists = await sftp.directoryExists('/data/incoming');
|
|
689
|
-
|
|
690
|
-
if (!exists) {
|
|
691
|
-
await sftp.createDirectory('/data/incoming', true);
|
|
692
|
-
console.log('Directory created');
|
|
693
|
-
} else {
|
|
694
|
-
console.log('Directory already exists');
|
|
695
|
-
}
|
|
696
|
-
} finally {
|
|
697
|
-
await sftp.dispose();
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### Directory Operations Pattern
|
|
702
|
-
|
|
703
|
-
```typescript
|
|
704
|
-
async function ensureDirectoryExists(sftp: SftpDataSource, path: string) {
|
|
705
|
-
if (!(await sftp.directoryExists(path))) {
|
|
706
|
-
await sftp.createDirectory(path, true); // Recursive
|
|
707
|
-
console.log(`Created directory: ${path}`);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
try {
|
|
712
|
-
// Ensure archive directory exists before moving files
|
|
713
|
-
await ensureDirectoryExists(sftp, '/archive/2025/01/15');
|
|
714
|
-
|
|
715
|
-
const files = await sftp.listFiles();
|
|
716
|
-
for (const file of files) {
|
|
717
|
-
const archivePath = `/archive/2025/01/15/${file.name}`;
|
|
718
|
-
await sftp.moveFile(file.path, archivePath);
|
|
719
|
-
}
|
|
720
|
-
} finally {
|
|
721
|
-
await sftp.dispose();
|
|
722
|
-
}
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
## Built-in Content Parsing
|
|
726
|
-
|
|
727
|
-
SFTP data source provides built-in parsing methods for CSV and JSON content, eliminating the need for separate parser services.
|
|
728
|
-
|
|
729
|
-
### Parse CSV Content
|
|
730
|
-
|
|
731
|
-
```typescript
|
|
732
|
-
try {
|
|
733
|
-
// Download file
|
|
734
|
-
const csvContent = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
735
|
-
|
|
736
|
-
// Parse CSV content directly (no need for CSVParserService)
|
|
737
|
-
const fileMetadata = {
|
|
738
|
-
path: '/data/incoming/orders.csv',
|
|
739
|
-
name: 'orders.csv',
|
|
740
|
-
size: csvContent.length,
|
|
741
|
-
source: 'SFTP'
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
const records = sftp.parseCsvContent(csvContent as string, fileMetadata);
|
|
745
|
-
|
|
746
|
-
console.log(`Parsed ${records.length} CSV records`);
|
|
747
|
-
records.forEach(record => {
|
|
748
|
-
console.log(`Order: ${record.orderId}, Total: ${record.total}`);
|
|
749
|
-
});
|
|
750
|
-
} finally {
|
|
751
|
-
await sftp.dispose();
|
|
752
|
-
}
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
### Parse JSON Content
|
|
756
|
-
|
|
757
|
-
```typescript
|
|
758
|
-
try {
|
|
759
|
-
// Download JSON file
|
|
760
|
-
const jsonContent = await sftp.downloadFile('/data/incoming/orders.json');
|
|
761
|
-
|
|
762
|
-
// Parse JSON content (supports both JSON and JSONL formats)
|
|
763
|
-
const fileMetadata = {
|
|
764
|
-
path: '/data/incoming/orders.json',
|
|
765
|
-
name: 'orders.json',
|
|
766
|
-
size: jsonContent.length,
|
|
767
|
-
source: 'SFTP'
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
// Auto-detect format (JSON array or JSONL)
|
|
771
|
-
const records = sftp.parseJsonContent(jsonContent as string, fileMetadata);
|
|
772
|
-
|
|
773
|
-
// Or specify format explicitly
|
|
774
|
-
const jsonRecords = sftp.parseJsonContent(jsonContent as string, fileMetadata, {
|
|
775
|
-
format: 'json', // or 'jsonl'
|
|
776
|
-
validate: true, // Throw on invalid JSON
|
|
777
|
-
maxDepth: 10 // Limit nesting depth
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
console.log(`Parsed ${records.length} JSON records`);
|
|
781
|
-
} finally {
|
|
782
|
-
await sftp.dispose();
|
|
783
|
-
}
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
### Write CSV Content
|
|
787
|
-
|
|
788
|
-
```typescript
|
|
789
|
-
try {
|
|
790
|
-
// Prepare data
|
|
791
|
-
const records = [
|
|
792
|
-
{ sku: 'SKU-001', quantity: 10, location: 'WAREHOUSE-A' },
|
|
793
|
-
{ sku: 'SKU-002', quantity: 25, location: 'WAREHOUSE-B' }
|
|
794
|
-
];
|
|
795
|
-
|
|
796
|
-
// Generate CSV content
|
|
797
|
-
const csvContent = sftp.writeCsvContent(records, {
|
|
798
|
-
headers: ['sku', 'quantity', 'location'],
|
|
799
|
-
delimiter: ',',
|
|
800
|
-
includeHeaders: true
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
// Upload CSV file
|
|
804
|
-
await sftp.uploadFile('/data/outgoing/inventory.csv', csvContent);
|
|
805
|
-
} finally {
|
|
806
|
-
await sftp.dispose();
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
### Write JSON Content
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
try {
|
|
814
|
-
// Prepare data
|
|
815
|
-
const orders = [
|
|
816
|
-
{ id: 'ORD-001', total: 99.99, status: 'pending' },
|
|
817
|
-
{ id: 'ORD-002', total: 149.99, status: 'shipped' }
|
|
818
|
-
];
|
|
819
|
-
|
|
820
|
-
// Generate JSON content (compact or pretty)
|
|
821
|
-
const jsonContent = sftp.writeJsonContent(orders, {
|
|
822
|
-
format: 'json', // or 'jsonl' for JSON Lines
|
|
823
|
-
pretty: true // Pretty-print JSON
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
// Upload JSON file
|
|
827
|
-
await sftp.uploadFile('/data/outgoing/orders.json', jsonContent);
|
|
828
|
-
} finally {
|
|
829
|
-
await sftp.dispose();
|
|
830
|
-
}
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
### Complete Parsing Workflow
|
|
834
|
-
|
|
835
|
-
```typescript
|
|
836
|
-
try {
|
|
837
|
-
// List CSV files
|
|
838
|
-
const csvFiles = await sftp.listFiles({ filePattern: '*.csv' });
|
|
839
|
-
|
|
840
|
-
for (const file of csvFiles) {
|
|
841
|
-
// Download and parse in one step
|
|
842
|
-
const content = await sftp.downloadFile(file.path);
|
|
843
|
-
const records = sftp.parseCsvContent(content as string, file);
|
|
844
|
-
|
|
845
|
-
// Transform data
|
|
846
|
-
const transformed = records.map(record => ({
|
|
847
|
-
ref: record.sku,
|
|
848
|
-
qty: parseInt(record.quantity),
|
|
849
|
-
locationRef: record.location
|
|
850
|
-
}));
|
|
851
|
-
|
|
852
|
-
// Write transformed data as JSON
|
|
853
|
-
const jsonContent = sftp.writeJsonContent(transformed, { pretty: true });
|
|
854
|
-
const outputPath = file.path.replace('.csv', '.json');
|
|
855
|
-
await sftp.uploadFile(outputPath, jsonContent);
|
|
856
|
-
|
|
857
|
-
// Archive original
|
|
858
|
-
await sftp.moveFile(file.path, `/archive/${file.name}`);
|
|
859
|
-
}
|
|
860
|
-
} finally {
|
|
861
|
-
await sftp.dispose();
|
|
862
|
-
}
|
|
863
|
-
```
|
|
864
|
-
|
|
865
|
-
## Parquet Support
|
|
866
|
-
|
|
867
|
-
SFTP data source supports writing Parquet files for efficient data storage and transfer.
|
|
868
|
-
|
|
869
|
-
### Write Parquet Content
|
|
870
|
-
|
|
871
|
-
```typescript
|
|
872
|
-
try {
|
|
873
|
-
// Prepare data records
|
|
874
|
-
const records = [
|
|
875
|
-
{ sku: 'SKU-001', quantity: 10, price: 99.99, available: true },
|
|
876
|
-
{ sku: 'SKU-002', quantity: 25, price: 149.99, available: true }
|
|
877
|
-
];
|
|
878
|
-
|
|
879
|
-
// Generate Parquet content as Buffer
|
|
880
|
-
const parquetBuffer = await sftp.writeParquetContent(records, {
|
|
881
|
-
compression: 'GZIP', // Options: 'UNCOMPRESSED', 'GZIP', 'SNAPPY', 'LZO', 'BROTLI'
|
|
882
|
-
rowGroupSize: 5000, // Number of rows per row group
|
|
883
|
-
schema: { // Optional: explicit schema (auto-detected if not provided)
|
|
884
|
-
sku: { type: 'UTF8' },
|
|
885
|
-
quantity: { type: 'INT64' },
|
|
886
|
-
price: { type: 'DOUBLE' },
|
|
887
|
-
available: { type: 'BOOLEAN' }
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// Upload Parquet file
|
|
892
|
-
await sftp.uploadFile('/data/outgoing/inventory.parquet', parquetBuffer);
|
|
893
|
-
|
|
894
|
-
console.log(`Generated Parquet file: ${parquetBuffer.length} bytes`);
|
|
895
|
-
} finally {
|
|
896
|
-
await sftp.dispose();
|
|
897
|
-
}
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
### Parquet with Compression
|
|
901
|
-
|
|
902
|
-
```typescript
|
|
903
|
-
try {
|
|
904
|
-
const records = /* ... large dataset ... */;
|
|
905
|
-
|
|
906
|
-
// Use SNAPPY compression for faster processing
|
|
907
|
-
const compressed = await sftp.writeParquetContent(records, {
|
|
908
|
-
compression: 'SNAPPY',
|
|
909
|
-
rowGroupSize: 10000
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
// Use GZIP for smaller file size
|
|
913
|
-
const gzipped = await sftp.writeParquetContent(records, {
|
|
914
|
-
compression: 'GZIP',
|
|
915
|
-
rowGroupSize: 10000
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
console.log(`SNAPPY: ${compressed.length} bytes`);
|
|
919
|
-
console.log(`GZIP: ${gzipped.length} bytes`);
|
|
920
|
-
|
|
921
|
-
// Upload smaller file
|
|
922
|
-
await sftp.uploadFile('/data/outgoing/data.parquet', gzipped);
|
|
923
|
-
} finally {
|
|
924
|
-
await sftp.dispose();
|
|
925
|
-
}
|
|
926
|
-
```
|
|
927
|
-
|
|
928
|
-
**Note:** For reading Parquet files, use `ParquetParserService` from the SDK:
|
|
929
|
-
|
|
930
|
-
```typescript
|
|
931
|
-
import { ParquetParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
932
|
-
|
|
933
|
-
const parser = new ParquetParserService();
|
|
934
|
-
const content = await sftp.downloadFile('/data/incoming/inventory.parquet', {
|
|
935
|
-
asBuffer: true
|
|
936
|
-
});
|
|
937
|
-
const records = await parser.parse(content as Buffer);
|
|
938
|
-
```
|
|
939
|
-
|
|
940
|
-
## Key Takeaways
|
|
941
|
-
|
|
942
|
-
- 🎯 Always call `dispose()` in `finally` blocks to close connections
|
|
943
|
-
- 🎯 Use absolute paths with leading slashes (`/data/file.csv`)
|
|
944
|
-
- 🎯 SFTP has native move operation (faster than S3's copy + delete)
|
|
945
|
-
- 🎯 Use `createDirectory()` with `recursive: true` to create parent directories
|
|
946
|
-
- 🎯 Use `directoryExists()` to check before creating directories
|
|
947
|
-
- 🎯 Built-in `parseCsvContent()` and `parseJsonContent()` eliminate need for separate parsers
|
|
948
|
-
- 🎯 Built-in `writeCsvContent()` and `writeJsonContent()` for generating content
|
|
949
|
-
- 🎯 `writeParquetContent()` generates compressed Parquet files efficiently
|
|
950
|
-
- 🎯 Reuse connections for batch operations to improve performance
|
|
951
|
-
- 🎯 Handle authentication errors, connection timeouts, and permission issues
|
|
952
|
-
|
|
953
|
-
## Next Steps
|
|
954
|
-
|
|
955
|
-
Continue to [Module 5: Streaming & Performance](./file-operations-05-streaming-performance.md) for handling large files.
|
|
956
|
-
|
|
957
|
-
---
|
|
958
|
-
|
|
959
|
-
**Related Resources:**
|
|
960
|
-
|
|
961
|
-
- [Quick Reference](../../../02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md) - SFTP operations cheat sheet
|
|
962
|
-
- [Common Patterns](../examples/common-patterns.ts) - Copy-paste ready snippets
|
|
963
|
-
- [Testing Guide](./file-operations-07-testing-troubleshooting.md) - SFTP test coverage
|
|
1
|
+
# Module 4: SFTP Operations
|
|
2
|
+
|
|
3
|
+
**Level:** Intermediate
|
|
4
|
+
**Estimated Time:** 25 minutes
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
Complete guide to SFTP-specific file operations, including connection management, directory operations, and SFTP-specific features.
|
|
9
|
+
|
|
10
|
+
## Learning Objectives
|
|
11
|
+
|
|
12
|
+
By the end of this module, you will:
|
|
13
|
+
|
|
14
|
+
- ✅ Master all SFTP file operations
|
|
15
|
+
- ✅ Understand connection pooling and lifecycle
|
|
16
|
+
- ✅ Learn directory creation and path handling
|
|
17
|
+
- ✅ Implement secure authentication (password and key-based)
|
|
18
|
+
- ✅ Handle SFTP-specific errors
|
|
19
|
+
|
|
20
|
+
## SFTP Concepts
|
|
21
|
+
|
|
22
|
+
### Connection Lifecycle
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import {
|
|
26
|
+
SftpDataSource,
|
|
27
|
+
createConsoleLogger,
|
|
28
|
+
toStructuredLogger
|
|
29
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
30
|
+
|
|
31
|
+
const logger = createConsoleLogger();
|
|
32
|
+
|
|
33
|
+
// Create data source (connection established on first operation)
|
|
34
|
+
const sftp = new SftpDataSource(
|
|
35
|
+
{
|
|
36
|
+
settings: {
|
|
37
|
+
remotePath: '/data/incoming',
|
|
38
|
+
filePattern: '*.csv',
|
|
39
|
+
host: process.env.SFTP_HOST!,
|
|
40
|
+
port: 22,
|
|
41
|
+
username: process.env.SFTP_USERNAME!,
|
|
42
|
+
password: process.env.SFTP_PASSWORD!,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
logger
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Use the connection
|
|
50
|
+
const files = await sftp.listFiles();
|
|
51
|
+
} finally {
|
|
52
|
+
// CRITICAL: Always dispose to close connection
|
|
53
|
+
await sftp.dispose();
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Path Conventions
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// ✅ CORRECT: Absolute paths with leading slash
|
|
61
|
+
const file = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
62
|
+
|
|
63
|
+
// ❌ WRONG: Relative paths without leading slash
|
|
64
|
+
const file = await sftp.downloadFile('data/incoming/orders.csv'); // May fail
|
|
65
|
+
|
|
66
|
+
// remotePath in config provides base path
|
|
67
|
+
settings: {
|
|
68
|
+
remotePath: '/data/incoming', // Base path
|
|
69
|
+
filePattern: '*.csv'
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## All SFTP Operations
|
|
74
|
+
|
|
75
|
+
### List Files
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const sftp = new SftpDataSource(
|
|
79
|
+
{
|
|
80
|
+
settings: {
|
|
81
|
+
remotePath: '/data/incoming',
|
|
82
|
+
filePattern: '*.csv', // Pattern specified in config
|
|
83
|
+
host: process.env.SFTP_HOST!,
|
|
84
|
+
username: process.env.SFTP_USERNAME!,
|
|
85
|
+
password: process.env.SFTP_PASSWORD!,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
logger
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// List files matching pattern from remotePath
|
|
93
|
+
const files = await sftp.listFiles();
|
|
94
|
+
|
|
95
|
+
files.forEach(file => {
|
|
96
|
+
console.log(`Name: ${file.name}`);
|
|
97
|
+
console.log(`Path: ${file.path}`);
|
|
98
|
+
console.log(`Size: ${file.size} bytes`);
|
|
99
|
+
});
|
|
100
|
+
} finally {
|
|
101
|
+
await sftp.dispose();
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Download Files
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
try {
|
|
109
|
+
const content = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
110
|
+
console.log(`Downloaded ${content.length} bytes`);
|
|
111
|
+
|
|
112
|
+
// Process content
|
|
113
|
+
const lines = content.split('\n');
|
|
114
|
+
console.log(`File has ${lines.length} lines`);
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
if (error.message.includes('No such file')) {
|
|
117
|
+
console.error('File does not exist');
|
|
118
|
+
} else {
|
|
119
|
+
console.error('Download failed:', error);
|
|
120
|
+
}
|
|
121
|
+
} finally {
|
|
122
|
+
await sftp.dispose();
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Upload Files
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
try {
|
|
130
|
+
const data = JSON.stringify({ status: 'processed' });
|
|
131
|
+
|
|
132
|
+
await sftp.uploadFile('/data/outgoing/results.json', data);
|
|
133
|
+
console.log('Upload successful');
|
|
134
|
+
|
|
135
|
+
// Upload buffer
|
|
136
|
+
const buffer = Buffer.from('Hello, SFTP!');
|
|
137
|
+
await sftp.uploadFile('/data/messages/greeting.txt', buffer);
|
|
138
|
+
} finally {
|
|
139
|
+
await sftp.dispose();
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Options parameter** (optional):
|
|
144
|
+
```typescript
|
|
145
|
+
await sftp.uploadFile('/data/outgoing/results.json', data, {
|
|
146
|
+
overwrite: true, // Overwrite existing file (default: false)
|
|
147
|
+
createDirectories: true, // Create parent directories (default: false)
|
|
148
|
+
permissions: '0644', // File permissions (default: server default)
|
|
149
|
+
encoding: 'utf8' // Content encoding (default: 'utf8')
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Move Files
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
try {
|
|
157
|
+
// Move/rename file (default: overwrite = false)
|
|
158
|
+
await sftp.moveFile('/data/incoming/orders.csv', '/data/processed/orders.csv');
|
|
159
|
+
|
|
160
|
+
// Move with overwrite flag
|
|
161
|
+
await sftp.moveFile(
|
|
162
|
+
'/data/incoming/inventory.csv',
|
|
163
|
+
'/archive/2025/01/15/inventory.csv',
|
|
164
|
+
true // Overwrite if destination exists
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
console.log('Files moved successfully');
|
|
168
|
+
} finally {
|
|
169
|
+
await sftp.dispose();
|
|
170
|
+
}
|
|
171
|
+
````
|
|
172
|
+
|
|
173
|
+
### Copy Files
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
try {
|
|
177
|
+
// Copy file (default: overwrite = false)
|
|
178
|
+
await sftp.copyFile('/data/important/config.json', '/backup/config.json');
|
|
179
|
+
|
|
180
|
+
// Copy with overwrite
|
|
181
|
+
await sftp.copyFile('/data/source.json', '/backup/target.json', true);
|
|
182
|
+
|
|
183
|
+
console.log('File copied successfully');
|
|
184
|
+
} finally {
|
|
185
|
+
await sftp.dispose();
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Delete Files
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
try {
|
|
193
|
+
// Delete file
|
|
194
|
+
await sftp.deleteFile('/data/temp/old-file.csv');
|
|
195
|
+
|
|
196
|
+
// Delete with existence check
|
|
197
|
+
if (await sftp.fileExists('/data/archive/old-data.csv')) {
|
|
198
|
+
await sftp.deleteFile('/data/archive/old-data.csv');
|
|
199
|
+
console.log('File deleted');
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
await sftp.dispose();
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Check File Existence
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
try {
|
|
210
|
+
const exists = await sftp.fileExists('/data/incoming/orders.csv');
|
|
211
|
+
console.log(`File exists: ${exists}`);
|
|
212
|
+
|
|
213
|
+
// Conditional processing
|
|
214
|
+
if (exists) {
|
|
215
|
+
const content = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
216
|
+
// Process file
|
|
217
|
+
}
|
|
218
|
+
} finally {
|
|
219
|
+
await sftp.dispose();
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Authentication Methods
|
|
224
|
+
|
|
225
|
+
SFTP supports multiple authentication methods. How you access credentials depends on your deployment context.
|
|
226
|
+
|
|
227
|
+
### Credential Access by Runtime
|
|
228
|
+
|
|
229
|
+
| Runtime | Method | Location |
|
|
230
|
+
|---------|--------|----------|
|
|
231
|
+
| **Versori** | `activation.connections` (recommended) | Connection UI |
|
|
232
|
+
| **Versori** | `credentials().get()` (alternative) | Connection UI |
|
|
233
|
+
| **Standalone** | Environment variables | `.env` file |
|
|
234
|
+
|
|
235
|
+
### Versori Platform: Connection-Based Auth (Recommended)
|
|
236
|
+
|
|
237
|
+
**Best practice for Versori platform** - credentials are already decoded and type-safe:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { schedule, http } from '@versori/run';
|
|
241
|
+
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
242
|
+
|
|
243
|
+
export const sftpWorkflow = schedule("sftp-sync", "0 * * * *")
|
|
244
|
+
.then(http("process", { connection: "fluent_commerce" }, async (ctx) => {
|
|
245
|
+
const { activation, log } = ctx;
|
|
246
|
+
|
|
247
|
+
// Access ALL connections from activation
|
|
248
|
+
const allConnections = activation.connections || [];
|
|
249
|
+
|
|
250
|
+
// Find SFTP connection by name
|
|
251
|
+
const sftpConnection = allConnections.find(c => c.name === 'versori_ftp_server');
|
|
252
|
+
|
|
253
|
+
if (!sftpConnection) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`SFTP connection not found. Available: ${allConnections.map(c => c.name).join(', ')}`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Extract credentials (already decoded - no Buffer needed!)
|
|
260
|
+
const sftpCred = sftpConnection.credentials[0]?.credential;
|
|
261
|
+
|
|
262
|
+
if (!sftpCred?.data?.basicAuth) {
|
|
263
|
+
throw new Error('SFTP connection missing Basic Authentication configuration');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const sftpUsername = sftpCred.data.basicAuth.username;
|
|
267
|
+
const sftpPassword = sftpCred.data.basicAuth.password;
|
|
268
|
+
const sftpHost = sftpConnection.baseUrl || 'sftp.example.com';
|
|
269
|
+
|
|
270
|
+
// Create SFTP data source
|
|
271
|
+
const sftp = new SftpDataSource(
|
|
272
|
+
{
|
|
273
|
+
settings: {
|
|
274
|
+
host: sftpHost,
|
|
275
|
+
port: 22,
|
|
276
|
+
username: sftpUsername,
|
|
277
|
+
password: sftpPassword,
|
|
278
|
+
remotePath: '/data',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
log
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Use SFTP
|
|
285
|
+
const files = await sftp.listFiles();
|
|
286
|
+
return { success: true, fileCount: files.length };
|
|
287
|
+
}));
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Why this is recommended:**
|
|
291
|
+
- ✅ Credentials already decoded (no Buffer.from needed)
|
|
292
|
+
- ✅ Works in any task type (fn, http, webhook)
|
|
293
|
+
- ✅ Type-safe access
|
|
294
|
+
- ✅ Better error messages
|
|
295
|
+
- ✅ Supports multiple connections
|
|
296
|
+
|
|
297
|
+
### Versori Platform: credentials().get() (Alternative)
|
|
298
|
+
|
|
299
|
+
**Use when working in fn() tasks** where connectionVariables aren't available:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { schedule, fn } from '@versori/run';
|
|
303
|
+
import { Buffer } from 'node:buffer'; // Required for Deno runtime!
|
|
304
|
+
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
305
|
+
|
|
306
|
+
export const sftpWorkflow = schedule("sftp-sync", "0 * * * *")
|
|
307
|
+
.then(fn("get-files", async (ctx) => {
|
|
308
|
+
const { log } = ctx;
|
|
309
|
+
|
|
310
|
+
// Retrieve credentials from connection configuration
|
|
311
|
+
const sftpCred = await ctx.credentials().getAccessToken('SFTP');
|
|
312
|
+
|
|
313
|
+
if (!sftpCred?.accessToken) {
|
|
314
|
+
throw new Error('No SFTP credentials found in connection configuration');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Decode base64 accessToken to get "username:password"
|
|
318
|
+
const rawBasicAuth = Buffer.from(sftpCred.accessToken, 'base64').toString('utf-8');
|
|
319
|
+
|
|
320
|
+
// Split on ':' to extract username and password
|
|
321
|
+
const [sftpUsername, sftpPassword] = rawBasicAuth.split(':');
|
|
322
|
+
|
|
323
|
+
if (!sftpUsername || !sftpPassword) {
|
|
324
|
+
throw new Error('Invalid SFTP credential format - expected username:password');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Create SFTP data source
|
|
328
|
+
const sftp = new SftpDataSource(
|
|
329
|
+
{
|
|
330
|
+
settings: {
|
|
331
|
+
host: 'sftp.example.com',
|
|
332
|
+
port: 22,
|
|
333
|
+
username: sftpUsername,
|
|
334
|
+
password: sftpPassword,
|
|
335
|
+
remotePath: '/data',
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
log
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const files = await sftp.listFiles();
|
|
342
|
+
return { success: true, fileCount: files.length };
|
|
343
|
+
}));
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Limitations:**
|
|
347
|
+
- ⚠️ Requires manual base64 decoding
|
|
348
|
+
- ⚠️ Only works in fn() tasks
|
|
349
|
+
- ⚠️ More error-prone (string splitting)
|
|
350
|
+
|
|
351
|
+
### Standalone: Password Authentication
|
|
352
|
+
|
|
353
|
+
**For Node.js/Deno standalone scripts:**
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { SftpDataSource, createConsoleLogger } from '@fluentcommerce/fc-connect-sdk';
|
|
357
|
+
|
|
358
|
+
const logger = createConsoleLogger();
|
|
359
|
+
|
|
360
|
+
const sftp = new SftpDataSource(
|
|
361
|
+
{
|
|
362
|
+
settings: {
|
|
363
|
+
host: process.env.SFTP_HOST!,
|
|
364
|
+
port: 22,
|
|
365
|
+
username: process.env.SFTP_USERNAME!,
|
|
366
|
+
password: process.env.SFTP_PASSWORD!,
|
|
367
|
+
remotePath: '/data',
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
logger
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const files = await sftp.listFiles();
|
|
375
|
+
console.log(`Found ${files.length} files`);
|
|
376
|
+
} finally {
|
|
377
|
+
await sftp.dispose();
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Environment variables (.env):**
|
|
382
|
+
```bash
|
|
383
|
+
SFTP_HOST=sftp.example.com
|
|
384
|
+
SFTP_USERNAME=myuser
|
|
385
|
+
SFTP_PASSWORD=mypassword
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Standalone: Private Key Authentication
|
|
389
|
+
|
|
390
|
+
**For key-based authentication in standalone scripts:**
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import * as fs from 'fs';
|
|
394
|
+
import { SftpDataSource, createConsoleLogger } from '@fluentcommerce/fc-connect-sdk';
|
|
395
|
+
|
|
396
|
+
const logger = createConsoleLogger();
|
|
397
|
+
const privateKey = fs.readFileSync('/path/to/private-key.pem', 'utf-8');
|
|
398
|
+
|
|
399
|
+
const sftp = new SftpDataSource(
|
|
400
|
+
{
|
|
401
|
+
settings: {
|
|
402
|
+
host: 'sftp.example.com',
|
|
403
|
+
port: 22,
|
|
404
|
+
username: 'myuser',
|
|
405
|
+
privateKey: privateKey,
|
|
406
|
+
passphrase: process.env.KEY_PASSPHRASE, // Optional if key is encrypted
|
|
407
|
+
remotePath: '/data',
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
logger
|
|
411
|
+
);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Security Best Practices
|
|
415
|
+
|
|
416
|
+
**For all authentication methods:**
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// ✅ GOOD: Never log credentials
|
|
420
|
+
log.info('SFTP configuration', {
|
|
421
|
+
host: sftpHost,
|
|
422
|
+
username: sftpUsername,
|
|
423
|
+
hasPassword: !!sftpPassword,
|
|
424
|
+
passwordLength: sftpPassword.length // Log length, not value
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// ❌ BAD: Logging credentials
|
|
428
|
+
log.info('SFTP config', { username, password }); // Don't do this!
|
|
429
|
+
|
|
430
|
+
// ✅ GOOD: Validate credentials before use
|
|
431
|
+
if (!sftpUsername || !sftpPassword) {
|
|
432
|
+
throw new Error('Missing SFTP credentials');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (sftpPassword.length < 8) {
|
|
436
|
+
log.warn('SFTP password seems short', { length: sftpPassword.length });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ✅ GOOD: Centralized credential management
|
|
440
|
+
// Store credentials in Versori Connections (platform) or .env files (standalone)
|
|
441
|
+
// Never hardcode credentials in code
|
|
442
|
+
|
|
443
|
+
// ❌ BAD: Hardcoded credentials
|
|
444
|
+
const username = 'hardcoded-user'; // Don't do this!
|
|
445
|
+
const password = 'hardcoded-pass'; // Don't do this!
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**See also:** [SFTP Credential Access & Security Guide](../../../02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md) for complete credential retrieval patterns.
|
|
449
|
+
|
|
450
|
+
## SFTP-Specific Patterns
|
|
451
|
+
|
|
452
|
+
### Multi-Folder Processing
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
const folders = ['orders', 'inventory', 'products'];
|
|
456
|
+
|
|
457
|
+
for (const folder of folders) {
|
|
458
|
+
const sftp = new SftpDataSource(
|
|
459
|
+
{
|
|
460
|
+
settings: {
|
|
461
|
+
remotePath: `/data/${folder}`,
|
|
462
|
+
filePattern: '*.csv',
|
|
463
|
+
host: process.env.SFTP_HOST!,
|
|
464
|
+
username: process.env.SFTP_USERNAME!,
|
|
465
|
+
password: process.env.SFTP_PASSWORD!,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
logger
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const files = await sftp.listFiles();
|
|
473
|
+
console.log(`Processing ${files.length} files from ${folder}`);
|
|
474
|
+
|
|
475
|
+
for (const file of files) {
|
|
476
|
+
const content = await sftp.downloadFile(file.path);
|
|
477
|
+
await processByType(folder, content);
|
|
478
|
+
|
|
479
|
+
const archivePath = `/archive/${folder}/${file.name}`;
|
|
480
|
+
await sftp.moveFile(file.path, archivePath);
|
|
481
|
+
}
|
|
482
|
+
} finally {
|
|
483
|
+
await sftp.dispose();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Date-Based Archive
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
function getDateBasedPath(filename: string, basePath: string): string {
|
|
492
|
+
const now = new Date();
|
|
493
|
+
const year = now.getFullYear();
|
|
494
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
495
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
496
|
+
|
|
497
|
+
return `${basePath}/${year}/${month}/${day}/${filename}`;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
const files = await sftp.listFiles();
|
|
502
|
+
|
|
503
|
+
for (const file of files) {
|
|
504
|
+
const content = await sftp.downloadFile(file.path);
|
|
505
|
+
await processData(content);
|
|
506
|
+
|
|
507
|
+
const archivePath = getDateBasedPath(file.name, '/archive');
|
|
508
|
+
await sftp.moveFile(file.path, archivePath);
|
|
509
|
+
// Result: /archive/2025/01/15/orders.csv
|
|
510
|
+
}
|
|
511
|
+
} finally {
|
|
512
|
+
await sftp.dispose();
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Batch Processing with Connection Reuse
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
const sftp = new SftpDataSource(
|
|
520
|
+
{
|
|
521
|
+
settings: {
|
|
522
|
+
remotePath: '/data/incoming',
|
|
523
|
+
filePattern: '*.csv',
|
|
524
|
+
host: process.env.SFTP_HOST!,
|
|
525
|
+
username: process.env.SFTP_USERNAME!,
|
|
526
|
+
password: process.env.SFTP_PASSWORD!,
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
logger
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
const files = await sftp.listFiles();
|
|
534
|
+
|
|
535
|
+
// Process all files using same connection
|
|
536
|
+
for (const file of files) {
|
|
537
|
+
try {
|
|
538
|
+
const content = await sftp.downloadFile(file.path);
|
|
539
|
+
const result = await processData(content);
|
|
540
|
+
|
|
541
|
+
await sftp.uploadFile(`/data/outgoing/${file.name.replace('.csv', '.json', JSON.stringify(result))}`
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
const today = new Date().toISOString().split('T')[0];
|
|
545
|
+
await sftp.moveFile(file.path, `/archive/${today}/${file.name}`);
|
|
546
|
+
|
|
547
|
+
console.log(`Processed ${file.name}`);
|
|
548
|
+
} catch (error) {
|
|
549
|
+
console.error(`Failed to process ${file.name}:`, error);
|
|
550
|
+
// Continue with next file
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
} finally {
|
|
554
|
+
// Connection closed once after processing all files
|
|
555
|
+
await sftp.dispose();
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
## Error Handling
|
|
560
|
+
|
|
561
|
+
### Common SFTP Errors
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
try {
|
|
565
|
+
await sftp.downloadFile('/data/missing-file.csv');
|
|
566
|
+
} catch (error: any) {
|
|
567
|
+
const message = error.message.toLowerCase();
|
|
568
|
+
|
|
569
|
+
if (message.includes('no such file')) {
|
|
570
|
+
console.error('File does not exist');
|
|
571
|
+
} else if (message.includes('permission denied')) {
|
|
572
|
+
console.error('Permission denied - check SFTP permissions');
|
|
573
|
+
} else if (message.includes('connection')) {
|
|
574
|
+
console.error('Connection error - check host/port/credentials');
|
|
575
|
+
} else if (message.includes('authentication')) {
|
|
576
|
+
console.error('Authentication failed - check username/password');
|
|
577
|
+
} else if (message.includes('timeout')) {
|
|
578
|
+
console.error('Operation timed out - check network connectivity');
|
|
579
|
+
} else {
|
|
580
|
+
console.error('SFTP error:', error.message);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Connection Retry Logic
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
async function createSftpWithRetry(
|
|
589
|
+
config: any,
|
|
590
|
+
logger: Logger,
|
|
591
|
+
maxRetries = 3
|
|
592
|
+
): Promise<SftpDataSource> {
|
|
593
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
594
|
+
try {
|
|
595
|
+
const sftp = new SftpDataSource(config, logger);
|
|
596
|
+
|
|
597
|
+
// Test connection
|
|
598
|
+
await sftp.fileExists('/'); // Simple operation to verify connection
|
|
599
|
+
|
|
600
|
+
return sftp;
|
|
601
|
+
} catch (error: any) {
|
|
602
|
+
if (attempt === maxRetries) throw error;
|
|
603
|
+
|
|
604
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
605
|
+
console.log(`Connection retry ${attempt}/${maxRetries} after ${delay}ms`);
|
|
606
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
throw new Error('Should not reach here');
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Performance Tips
|
|
614
|
+
|
|
615
|
+
### Connection Pooling
|
|
616
|
+
|
|
617
|
+
The SDK handles connection pooling automatically, but you should:
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
// ✅ GOOD: Reuse connection for multiple operations
|
|
621
|
+
const sftp = new SftpDataSource(config, logger);
|
|
622
|
+
try {
|
|
623
|
+
await sftp.downloadFile('/file1.csv');
|
|
624
|
+
await sftp.downloadFile('/file2.csv');
|
|
625
|
+
await sftp.downloadFile('/file3.csv');
|
|
626
|
+
} finally {
|
|
627
|
+
await sftp.dispose(); // Close once
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ❌ BAD: Create/dispose for each operation
|
|
631
|
+
for (const file of files) {
|
|
632
|
+
const sftp = new SftpDataSource(config, logger);
|
|
633
|
+
await sftp.downloadFile(file.path);
|
|
634
|
+
await sftp.dispose(); // Too many connections!
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Parallel Operations
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
// Process files in parallel (with connection limit)
|
|
642
|
+
const sftp = new SftpDataSource(config, logger);
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
const files = await sftp.listFiles();
|
|
646
|
+
|
|
647
|
+
// Limit concurrency to avoid overwhelming server
|
|
648
|
+
const concurrencyLimit = 5;
|
|
649
|
+
|
|
650
|
+
for (let i = 0; i < files.length; i += concurrencyLimit) {
|
|
651
|
+
const batch = files.slice(i, i + concurrencyLimit);
|
|
652
|
+
|
|
653
|
+
await Promise.all(
|
|
654
|
+
batch.map(async file => {
|
|
655
|
+
const content = await sftp.downloadFile(file.path);
|
|
656
|
+
return processData(content);
|
|
657
|
+
})
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
} finally {
|
|
661
|
+
await sftp.dispose();
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## Directory Operations
|
|
666
|
+
|
|
667
|
+
### Create Directory
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
try {
|
|
671
|
+
// Create directory (non-recursive)
|
|
672
|
+
await sftp.createDirectory('/data/incoming');
|
|
673
|
+
|
|
674
|
+
// Create directory recursively (creates parent directories if needed)
|
|
675
|
+
await sftp.createDirectory('/data/archive/2025/01/15', true);
|
|
676
|
+
|
|
677
|
+
// Create with specific permissions
|
|
678
|
+
await sftp.createDirectory('/data/public', true, '0755');
|
|
679
|
+
} finally {
|
|
680
|
+
await sftp.dispose();
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Check Directory Existence
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
try {
|
|
688
|
+
const exists = await sftp.directoryExists('/data/incoming');
|
|
689
|
+
|
|
690
|
+
if (!exists) {
|
|
691
|
+
await sftp.createDirectory('/data/incoming', true);
|
|
692
|
+
console.log('Directory created');
|
|
693
|
+
} else {
|
|
694
|
+
console.log('Directory already exists');
|
|
695
|
+
}
|
|
696
|
+
} finally {
|
|
697
|
+
await sftp.dispose();
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Directory Operations Pattern
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
async function ensureDirectoryExists(sftp: SftpDataSource, path: string) {
|
|
705
|
+
if (!(await sftp.directoryExists(path))) {
|
|
706
|
+
await sftp.createDirectory(path, true); // Recursive
|
|
707
|
+
console.log(`Created directory: ${path}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
try {
|
|
712
|
+
// Ensure archive directory exists before moving files
|
|
713
|
+
await ensureDirectoryExists(sftp, '/archive/2025/01/15');
|
|
714
|
+
|
|
715
|
+
const files = await sftp.listFiles();
|
|
716
|
+
for (const file of files) {
|
|
717
|
+
const archivePath = `/archive/2025/01/15/${file.name}`;
|
|
718
|
+
await sftp.moveFile(file.path, archivePath);
|
|
719
|
+
}
|
|
720
|
+
} finally {
|
|
721
|
+
await sftp.dispose();
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Built-in Content Parsing
|
|
726
|
+
|
|
727
|
+
SFTP data source provides built-in parsing methods for CSV and JSON content, eliminating the need for separate parser services.
|
|
728
|
+
|
|
729
|
+
### Parse CSV Content
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
try {
|
|
733
|
+
// Download file
|
|
734
|
+
const csvContent = await sftp.downloadFile('/data/incoming/orders.csv');
|
|
735
|
+
|
|
736
|
+
// Parse CSV content directly (no need for CSVParserService)
|
|
737
|
+
const fileMetadata = {
|
|
738
|
+
path: '/data/incoming/orders.csv',
|
|
739
|
+
name: 'orders.csv',
|
|
740
|
+
size: csvContent.length,
|
|
741
|
+
source: 'SFTP'
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const records = sftp.parseCsvContent(csvContent as string, fileMetadata);
|
|
745
|
+
|
|
746
|
+
console.log(`Parsed ${records.length} CSV records`);
|
|
747
|
+
records.forEach(record => {
|
|
748
|
+
console.log(`Order: ${record.orderId}, Total: ${record.total}`);
|
|
749
|
+
});
|
|
750
|
+
} finally {
|
|
751
|
+
await sftp.dispose();
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Parse JSON Content
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
try {
|
|
759
|
+
// Download JSON file
|
|
760
|
+
const jsonContent = await sftp.downloadFile('/data/incoming/orders.json');
|
|
761
|
+
|
|
762
|
+
// Parse JSON content (supports both JSON and JSONL formats)
|
|
763
|
+
const fileMetadata = {
|
|
764
|
+
path: '/data/incoming/orders.json',
|
|
765
|
+
name: 'orders.json',
|
|
766
|
+
size: jsonContent.length,
|
|
767
|
+
source: 'SFTP'
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// Auto-detect format (JSON array or JSONL)
|
|
771
|
+
const records = sftp.parseJsonContent(jsonContent as string, fileMetadata);
|
|
772
|
+
|
|
773
|
+
// Or specify format explicitly
|
|
774
|
+
const jsonRecords = sftp.parseJsonContent(jsonContent as string, fileMetadata, {
|
|
775
|
+
format: 'json', // or 'jsonl'
|
|
776
|
+
validate: true, // Throw on invalid JSON
|
|
777
|
+
maxDepth: 10 // Limit nesting depth
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
console.log(`Parsed ${records.length} JSON records`);
|
|
781
|
+
} finally {
|
|
782
|
+
await sftp.dispose();
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
### Write CSV Content
|
|
787
|
+
|
|
788
|
+
```typescript
|
|
789
|
+
try {
|
|
790
|
+
// Prepare data
|
|
791
|
+
const records = [
|
|
792
|
+
{ sku: 'SKU-001', quantity: 10, location: 'WAREHOUSE-A' },
|
|
793
|
+
{ sku: 'SKU-002', quantity: 25, location: 'WAREHOUSE-B' }
|
|
794
|
+
];
|
|
795
|
+
|
|
796
|
+
// Generate CSV content
|
|
797
|
+
const csvContent = sftp.writeCsvContent(records, {
|
|
798
|
+
headers: ['sku', 'quantity', 'location'],
|
|
799
|
+
delimiter: ',',
|
|
800
|
+
includeHeaders: true
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// Upload CSV file
|
|
804
|
+
await sftp.uploadFile('/data/outgoing/inventory.csv', csvContent);
|
|
805
|
+
} finally {
|
|
806
|
+
await sftp.dispose();
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Write JSON Content
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
try {
|
|
814
|
+
// Prepare data
|
|
815
|
+
const orders = [
|
|
816
|
+
{ id: 'ORD-001', total: 99.99, status: 'pending' },
|
|
817
|
+
{ id: 'ORD-002', total: 149.99, status: 'shipped' }
|
|
818
|
+
];
|
|
819
|
+
|
|
820
|
+
// Generate JSON content (compact or pretty)
|
|
821
|
+
const jsonContent = sftp.writeJsonContent(orders, {
|
|
822
|
+
format: 'json', // or 'jsonl' for JSON Lines
|
|
823
|
+
pretty: true // Pretty-print JSON
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// Upload JSON file
|
|
827
|
+
await sftp.uploadFile('/data/outgoing/orders.json', jsonContent);
|
|
828
|
+
} finally {
|
|
829
|
+
await sftp.dispose();
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### Complete Parsing Workflow
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
try {
|
|
837
|
+
// List CSV files
|
|
838
|
+
const csvFiles = await sftp.listFiles({ filePattern: '*.csv' });
|
|
839
|
+
|
|
840
|
+
for (const file of csvFiles) {
|
|
841
|
+
// Download and parse in one step
|
|
842
|
+
const content = await sftp.downloadFile(file.path);
|
|
843
|
+
const records = sftp.parseCsvContent(content as string, file);
|
|
844
|
+
|
|
845
|
+
// Transform data
|
|
846
|
+
const transformed = records.map(record => ({
|
|
847
|
+
ref: record.sku,
|
|
848
|
+
qty: parseInt(record.quantity),
|
|
849
|
+
locationRef: record.location
|
|
850
|
+
}));
|
|
851
|
+
|
|
852
|
+
// Write transformed data as JSON
|
|
853
|
+
const jsonContent = sftp.writeJsonContent(transformed, { pretty: true });
|
|
854
|
+
const outputPath = file.path.replace('.csv', '.json');
|
|
855
|
+
await sftp.uploadFile(outputPath, jsonContent);
|
|
856
|
+
|
|
857
|
+
// Archive original
|
|
858
|
+
await sftp.moveFile(file.path, `/archive/${file.name}`);
|
|
859
|
+
}
|
|
860
|
+
} finally {
|
|
861
|
+
await sftp.dispose();
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
## Parquet Support
|
|
866
|
+
|
|
867
|
+
SFTP data source supports writing Parquet files for efficient data storage and transfer.
|
|
868
|
+
|
|
869
|
+
### Write Parquet Content
|
|
870
|
+
|
|
871
|
+
```typescript
|
|
872
|
+
try {
|
|
873
|
+
// Prepare data records
|
|
874
|
+
const records = [
|
|
875
|
+
{ sku: 'SKU-001', quantity: 10, price: 99.99, available: true },
|
|
876
|
+
{ sku: 'SKU-002', quantity: 25, price: 149.99, available: true }
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
// Generate Parquet content as Buffer
|
|
880
|
+
const parquetBuffer = await sftp.writeParquetContent(records, {
|
|
881
|
+
compression: 'GZIP', // Options: 'UNCOMPRESSED', 'GZIP', 'SNAPPY', 'LZO', 'BROTLI'
|
|
882
|
+
rowGroupSize: 5000, // Number of rows per row group
|
|
883
|
+
schema: { // Optional: explicit schema (auto-detected if not provided)
|
|
884
|
+
sku: { type: 'UTF8' },
|
|
885
|
+
quantity: { type: 'INT64' },
|
|
886
|
+
price: { type: 'DOUBLE' },
|
|
887
|
+
available: { type: 'BOOLEAN' }
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// Upload Parquet file
|
|
892
|
+
await sftp.uploadFile('/data/outgoing/inventory.parquet', parquetBuffer);
|
|
893
|
+
|
|
894
|
+
console.log(`Generated Parquet file: ${parquetBuffer.length} bytes`);
|
|
895
|
+
} finally {
|
|
896
|
+
await sftp.dispose();
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
### Parquet with Compression
|
|
901
|
+
|
|
902
|
+
```typescript
|
|
903
|
+
try {
|
|
904
|
+
const records = /* ... large dataset ... */;
|
|
905
|
+
|
|
906
|
+
// Use SNAPPY compression for faster processing
|
|
907
|
+
const compressed = await sftp.writeParquetContent(records, {
|
|
908
|
+
compression: 'SNAPPY',
|
|
909
|
+
rowGroupSize: 10000
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// Use GZIP for smaller file size
|
|
913
|
+
const gzipped = await sftp.writeParquetContent(records, {
|
|
914
|
+
compression: 'GZIP',
|
|
915
|
+
rowGroupSize: 10000
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
console.log(`SNAPPY: ${compressed.length} bytes`);
|
|
919
|
+
console.log(`GZIP: ${gzipped.length} bytes`);
|
|
920
|
+
|
|
921
|
+
// Upload smaller file
|
|
922
|
+
await sftp.uploadFile('/data/outgoing/data.parquet', gzipped);
|
|
923
|
+
} finally {
|
|
924
|
+
await sftp.dispose();
|
|
925
|
+
}
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
**Note:** For reading Parquet files, use `ParquetParserService` from the SDK:
|
|
929
|
+
|
|
930
|
+
```typescript
|
|
931
|
+
import { ParquetParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
932
|
+
|
|
933
|
+
const parser = new ParquetParserService();
|
|
934
|
+
const content = await sftp.downloadFile('/data/incoming/inventory.parquet', {
|
|
935
|
+
asBuffer: true
|
|
936
|
+
});
|
|
937
|
+
const records = await parser.parse(content as Buffer);
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
## Key Takeaways
|
|
941
|
+
|
|
942
|
+
- 🎯 Always call `dispose()` in `finally` blocks to close connections
|
|
943
|
+
- 🎯 Use absolute paths with leading slashes (`/data/file.csv`)
|
|
944
|
+
- 🎯 SFTP has native move operation (faster than S3's copy + delete)
|
|
945
|
+
- 🎯 Use `createDirectory()` with `recursive: true` to create parent directories
|
|
946
|
+
- 🎯 Use `directoryExists()` to check before creating directories
|
|
947
|
+
- 🎯 Built-in `parseCsvContent()` and `parseJsonContent()` eliminate need for separate parsers
|
|
948
|
+
- 🎯 Built-in `writeCsvContent()` and `writeJsonContent()` for generating content
|
|
949
|
+
- 🎯 `writeParquetContent()` generates compressed Parquet files efficiently
|
|
950
|
+
- 🎯 Reuse connections for batch operations to improve performance
|
|
951
|
+
- 🎯 Handle authentication errors, connection timeouts, and permission issues
|
|
952
|
+
|
|
953
|
+
## Next Steps
|
|
954
|
+
|
|
955
|
+
Continue to [Module 5: Streaming & Performance](./file-operations-05-streaming-performance.md) for handling large files.
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
**Related Resources:**
|
|
960
|
+
|
|
961
|
+
- [Quick Reference](../../../02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md) - SFTP operations cheat sheet
|
|
962
|
+
- [Common Patterns](../examples/common-patterns.ts) - Copy-paste ready snippets
|
|
963
|
+
- [Testing Guide](./file-operations-07-testing-troubleshooting.md) - SFTP test coverage
|