@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -2
- package/README.md +39 -0
- package/dist/cjs/auth/index.d.ts +3 -0
- package/dist/cjs/auth/index.js +13 -0
- package/dist/cjs/auth/profile-loader.d.ts +18 -0
- package/dist/cjs/auth/profile-loader.js +208 -0
- package/dist/cjs/client-factory.d.ts +4 -0
- package/dist/cjs/client-factory.js +10 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/auth/index.d.ts +3 -0
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/profile-loader.d.ts +18 -0
- package/dist/esm/auth/profile-loader.js +169 -0
- package/dist/esm/client-factory.d.ts +4 -0
- package/dist/esm/client-factory.js +9 -0
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/auth/index.d.ts +3 -0
- package/dist/types/auth/profile-loader.d.ts +18 -0
- package/dist/types/client-factory.d.ts +4 -0
- package/dist/types/index.d.ts +3 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -482
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md
CHANGED
|
@@ -1,1425 +1,1425 @@
|
|
|
1
|
-
# S3 & SFTP Configuration Guide for Versori
|
|
2
|
-
|
|
3
|
-
**FC Connect SDK | Versori Platform Guide**
|
|
4
|
-
|
|
5
|
-
> **Purpose**: Learn the two ways to configure S3 and SFTP connections in Versori workflows, understand when to use each approach, and implement secure, production-ready configurations.
|
|
6
|
-
|
|
7
|
-
**Complexity**: Intermediate | **Time**: 15 minutes
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Table of Contents
|
|
12
|
-
|
|
13
|
-
- [Overview](#overview)
|
|
14
|
-
- [Two Configuration Approaches](#two-configuration-approaches)
|
|
15
|
-
- [Decision Matrix](#decision-matrix)
|
|
16
|
-
- [Approach 1: Versori Connections](#approach-1-versori-connections-recommended-for-production)
|
|
17
|
-
- [Approach 2: Activation Variables](#approach-2-activation-variables-development-simple-use-cases)
|
|
18
|
-
- [Side-by-Side Code Examples](#side-by-side-code-examples)
|
|
19
|
-
- [Security Best Practices](#security-best-practices)
|
|
20
|
-
- [Migration Guide](#migration-guide)
|
|
21
|
-
- [Troubleshooting](#troubleshooting)
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Overview
|
|
26
|
-
|
|
27
|
-
When building Versori workflows that interact with **S3** or **SFTP** servers, you have **two ways** to provide credentials and configuration:
|
|
28
|
-
|
|
29
|
-
| Approach | What It Is | Best For |
|
|
30
|
-
|----------|-----------|----------|
|
|
31
|
-
| **Versori Connections** | Centralized credential management in Versori UI | Production, multi-workflow setups, team collaboration |
|
|
32
|
-
| **Activation Variables** | Per-workflow environment variables | Development, prototyping, single-use workflows |
|
|
33
|
-
|
|
34
|
-
**Both approaches work with FC Connect SDK** - you choose based on your requirements.
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Two Configuration Approaches
|
|
39
|
-
|
|
40
|
-
### Quick Comparison
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
// ❶ VERSORI CONNECTION (Centralized)
|
|
44
|
-
const s3 = new S3DataSource({
|
|
45
|
-
type: 'S3_CSV',
|
|
46
|
-
connectionId: 's3-production', // References centralized connection
|
|
47
|
-
name: 'Production S3',
|
|
48
|
-
s3Config: {
|
|
49
|
-
bucket: ctx.activation?.connection?.params?.bucket,
|
|
50
|
-
region: ctx.activation?.connection?.params?.region,
|
|
51
|
-
credentials: ctx.activation?.connection?.credentials
|
|
52
|
-
}
|
|
53
|
-
}, logger);
|
|
54
|
-
|
|
55
|
-
// ❷ ACTIVATION VARIABLES (Per-workflow)
|
|
56
|
-
const s3 = new S3DataSource({
|
|
57
|
-
type: 'S3_CSV',
|
|
58
|
-
connectionId: 'inline-config',
|
|
59
|
-
name: 'S3 Data Source',
|
|
60
|
-
s3Config: {
|
|
61
|
-
bucket: ctx.activation?.getVariable('s3BucketName') as string,
|
|
62
|
-
region: ctx.activation?.getVariable('awsRegion') as string,
|
|
63
|
-
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
64
|
-
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string
|
|
65
|
-
}
|
|
66
|
-
}, logger);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Key Difference:**
|
|
70
|
-
- **Connections** → Credentials stored once in Versori, referenced by name
|
|
71
|
-
- **Variables** → Credentials stored per-workflow as activation variables
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Decision Matrix
|
|
76
|
-
|
|
77
|
-
Use this table to choose the right approach:
|
|
78
|
-
|
|
79
|
-
| Factor | Versori Connections | Activation Variables |
|
|
80
|
-
|--------|---------------------|---------------------|
|
|
81
|
-
| **Setup Complexity** | ⚠️ Medium (UI setup required) | ✅ Simple (just add variables) |
|
|
82
|
-
| **Credential Security** | ✅✅ Centralized, encrypted, audited | ⚠️ Duplicated across workflows |
|
|
83
|
-
| **Reusability** | ✅✅ Share across multiple workflows | ❌ One workflow only |
|
|
84
|
-
| **Credential Rotation** | ✅✅ Update once, affects all workflows | ❌ Update every workflow individually |
|
|
85
|
-
| **Team Collaboration** | ✅✅ Central management, access control | ⚠️ Each dev needs credentials |
|
|
86
|
-
| **Development Speed** | ⚠️ Slower (create connection first) | ✅ Faster (directly add variables) |
|
|
87
|
-
| **Best For** | **Production environments** | **Development, prototyping, POC** |
|
|
88
|
-
| **SDK Support** | ✅ Full support | ✅ Full support |
|
|
89
|
-
|
|
90
|
-
### 🎯 Recommendations
|
|
91
|
-
|
|
92
|
-
**Use Versori Connections when:**
|
|
93
|
-
- ✅ Deploying to production
|
|
94
|
-
- ✅ Multiple workflows use the same S3 bucket or SFTP server
|
|
95
|
-
- ✅ Working in a team (shared credential management)
|
|
96
|
-
- ✅ Security/compliance requires centralized credential storage
|
|
97
|
-
- ✅ Credentials need to be rotated regularly
|
|
98
|
-
|
|
99
|
-
**Use Activation Variables when:**
|
|
100
|
-
- ✅ Prototyping or proof-of-concept
|
|
101
|
-
- ✅ One-off data migration or testing
|
|
102
|
-
- ✅ Each workflow needs different S3/SFTP credentials
|
|
103
|
-
- ✅ Local development or sandbox environments
|
|
104
|
-
- ✅ Quick iteration without UI configuration
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Approach 1: Versori Connections (Recommended for Production)
|
|
109
|
-
|
|
110
|
-
### Benefits
|
|
111
|
-
|
|
112
|
-
✅ **Centralized Management** - One place to manage credentials
|
|
113
|
-
✅ **Reusable** - Reference the same connection across multiple workflows
|
|
114
|
-
✅ **Secure** - Encrypted storage with access control
|
|
115
|
-
✅ **Auditable** - Track who uses which credentials
|
|
116
|
-
✅ **Easy Rotation** - Update once, all workflows inherit changes
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Credential Management: Connection vs Activation
|
|
121
|
-
|
|
122
|
-
**IMPORTANT:** There are **two secure approaches** to fetch credentials - choose based on your security requirements.
|
|
123
|
-
|
|
124
|
-
**⚠️ Versori/Deno Runtime Note:** When decoding Base64 credentials, always import Buffer:
|
|
125
|
-
```typescript
|
|
126
|
-
import { Buffer } from 'node:buffer';
|
|
127
|
-
```
|
|
128
|
-
This is required in Deno/Versori runtime (unlike Node.js where Buffer is global).
|
|
129
|
-
|
|
130
|
-
### Approach 1A: Fetch from Versori Connection (Most Secure - Production)
|
|
131
|
-
|
|
132
|
-
**Why Use This:**
|
|
133
|
-
- ✅ **Most secure** - Credentials stored in Versori connection vault, not activation variables
|
|
134
|
-
- ✅ **Centralized management** - Update connection in one place
|
|
135
|
-
- ✅ **Access control** - Versori manages who can access credentials
|
|
136
|
-
- ✅ **Audit trail** - Connection usage is logged by platform
|
|
137
|
-
- ✅ **Production-ready** - Best practice for deployed workflows
|
|
138
|
-
|
|
139
|
-
**How It Works:**
|
|
140
|
-
1. **Create Basic Auth connection in Versori:**
|
|
141
|
-
- Connection Name: `versori_ftp_server`
|
|
142
|
-
- Type: Basic Auth
|
|
143
|
-
- Username: your-sftp-username
|
|
144
|
-
- Password: your-sftp-password
|
|
145
|
-
|
|
146
|
-
2. **Versori encodes credentials:**
|
|
147
|
-
- Format: `username:password`
|
|
148
|
-
- Encoding: Base64
|
|
149
|
-
- Stored as `accessToken`
|
|
150
|
-
|
|
151
|
-
3. **Your code decodes:**
|
|
152
|
-
- `ctx.connections.connection_name.accessToken` → get base64 token from connection object
|
|
153
|
-
- Decode base64 → get `username:password` string
|
|
154
|
-
- Split by `:` → extract username and password
|
|
155
|
-
|
|
156
|
-
**Example: SFTP with Connection Credentials**
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
import { Buffer } from 'node:buffer';
|
|
160
|
-
import { schedule, http } from '@versori/run';
|
|
161
|
-
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
162
|
-
|
|
163
|
-
export const sftpIngestion = schedule('sftp-sync', '0 2 * * *').then(
|
|
164
|
-
http('process-sftp-files', {
|
|
165
|
-
connection: 'fluent_commerce'
|
|
166
|
-
}, async (ctx) => {
|
|
167
|
-
const { log, activation } = ctx;
|
|
168
|
-
|
|
169
|
-
// ✅ APPROACH 1A: Fetch credentials from Versori connection (SECURE - Production)
|
|
170
|
-
// IMPORTANT: Access connections directly via ctx.connections (Versori platform pattern)
|
|
171
|
-
const { connections } = ctx;
|
|
172
|
-
const conn = connections.versori_ftp_server;
|
|
173
|
-
const rawAccessToken = conn.accessToken;
|
|
174
|
-
|
|
175
|
-
// Decode base64-encoded "username:password"
|
|
176
|
-
const rawBasicAuth = Buffer.from(rawAccessToken, 'base64').toString('utf-8');
|
|
177
|
-
const [username, password] = rawBasicAuth.split(':');
|
|
178
|
-
|
|
179
|
-
// Initialize SftpDataSource with connection credentials
|
|
180
|
-
const sftp = new SftpDataSource({
|
|
181
|
-
type: 'SFTP_CSV',
|
|
182
|
-
connectionId: 'versori_ftp_server',
|
|
183
|
-
name: 'FTP Server',
|
|
184
|
-
settings: {
|
|
185
|
-
host: activation.getVariable('sftpHost'),
|
|
186
|
-
port: parseInt(activation.getVariable('sftpPort') || '22'),
|
|
187
|
-
username, // ← From connection (secure)
|
|
188
|
-
password, // ← From connection (secure)
|
|
189
|
-
remotePath: '/incoming',
|
|
190
|
-
filePattern: '*.csv'
|
|
191
|
-
}
|
|
192
|
-
}, log);
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
// Use SFTP normally
|
|
196
|
-
const files = await sftp.listFiles({ directory: '/incoming' });
|
|
197
|
-
log.info(`Found ${files.length} files`);
|
|
198
|
-
|
|
199
|
-
for (const file of files) {
|
|
200
|
-
const content = await sftp.downloadFile(file.path, { encoding: 'utf8' });
|
|
201
|
-
log.info('File downloaded', { file: file.name, size: content.length });
|
|
202
|
-
// Process file...
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return { success: true, filesProcessed: files.length };
|
|
206
|
-
} finally {
|
|
207
|
-
await sftp.disconnect();
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Approach 1B: Activation Variables (Development/Testing)
|
|
214
|
-
|
|
215
|
-
**When Use This:**
|
|
216
|
-
- Development/testing environments
|
|
217
|
-
- Quick prototyping
|
|
218
|
-
- When connection setup is not yet complete
|
|
219
|
-
|
|
220
|
-
**Example: SFTP with Activation Variables**
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
import { Buffer } from 'node:buffer';
|
|
224
|
-
import { schedule, http } from '@versori/run';
|
|
225
|
-
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
226
|
-
|
|
227
|
-
export const sftpIngestion = schedule('sftp-sync', '0 2 * * *').then(
|
|
228
|
-
http('process-sftp-files', {
|
|
229
|
-
connection: 'fluent_commerce'
|
|
230
|
-
}, async (ctx) => {
|
|
231
|
-
const { log, activation } = ctx;
|
|
232
|
-
|
|
233
|
-
// ✅ APPROACH 1B: Activation variables (Development/Testing)
|
|
234
|
-
const sftp = new SftpDataSource({
|
|
235
|
-
type: 'SFTP_CSV',
|
|
236
|
-
connectionId: 'sftp-server',
|
|
237
|
-
name: 'FTP Server',
|
|
238
|
-
settings: {
|
|
239
|
-
host: activation.getVariable('sftpHost'),
|
|
240
|
-
port: parseInt(activation.getVariable('sftpPort') || '22'),
|
|
241
|
-
username: activation.getVariable('sftpUsername'), // ← From activation
|
|
242
|
-
password: activation.getVariable('sftpPassword'), // ← From activation
|
|
243
|
-
remotePath: '/incoming',
|
|
244
|
-
filePattern: '*.csv'
|
|
245
|
-
}
|
|
246
|
-
}, log);
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const files = await sftp.listFiles({ directory: '/incoming' });
|
|
250
|
-
log.info(`Found ${files.length} files`);
|
|
251
|
-
return { success: true, filesProcessed: files.length };
|
|
252
|
-
} finally {
|
|
253
|
-
await sftp.disconnect();
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
);
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### Comparison: Connection vs Activation
|
|
260
|
-
|
|
261
|
-
| Factor | Connection (1A) | Activation (1B) |
|
|
262
|
-
|--------|----------------|-----------------|
|
|
263
|
-
| **Security** | ✅✅ Vault storage | ⚠️ Variable storage |
|
|
264
|
-
| **Setup Complexity** | ⚠️ Requires connection creation | ✅ Quick variable setup |
|
|
265
|
-
| **Credential Rotation** | ✅✅ Update once | ❌ Update each workflow |
|
|
266
|
-
| **Team Collaboration** | ✅✅ Central management | ⚠️ Each dev needs creds |
|
|
267
|
-
| **Audit Trail** | ✅✅ Platform-logged | ⚠️ Variable access only |
|
|
268
|
-
| **Best For** | **Production** | **Development/Testing** |
|
|
269
|
-
|
|
270
|
-
### Complete Working Example: Hybrid Approach
|
|
271
|
-
|
|
272
|
-
**Support both methods with fallback for maximum flexibility:**
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
import { Buffer } from 'node:buffer';
|
|
276
|
-
import { schedule, http } from '@versori/run';
|
|
277
|
-
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
278
|
-
|
|
279
|
-
export const sftpIngestion = schedule('sftp-sync', '0 2 * * *').then(
|
|
280
|
-
http('process-sftp-files', {
|
|
281
|
-
connection: 'fluent_commerce'
|
|
282
|
-
}, async (ctx) => {
|
|
283
|
-
const { log, activation } = ctx;
|
|
284
|
-
|
|
285
|
-
// Try connection-based credentials first (production), fallback to activation (development)
|
|
286
|
-
let username: string;
|
|
287
|
-
let password: string;
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
// ✅ PRIMARY: Try connection credentials (most secure)
|
|
291
|
-
const { connections } = ctx;
|
|
292
|
-
const conn = connections.versori_ftp_server;
|
|
293
|
-
const rawAccessToken = conn.accessToken;
|
|
294
|
-
const rawBasicAuth = Buffer.from(rawAccessToken, 'base64').toString('utf-8');
|
|
295
|
-
[username, password] = rawBasicAuth.split(':');
|
|
296
|
-
|
|
297
|
-
log.info('Using connection credentials for SFTP');
|
|
298
|
-
} catch (error) {
|
|
299
|
-
// ⚠️ FALLBACK: Use activation variables (development/testing)
|
|
300
|
-
username = activation.getVariable('sftpUsername');
|
|
301
|
-
password = activation.getVariable('sftpPassword');
|
|
302
|
-
|
|
303
|
-
log.warn('Connection credentials unavailable, using activation variables');
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Validate credentials are available
|
|
307
|
-
if (!username || !password) {
|
|
308
|
-
throw new Error('SFTP credentials not found in connection or activation variables');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Initialize SftpDataSource
|
|
312
|
-
const sftp = new SftpDataSource({
|
|
313
|
-
type: 'SFTP_CSV',
|
|
314
|
-
connectionId: 'versori_ftp_server',
|
|
315
|
-
name: 'FTP Server',
|
|
316
|
-
settings: {
|
|
317
|
-
host: activation.getVariable('sftpHost'),
|
|
318
|
-
port: parseInt(activation.getVariable('sftpPort') || '22'),
|
|
319
|
-
username,
|
|
320
|
-
password,
|
|
321
|
-
remotePath: '/incoming',
|
|
322
|
-
filePattern: '*.csv'
|
|
323
|
-
}
|
|
324
|
-
}, log);
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
const files = await sftp.listFiles({ directory: '/incoming' });
|
|
328
|
-
log.info(`Found ${files.length} files`);
|
|
329
|
-
return { success: true, filesProcessed: files.length };
|
|
330
|
-
} finally {
|
|
331
|
-
await sftp.disconnect();
|
|
332
|
-
}
|
|
333
|
-
})
|
|
334
|
-
);
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
### Setup: AWS S3 Connection
|
|
340
|
-
|
|
341
|
-
#### Step 1: Create Connection in Versori UI
|
|
342
|
-
|
|
343
|
-
1. **Navigate to Versori Dashboard** → **Connections** tab
|
|
344
|
-
2. Click **"Add Connection"**
|
|
345
|
-
3. **Configure Connection:**
|
|
346
|
-
|
|
347
|
-
```
|
|
348
|
-
Name: s3-production
|
|
349
|
-
Description: Production S3 bucket for inventory data
|
|
350
|
-
Type: AWS S3 (or API Key if S3 not available)
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
4. **Authentication Details:**
|
|
354
|
-
|
|
355
|
-
```
|
|
356
|
-
AWS Access Key ID: AKIAIOSFODNN7EXAMPLE
|
|
357
|
-
AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
358
|
-
Region: us-east-1
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
5. **Additional Parameters (Optional):**
|
|
362
|
-
|
|
363
|
-
```json
|
|
364
|
-
{
|
|
365
|
-
"bucket": "my-production-inventory",
|
|
366
|
-
"prefix": "inventory/incoming/"
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
6. **Test Connection** → Verify access
|
|
371
|
-
7. **Save Connection**
|
|
372
|
-
|
|
373
|
-
#### Step 2: Use Connection in Workflow Code
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
import { schedule, http } from '@versori/run';
|
|
377
|
-
import { createClient, S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
378
|
-
|
|
379
|
-
export const inventorySync = schedule('daily-sync', '0 2 * * *').then(
|
|
380
|
-
http('process-s3-files', {
|
|
381
|
-
connection: 'fluent_commerce', // Fluent connection
|
|
382
|
-
additionalConnections: {
|
|
383
|
-
s3: 's3-production' // ← Reference S3 connection by name
|
|
384
|
-
}
|
|
385
|
-
}, async (ctx) => {
|
|
386
|
-
const log = ctx.log;
|
|
387
|
-
|
|
388
|
-
// Access S3 connection
|
|
389
|
-
const s3Connection = ctx.activation?.connections?.s3;
|
|
390
|
-
|
|
391
|
-
// Initialize S3DataSource with connection credentials
|
|
392
|
-
const s3DataSource = new S3DataSource({
|
|
393
|
-
type: 'S3_CSV',
|
|
394
|
-
connectionId: s3Connection?.id || 's3-production',
|
|
395
|
-
name: 'Production S3',
|
|
396
|
-
s3Config: {
|
|
397
|
-
bucket: s3Connection?.params?.bucket || ctx.activation?.getVariable('s3BucketName') as string,
|
|
398
|
-
region: s3Connection?.params?.region || 'us-east-1',
|
|
399
|
-
accessKeyId: s3Connection?.credentials?.accessKeyId,
|
|
400
|
-
secretAccessKey: s3Connection?.credentials?.secretAccessKey
|
|
401
|
-
}
|
|
402
|
-
}, log);
|
|
403
|
-
|
|
404
|
-
// Use S3DataSource normally
|
|
405
|
-
const files = await s3DataSource.listFiles({ prefix: 'inventory/' });
|
|
406
|
-
log.info(`Found ${files.length} files`);
|
|
407
|
-
|
|
408
|
-
return { success: true, filesFound: files.length };
|
|
409
|
-
})
|
|
410
|
-
);
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**Key Points:**
|
|
414
|
-
- ✅ `additionalConnections` parameter references the connection name
|
|
415
|
-
- ✅ Access via `ctx.activation?.connections?.s3`
|
|
416
|
-
- ✅ Fallback to activation variables for flexibility
|
|
417
|
-
- ✅ No credentials in code
|
|
418
|
-
|
|
419
|
-
#### Step 3: Test & Deploy
|
|
420
|
-
|
|
421
|
-
```bash
|
|
422
|
-
# Deploy workflow
|
|
423
|
-
versori deploy
|
|
424
|
-
|
|
425
|
-
# Verify connection
|
|
426
|
-
versori logs --workflow daily-sync
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
### Setup: SFTP Connection
|
|
432
|
-
|
|
433
|
-
#### Step 1: Create Connection in Versori UI
|
|
434
|
-
|
|
435
|
-
1. **Navigate to** → **Connections** → **"Add Connection"**
|
|
436
|
-
2. **Configure:**
|
|
437
|
-
|
|
438
|
-
```
|
|
439
|
-
Name: sftp-partner-server
|
|
440
|
-
Description: Partner SFTP server for exports
|
|
441
|
-
Type: SFTP (or Custom if not available)
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
3. **SFTP Details:**
|
|
445
|
-
|
|
446
|
-
```
|
|
447
|
-
Host: sftp.partner.com
|
|
448
|
-
Port: 22
|
|
449
|
-
Username: inventory_export
|
|
450
|
-
Authentication: Password (or SSH Key)
|
|
451
|
-
Password: ******** (or upload private key)
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
4. **Additional Parameters:**
|
|
455
|
-
|
|
456
|
-
```json
|
|
457
|
-
{
|
|
458
|
-
"remotePath": "/incoming/inventory/",
|
|
459
|
-
"timeout": 30000,
|
|
460
|
-
"keepalive": 10000
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
5. **Test Connection** → **Save**
|
|
465
|
-
|
|
466
|
-
#### Step 2: Use Connection in Workflow
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
import { schedule, http } from '@versori/run';
|
|
470
|
-
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
471
|
-
|
|
472
|
-
export const sftpExport = schedule('daily-export', '0 3 * * *').then(
|
|
473
|
-
http('export-to-sftp', {
|
|
474
|
-
connection: 'fluent_commerce',
|
|
475
|
-
additionalConnections: {
|
|
476
|
-
sftp: 'sftp-partner-server' // ← Reference SFTP connection
|
|
477
|
-
}
|
|
478
|
-
}, async (ctx) => {
|
|
479
|
-
const log = ctx.log;
|
|
480
|
-
|
|
481
|
-
// Access SFTP connection
|
|
482
|
-
const sftpConnection = ctx.activation?.connections?.sftp;
|
|
483
|
-
|
|
484
|
-
// Initialize SftpDataSource with connection credentials
|
|
485
|
-
const sftpDataSource = new SftpDataSource({
|
|
486
|
-
type: 'SFTP_CSV',
|
|
487
|
-
connectionId: sftpConnection?.id || 'sftp-partner',
|
|
488
|
-
name: 'Partner SFTP',
|
|
489
|
-
sftpConfig: {
|
|
490
|
-
host: sftpConnection?.params?.host || ctx.activation?.getVariable('sftpHost') as string,
|
|
491
|
-
port: parseInt(sftpConnection?.params?.port || '22'),
|
|
492
|
-
username: sftpConnection?.credentials?.username,
|
|
493
|
-
password: sftpConnection?.credentials?.password,
|
|
494
|
-
privateKey: sftpConnection?.credentials?.privateKey,
|
|
495
|
-
timeout: parseInt(sftpConnection?.params?.timeout || '30000'),
|
|
496
|
-
keepaliveInterval: parseInt(sftpConnection?.params?.keepalive || '10000')
|
|
497
|
-
}
|
|
498
|
-
}, log);
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
// Upload file to SFTP
|
|
502
|
-
const csvData = 'sku,qty\nSKU-001,100\n';
|
|
503
|
-
await sftpDataSource.uploadFile(
|
|
504
|
-
'/incoming/inventory/inventory-export.csv',
|
|
505
|
-
Buffer.from(csvData, 'utf-8'),
|
|
506
|
-
{ encoding: 'utf-8' }
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
log.info('File uploaded successfully');
|
|
510
|
-
return { success: true };
|
|
511
|
-
} finally {
|
|
512
|
-
// Always disconnect SFTP
|
|
513
|
-
await sftpDataSource.disconnect();
|
|
514
|
-
}
|
|
515
|
-
})
|
|
516
|
-
);
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
**Key Points:**
|
|
520
|
-
- ✅ `additionalConnections.sftp` references connection
|
|
521
|
-
- ✅ Support for password OR SSH key authentication
|
|
522
|
-
- ✅ Always call `disconnect()` in finally block
|
|
523
|
-
- ✅ Timeout and keep-alive from connection config
|
|
524
|
-
|
|
525
|
-
---
|
|
526
|
-
|
|
527
|
-
## Approach 2: Activation Variables (Development & Simple Use Cases)
|
|
528
|
-
|
|
529
|
-
### Benefits
|
|
530
|
-
|
|
531
|
-
✅ **Quick Setup** - No UI configuration needed
|
|
532
|
-
✅ **Per-Workflow Control** - Each workflow has independent config
|
|
533
|
-
✅ **Development-Friendly** - Easy to test with different credentials
|
|
534
|
-
✅ **Explicit** - All configuration visible in code
|
|
535
|
-
|
|
536
|
-
### Setup: AWS S3 with Activation Variables
|
|
537
|
-
|
|
538
|
-
#### Step 1: Define Activation Variables
|
|
539
|
-
|
|
540
|
-
In Versori UI → **Workflow Settings** → **Activation Variables**:
|
|
541
|
-
|
|
542
|
-
```json
|
|
543
|
-
{
|
|
544
|
-
"s3BucketName": "my-inventory-bucket",
|
|
545
|
-
"awsRegion": "us-east-1",
|
|
546
|
-
"awsAccessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
547
|
-
"awsSecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
548
|
-
"s3Prefix": "inventory/incoming/"
|
|
549
|
-
}
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
**Security Note:** These are encrypted by Versori platform.
|
|
553
|
-
|
|
554
|
-
#### Step 2: Use Variables in Code
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
import { schedule, http } from '@versori/run';
|
|
558
|
-
import { createClient, S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
559
|
-
|
|
560
|
-
export const inventorySync = schedule('daily-sync', '0 2 * * *').then(
|
|
561
|
-
http('process-s3-files', {
|
|
562
|
-
connection: 'fluent_commerce'
|
|
563
|
-
}, async (ctx) => {
|
|
564
|
-
const log = ctx.log;
|
|
565
|
-
|
|
566
|
-
// Load configuration from activation variables
|
|
567
|
-
const config = {
|
|
568
|
-
s3Bucket: ctx.activation?.getVariable('s3BucketName') as string,
|
|
569
|
-
s3Region: ctx.activation?.getVariable('awsRegion') as string || 'us-east-1',
|
|
570
|
-
s3AccessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
571
|
-
s3SecretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string,
|
|
572
|
-
s3Prefix: ctx.activation?.getVariable('s3Prefix') as string || 'inventory/'
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
// Validate required variables
|
|
576
|
-
const missingVars: string[] = [];
|
|
577
|
-
if (!config.s3Bucket) missingVars.push('s3BucketName');
|
|
578
|
-
if (!config.s3AccessKeyId) missingVars.push('awsAccessKeyId');
|
|
579
|
-
if (!config.s3SecretAccessKey) missingVars.push('awsSecretAccessKey');
|
|
580
|
-
|
|
581
|
-
if (missingVars.length > 0) {
|
|
582
|
-
const errorMsg = `Missing required activation variables: ${missingVars.join(', ')}`;
|
|
583
|
-
log.error(errorMsg);
|
|
584
|
-
return { success: false, error: errorMsg };
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Initialize S3DataSource with activation variables
|
|
588
|
-
const s3DataSource = new S3DataSource({
|
|
589
|
-
type: 'S3_CSV',
|
|
590
|
-
connectionId: 'inline-s3-config',
|
|
591
|
-
name: 'S3 Data Source',
|
|
592
|
-
s3Config: {
|
|
593
|
-
bucket: config.s3Bucket,
|
|
594
|
-
region: config.s3Region,
|
|
595
|
-
accessKeyId: config.s3AccessKeyId,
|
|
596
|
-
secretAccessKey: config.s3SecretAccessKey
|
|
597
|
-
}
|
|
598
|
-
}, log);
|
|
599
|
-
|
|
600
|
-
// Use S3DataSource
|
|
601
|
-
const files = await s3DataSource.listFiles({ prefix: config.s3Prefix });
|
|
602
|
-
log.info(`Found ${files.length} files`);
|
|
603
|
-
|
|
604
|
-
return { success: true, filesFound: files.length };
|
|
605
|
-
})
|
|
606
|
-
);
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
**Key Points:**
|
|
610
|
-
- ✅ All config loaded via `ctx.activation?.getVariable()`
|
|
611
|
-
- ✅ Validation ensures required variables are present
|
|
612
|
-
- ✅ Fallback values for optional variables
|
|
613
|
-
- ✅ Clear error messages for missing config
|
|
614
|
-
|
|
615
|
-
---
|
|
616
|
-
|
|
617
|
-
### Setup: SFTP with Activation Variables
|
|
618
|
-
|
|
619
|
-
#### Step 1: Define Activation Variables
|
|
620
|
-
|
|
621
|
-
```json
|
|
622
|
-
{
|
|
623
|
-
"sftpHost": "sftp.partner.com",
|
|
624
|
-
"sftpPort": "22",
|
|
625
|
-
"sftpUsername": "inventory_export",
|
|
626
|
-
"sftpPassword": "********",
|
|
627
|
-
"sftpRemotePath": "/incoming/inventory/",
|
|
628
|
-
"sftpTimeout": "30000"
|
|
629
|
-
}
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
**SSH Key Authentication (Alternative):**
|
|
633
|
-
|
|
634
|
-
```json
|
|
635
|
-
{
|
|
636
|
-
"sftpHost": "sftp.partner.com",
|
|
637
|
-
"sftpPort": "22",
|
|
638
|
-
"sftpUsername": "inventory_export",
|
|
639
|
-
"sftpPrivateKey": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----",
|
|
640
|
-
"sftpPassphrase": "keypassphrase",
|
|
641
|
-
"sftpRemotePath": "/incoming/inventory/"
|
|
642
|
-
}
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
#### Step 2: Use Variables in Code
|
|
646
|
-
|
|
647
|
-
```typescript
|
|
648
|
-
import { schedule, http } from '@versori/run';
|
|
649
|
-
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
650
|
-
|
|
651
|
-
export const sftpExport = schedule('daily-export', '0 3 * * *').then(
|
|
652
|
-
http('export-to-sftp', {
|
|
653
|
-
connection: 'fluent_commerce'
|
|
654
|
-
}, async (ctx) => {
|
|
655
|
-
const log = ctx.log;
|
|
656
|
-
|
|
657
|
-
// Load SFTP configuration from activation variables
|
|
658
|
-
const config = {
|
|
659
|
-
sftpHost: ctx.activation?.getVariable('sftpHost') as string,
|
|
660
|
-
sftpPort: parseInt(ctx.activation?.getVariable('sftpPort') as string || '22'),
|
|
661
|
-
sftpUsername: ctx.activation?.getVariable('sftpUsername') as string,
|
|
662
|
-
sftpPassword: ctx.activation?.getVariable('sftpPassword') as string,
|
|
663
|
-
sftpPrivateKey: ctx.activation?.getVariable('sftpPrivateKey') as string,
|
|
664
|
-
sftpPassphrase: ctx.activation?.getVariable('sftpPassphrase') as string,
|
|
665
|
-
sftpRemotePath: ctx.activation?.getVariable('sftpRemotePath') as string || '/',
|
|
666
|
-
sftpTimeout: parseInt(ctx.activation?.getVariable('sftpTimeout') as string || '30000')
|
|
667
|
-
};
|
|
668
|
-
|
|
669
|
-
// Validate required variables
|
|
670
|
-
const missingVars: string[] = [];
|
|
671
|
-
if (!config.sftpHost) missingVars.push('sftpHost');
|
|
672
|
-
if (!config.sftpUsername) missingVars.push('sftpUsername');
|
|
673
|
-
if (!config.sftpPassword && !config.sftpPrivateKey) {
|
|
674
|
-
missingVars.push('sftpPassword OR sftpPrivateKey');
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
if (missingVars.length > 0) {
|
|
678
|
-
const errorMsg = `Missing required activation variables: ${missingVars.join(', ')}`;
|
|
679
|
-
log.error(errorMsg);
|
|
680
|
-
return { success: false, error: errorMsg };
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Initialize SftpDataSource
|
|
684
|
-
const sftpDataSource = new SftpDataSource({
|
|
685
|
-
type: 'SFTP_CSV',
|
|
686
|
-
connectionId: 'inline-sftp-config',
|
|
687
|
-
name: 'SFTP Data Source',
|
|
688
|
-
sftpConfig: {
|
|
689
|
-
host: config.sftpHost,
|
|
690
|
-
port: config.sftpPort,
|
|
691
|
-
username: config.sftpUsername,
|
|
692
|
-
password: config.sftpPassword,
|
|
693
|
-
privateKey: config.sftpPrivateKey,
|
|
694
|
-
passphrase: config.sftpPassphrase,
|
|
695
|
-
timeout: config.sftpTimeout,
|
|
696
|
-
keepaliveInterval: 10000,
|
|
697
|
-
retryDelay: 2000,
|
|
698
|
-
maxRetries: 3
|
|
699
|
-
}
|
|
700
|
-
}, log);
|
|
701
|
-
|
|
702
|
-
try {
|
|
703
|
-
// Upload file
|
|
704
|
-
const csvData = 'sku,qty\nSKU-001,100\n';
|
|
705
|
-
const remotePath = `${config.sftpRemotePath}/inventory-export-${Date.now()}.csv`;
|
|
706
|
-
|
|
707
|
-
await sftpDataSource.uploadFile(
|
|
708
|
-
remotePath,
|
|
709
|
-
Buffer.from(csvData, 'utf-8'),
|
|
710
|
-
{ encoding: 'utf-8' }
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
log.info(`File uploaded to ${remotePath}`);
|
|
714
|
-
return { success: true, remotePath };
|
|
715
|
-
} catch (error: any) {
|
|
716
|
-
log.error('SFTP upload failed', { error: error.message });
|
|
717
|
-
return { success: false, error: error.message };
|
|
718
|
-
} finally {
|
|
719
|
-
// Always disconnect
|
|
720
|
-
await sftpDataSource.disconnect();
|
|
721
|
-
}
|
|
722
|
-
})
|
|
723
|
-
);
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
**Key Points:**
|
|
727
|
-
- ✅ Support for password OR SSH key authentication
|
|
728
|
-
- ✅ Validation for mutually exclusive auth methods
|
|
729
|
-
- ✅ Always disconnect in finally block
|
|
730
|
-
- ✅ Comprehensive error handling
|
|
731
|
-
|
|
732
|
-
---
|
|
733
|
-
|
|
734
|
-
## Side-by-Side Code Examples
|
|
735
|
-
|
|
736
|
-
### S3DataSource: Both Approaches
|
|
737
|
-
|
|
738
|
-
```typescript
|
|
739
|
-
// ═══════════════════════════════════════════════════════════════
|
|
740
|
-
// APPROACH 1: VERSORI CONNECTION
|
|
741
|
-
// ═══════════════════════════════════════════════════════════════
|
|
742
|
-
import { S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
743
|
-
|
|
744
|
-
// In workflow with additionalConnections: { s3: 's3-production' }
|
|
745
|
-
const s3Connection = ctx.activation?.connections?.s3;
|
|
746
|
-
|
|
747
|
-
const s3DataSource = new S3DataSource({
|
|
748
|
-
type: 'S3_CSV',
|
|
749
|
-
connectionId: s3Connection?.id,
|
|
750
|
-
name: 'Production S3',
|
|
751
|
-
s3Config: {
|
|
752
|
-
bucket: s3Connection?.params?.bucket, // From connection
|
|
753
|
-
region: s3Connection?.params?.region, // From connection
|
|
754
|
-
accessKeyId: s3Connection?.credentials?.accessKeyId, // From connection
|
|
755
|
-
secretAccessKey: s3Connection?.credentials?.secretAccessKey // From connection
|
|
756
|
-
}
|
|
757
|
-
}, logger);
|
|
758
|
-
|
|
759
|
-
// ═══════════════════════════════════════════════════════════════
|
|
760
|
-
// APPROACH 2: ACTIVATION VARIABLES
|
|
761
|
-
// ═══════════════════════════════════════════════════════════════
|
|
762
|
-
const s3DataSource = new S3DataSource({
|
|
763
|
-
type: 'S3_CSV',
|
|
764
|
-
connectionId: 'inline-config',
|
|
765
|
-
name: 'S3 Data Source',
|
|
766
|
-
s3Config: {
|
|
767
|
-
bucket: ctx.activation?.getVariable('s3BucketName') as string, // From variable
|
|
768
|
-
region: ctx.activation?.getVariable('awsRegion') as string, // From variable
|
|
769
|
-
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string, // From variable
|
|
770
|
-
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string // From variable
|
|
771
|
-
}
|
|
772
|
-
}, logger);
|
|
773
|
-
|
|
774
|
-
// ═══════════════════════════════════════════════════════════════
|
|
775
|
-
// USAGE: SAME FOR BOTH APPROACHES
|
|
776
|
-
// ═══════════════════════════════════════════════════════════════
|
|
777
|
-
const files = await s3DataSource.listFiles({ prefix: 'inventory/' });
|
|
778
|
-
const fileContent = await s3DataSource.downloadFile(files[0].path, { encoding: 'utf8' });
|
|
779
|
-
await s3DataSource.uploadFile('output/result.csv', Buffer.from(csvData), {});
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
---
|
|
783
|
-
|
|
784
|
-
### SftpDataSource: Both Approaches
|
|
785
|
-
|
|
786
|
-
```typescript
|
|
787
|
-
// ═══════════════════════════════════════════════════════════════
|
|
788
|
-
// APPROACH 1: VERSORI CONNECTION
|
|
789
|
-
// ═══════════════════════════════════════════════════════════════
|
|
790
|
-
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
791
|
-
|
|
792
|
-
// In workflow with additionalConnections: { sftp: 'sftp-partner' }
|
|
793
|
-
const sftpConnection = ctx.activation?.connections?.sftp;
|
|
794
|
-
|
|
795
|
-
const sftpDataSource = new SftpDataSource({
|
|
796
|
-
type: 'SFTP_CSV',
|
|
797
|
-
connectionId: sftpConnection?.id,
|
|
798
|
-
name: 'Partner SFTP',
|
|
799
|
-
sftpConfig: {
|
|
800
|
-
host: sftpConnection?.params?.host, // From connection
|
|
801
|
-
port: parseInt(sftpConnection?.params?.port || '22'),
|
|
802
|
-
username: sftpConnection?.credentials?.username, // From connection
|
|
803
|
-
password: sftpConnection?.credentials?.password, // From connection
|
|
804
|
-
privateKey: sftpConnection?.credentials?.privateKey, // From connection
|
|
805
|
-
timeout: parseInt(sftpConnection?.params?.timeout || '30000'),
|
|
806
|
-
keepaliveInterval: 10000
|
|
807
|
-
}
|
|
808
|
-
}, logger);
|
|
809
|
-
|
|
810
|
-
// ═══════════════════════════════════════════════════════════════
|
|
811
|
-
// APPROACH 2: ACTIVATION VARIABLES
|
|
812
|
-
// ═══════════════════════════════════════════════════════════════
|
|
813
|
-
const sftpDataSource = new SftpDataSource({
|
|
814
|
-
type: 'SFTP_CSV',
|
|
815
|
-
connectionId: 'inline-config',
|
|
816
|
-
name: 'SFTP Data Source',
|
|
817
|
-
sftpConfig: {
|
|
818
|
-
host: ctx.activation?.getVariable('sftpHost') as string, // From variable
|
|
819
|
-
port: parseInt(ctx.activation?.getVariable('sftpPort') as string || '22'),
|
|
820
|
-
username: ctx.activation?.getVariable('sftpUsername') as string, // From variable
|
|
821
|
-
password: ctx.activation?.getVariable('sftpPassword') as string, // From variable
|
|
822
|
-
privateKey: ctx.activation?.getVariable('sftpPrivateKey') as string, // From variable
|
|
823
|
-
timeout: parseInt(ctx.activation?.getVariable('sftpTimeout') as string || '30000'),
|
|
824
|
-
keepaliveInterval: 10000,
|
|
825
|
-
retryDelay: 2000,
|
|
826
|
-
maxRetries: 3
|
|
827
|
-
}
|
|
828
|
-
}, logger);
|
|
829
|
-
|
|
830
|
-
// ═══════════════════════════════════════════════════════════════
|
|
831
|
-
// USAGE: SAME FOR BOTH APPROACHES
|
|
832
|
-
// ═══════════════════════════════════════════════════════════════
|
|
833
|
-
try {
|
|
834
|
-
const files = await sftpDataSource.listFiles({ directory: '/incoming' });
|
|
835
|
-
const fileContent = await sftpDataSource.downloadFile(files[0].path, { encoding: 'utf8' });
|
|
836
|
-
await sftpDataSource.uploadFile('/outgoing/result.csv', Buffer.from(csvData), {});
|
|
837
|
-
} finally {
|
|
838
|
-
await sftpDataSource.disconnect(); // ⚠️ ALWAYS disconnect SFTP!
|
|
839
|
-
}
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
---
|
|
843
|
-
|
|
844
|
-
## Security Best Practices
|
|
845
|
-
|
|
846
|
-
### 1. Never Hardcode Credentials
|
|
847
|
-
|
|
848
|
-
```typescript
|
|
849
|
-
// ❌ NEVER DO THIS
|
|
850
|
-
const s3DataSource = new S3DataSource({
|
|
851
|
-
s3Config: {
|
|
852
|
-
accessKeyId: 'AKIAIOSFODNN7EXAMPLE', // ❌ Hardcoded!
|
|
853
|
-
secretAccessKey: 'wJalrXUtnFEMI/K...' // ❌ Hardcoded!
|
|
854
|
-
}
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
// ✅ ALWAYS DO THIS (Connection)
|
|
858
|
-
const s3DataSource = new S3DataSource({
|
|
859
|
-
s3Config: {
|
|
860
|
-
accessKeyId: ctx.activation?.connections?.s3?.credentials?.accessKeyId,
|
|
861
|
-
secretAccessKey: ctx.activation?.connections?.s3?.credentials?.secretAccessKey
|
|
862
|
-
}
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
// ✅ OR THIS (Variables)
|
|
866
|
-
const s3DataSource = new S3DataSource({
|
|
867
|
-
s3Config: {
|
|
868
|
-
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
869
|
-
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
### 2. Use IAM Roles for S3 (When Possible)
|
|
875
|
-
|
|
876
|
-
If running on AWS infrastructure (EC2, Lambda), use IAM roles instead of access keys:
|
|
877
|
-
|
|
878
|
-
```typescript
|
|
879
|
-
// Best: IAM Role (no credentials needed)
|
|
880
|
-
const s3DataSource = new S3DataSource({
|
|
881
|
-
type: 'S3_CSV',
|
|
882
|
-
s3Config: {
|
|
883
|
-
bucket: 'my-bucket',
|
|
884
|
-
region: 'us-east-1'
|
|
885
|
-
// No accessKeyId/secretAccessKey - uses IAM role
|
|
886
|
-
}
|
|
887
|
-
}, logger);
|
|
888
|
-
```
|
|
889
|
-
|
|
890
|
-
### 3. Use SSH Keys for SFTP (Not Passwords)
|
|
891
|
-
|
|
892
|
-
```typescript
|
|
893
|
-
// ⚠️ Password authentication (less secure)
|
|
894
|
-
sftpConfig: {
|
|
895
|
-
username: 'user',
|
|
896
|
-
password: 'password123' // Can be brute-forced
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// ✅ SSH Key authentication (more secure)
|
|
900
|
-
sftpConfig: {
|
|
901
|
-
username: 'user',
|
|
902
|
-
privateKey: ctx.activation?.getVariable('sftpPrivateKey') as string,
|
|
903
|
-
passphrase: ctx.activation?.getVariable('sftpPassphrase') as string
|
|
904
|
-
}
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
### 4. Principle of Least Privilege
|
|
908
|
-
|
|
909
|
-
**S3 IAM Policy (Minimal Permissions):**
|
|
910
|
-
|
|
911
|
-
```json
|
|
912
|
-
{
|
|
913
|
-
"Version": "2012-10-17",
|
|
914
|
-
"Statement": [
|
|
915
|
-
{
|
|
916
|
-
"Effect": "Allow",
|
|
917
|
-
"Action": [
|
|
918
|
-
"s3:GetObject",
|
|
919
|
-
"s3:PutObject",
|
|
920
|
-
"s3:ListBucket"
|
|
921
|
-
],
|
|
922
|
-
"Resource": [
|
|
923
|
-
"arn:aws:s3:::my-inventory-bucket",
|
|
924
|
-
"arn:aws:s3:::my-inventory-bucket/*"
|
|
925
|
-
]
|
|
926
|
-
}
|
|
927
|
-
]
|
|
928
|
-
}
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
**SFTP User Permissions:**
|
|
932
|
-
- Read-only for ingestion workflows
|
|
933
|
-
- Write-only for extraction workflows
|
|
934
|
-
- Restrict to specific directories via chroot
|
|
935
|
-
|
|
936
|
-
### 5. Credential Rotation
|
|
937
|
-
|
|
938
|
-
**With Versori Connections (Easy):**
|
|
939
|
-
1. Update connection in Versori UI
|
|
940
|
-
2. All workflows automatically use new credentials
|
|
941
|
-
3. Zero code changes needed
|
|
942
|
-
|
|
943
|
-
**With Activation Variables (Manual):**
|
|
944
|
-
1. Update each workflow's activation variables individually
|
|
945
|
-
2. Redeploy each affected workflow
|
|
946
|
-
3. Risk of missed workflows
|
|
947
|
-
|
|
948
|
-
### 6. Audit Logging
|
|
949
|
-
|
|
950
|
-
Enable logging for credential usage:
|
|
951
|
-
|
|
952
|
-
```typescript
|
|
953
|
-
// Log connection info (NOT credentials)
|
|
954
|
-
log.info('Using S3 connection', {
|
|
955
|
-
connectionId: s3Connection?.id,
|
|
956
|
-
bucket: s3Connection?.params?.bucket,
|
|
957
|
-
region: s3Connection?.params?.region,
|
|
958
|
-
// ❌ NEVER log: accessKeyId, secretAccessKey
|
|
959
|
-
});
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
### 7. Environment Separation
|
|
963
|
-
|
|
964
|
-
Use different connections/variables per environment:
|
|
965
|
-
|
|
966
|
-
```
|
|
967
|
-
Development:
|
|
968
|
-
- Connection: s3-dev
|
|
969
|
-
- Variables: s3BucketName=dev-inventory
|
|
970
|
-
|
|
971
|
-
Staging:
|
|
972
|
-
- Connection: s3-staging
|
|
973
|
-
- Variables: s3BucketName=staging-inventory
|
|
974
|
-
|
|
975
|
-
Production:
|
|
976
|
-
- Connection: s3-production
|
|
977
|
-
- Variables: s3BucketName=prod-inventory
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
---
|
|
981
|
-
|
|
982
|
-
## Migration Guide
|
|
983
|
-
|
|
984
|
-
### From Activation Variables → Versori Connections
|
|
985
|
-
|
|
986
|
-
**Step 1: Create Connection**
|
|
987
|
-
|
|
988
|
-
Extract current activation variables and create Versori connection:
|
|
989
|
-
|
|
990
|
-
**Current Activation Variables:**
|
|
991
|
-
```json
|
|
992
|
-
{
|
|
993
|
-
"s3BucketName": "my-inventory-bucket",
|
|
994
|
-
"awsRegion": "us-east-1",
|
|
995
|
-
"awsAccessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
996
|
-
"awsSecretAccessKey": "wJalrXUtnFEMI..."
|
|
997
|
-
}
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
**New Versori Connection:**
|
|
1001
|
-
- Name: `s3-production`
|
|
1002
|
-
- Type: AWS S3
|
|
1003
|
-
- Access Key ID: `AKIAIOSFODNN7EXAMPLE`
|
|
1004
|
-
- Secret Access Key: `wJalrXUtnFEMI...`
|
|
1005
|
-
- Region: `us-east-1`
|
|
1006
|
-
- Parameters: `{ "bucket": "my-inventory-bucket" }`
|
|
1007
|
-
|
|
1008
|
-
**Step 2: Update Workflow Code**
|
|
1009
|
-
|
|
1010
|
-
**Before (Activation Variables):**
|
|
1011
|
-
```typescript
|
|
1012
|
-
export const sync = schedule('daily', '0 2 * * *').then(
|
|
1013
|
-
http('process', {
|
|
1014
|
-
connection: 'fluent_commerce'
|
|
1015
|
-
}, async (ctx) => {
|
|
1016
|
-
const s3 = new S3DataSource({
|
|
1017
|
-
type: 'S3_CSV',
|
|
1018
|
-
s3Config: {
|
|
1019
|
-
bucket: ctx.activation?.getVariable('s3BucketName') as string,
|
|
1020
|
-
region: ctx.activation?.getVariable('awsRegion') as string,
|
|
1021
|
-
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
1022
|
-
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string
|
|
1023
|
-
}
|
|
1024
|
-
}, ctx.log);
|
|
1025
|
-
// ... rest of code
|
|
1026
|
-
})
|
|
1027
|
-
);
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
**After (Versori Connection):**
|
|
1031
|
-
```typescript
|
|
1032
|
-
export const sync = schedule('daily', '0 2 * * *').then(
|
|
1033
|
-
http('process', {
|
|
1034
|
-
connection: 'fluent_commerce',
|
|
1035
|
-
additionalConnections: {
|
|
1036
|
-
s3: 's3-production' // ← Add this
|
|
1037
|
-
}
|
|
1038
|
-
}, async (ctx) => {
|
|
1039
|
-
const s3Conn = ctx.activation?.connections?.s3;
|
|
1040
|
-
const s3 = new S3DataSource({
|
|
1041
|
-
type: 'S3_CSV',
|
|
1042
|
-
s3Config: {
|
|
1043
|
-
bucket: s3Conn?.params?.bucket,
|
|
1044
|
-
region: s3Conn?.params?.region,
|
|
1045
|
-
accessKeyId: s3Conn?.credentials?.accessKeyId,
|
|
1046
|
-
secretAccessKey: s3Conn?.credentials?.secretAccessKey
|
|
1047
|
-
}
|
|
1048
|
-
}, ctx.log);
|
|
1049
|
-
// ... rest of code (unchanged)
|
|
1050
|
-
})
|
|
1051
|
-
);
|
|
1052
|
-
```
|
|
1053
|
-
|
|
1054
|
-
**Step 3: Test & Deploy**
|
|
1055
|
-
|
|
1056
|
-
```bash
|
|
1057
|
-
# Test locally
|
|
1058
|
-
versori dev
|
|
1059
|
-
|
|
1060
|
-
# Deploy to production
|
|
1061
|
-
versori deploy
|
|
1062
|
-
|
|
1063
|
-
# Verify logs
|
|
1064
|
-
versori logs --workflow daily
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
**Step 4: Remove Old Activation Variables**
|
|
1068
|
-
|
|
1069
|
-
After verifying the connection works, remove the old activation variables from workflow settings.
|
|
1070
|
-
|
|
1071
|
-
---
|
|
1072
|
-
|
|
1073
|
-
## Troubleshooting
|
|
1074
|
-
|
|
1075
|
-
### Issue 1: "Connection not found"
|
|
1076
|
-
|
|
1077
|
-
**Error:**
|
|
1078
|
-
```
|
|
1079
|
-
Error: Connection 's3-production' not found
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
**Causes:**
|
|
1083
|
-
- Typo in connection name
|
|
1084
|
-
- Connection not created in Versori UI
|
|
1085
|
-
- Connection not added to `additionalConnections`
|
|
1086
|
-
|
|
1087
|
-
**Solution:**
|
|
1088
|
-
```typescript
|
|
1089
|
-
// 1. Verify connection exists in Versori UI
|
|
1090
|
-
// 2. Check name matches exactly (case-sensitive)
|
|
1091
|
-
additionalConnections: {
|
|
1092
|
-
s3: 's3-production' // ← Must match UI name exactly
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// 3. Add fallback
|
|
1096
|
-
const s3Conn = ctx.activation?.connections?.s3;
|
|
1097
|
-
if (!s3Conn) {
|
|
1098
|
-
log.error('S3 connection not found');
|
|
1099
|
-
return { success: false, error: 'S3 connection required' };
|
|
1100
|
-
}
|
|
1101
|
-
```
|
|
1102
|
-
|
|
1103
|
-
---
|
|
1104
|
-
|
|
1105
|
-
### Issue 2: "Missing activation variable"
|
|
1106
|
-
|
|
1107
|
-
**Error:**
|
|
1108
|
-
```
|
|
1109
|
-
Missing required activation variables: awsAccessKeyId
|
|
1110
|
-
```
|
|
1111
|
-
|
|
1112
|
-
**Causes:**
|
|
1113
|
-
- Variable not defined in workflow settings
|
|
1114
|
-
- Typo in variable name
|
|
1115
|
-
- Variable name case mismatch
|
|
1116
|
-
|
|
1117
|
-
**Solution:**
|
|
1118
|
-
```typescript
|
|
1119
|
-
// 1. Verify activation variable exists in Versori UI
|
|
1120
|
-
// 2. Check exact name (case-sensitive)
|
|
1121
|
-
const accessKeyId = ctx.activation?.getVariable('awsAccessKeyId') as string;
|
|
1122
|
-
// ^^^^^^^^^^^^^^^
|
|
1123
|
-
// Must match UI exactly
|
|
1124
|
-
|
|
1125
|
-
// 3. Add validation with helpful error
|
|
1126
|
-
if (!accessKeyId) {
|
|
1127
|
-
log.error('Missing activation variable: awsAccessKeyId');
|
|
1128
|
-
log.info('Available variables:', Object.keys(ctx.activation?.variables || {}));
|
|
1129
|
-
throw new Error('Configuration error: awsAccessKeyId required');
|
|
1130
|
-
}
|
|
1131
|
-
```
|
|
1132
|
-
|
|
1133
|
-
---
|
|
1134
|
-
|
|
1135
|
-
### Issue 3: S3 Access Denied
|
|
1136
|
-
|
|
1137
|
-
**Error:**
|
|
1138
|
-
```
|
|
1139
|
-
AccessDenied: User: arn:aws:iam::123456789012:user/app-user is not authorized to perform: s3:ListBucket
|
|
1140
|
-
```
|
|
1141
|
-
|
|
1142
|
-
**Causes:**
|
|
1143
|
-
- Insufficient IAM permissions
|
|
1144
|
-
- Wrong bucket name
|
|
1145
|
-
- Incorrect region
|
|
1146
|
-
|
|
1147
|
-
**Solution:**
|
|
1148
|
-
```typescript
|
|
1149
|
-
// 1. Verify IAM permissions include required actions:
|
|
1150
|
-
// - s3:ListBucket (for listFiles)
|
|
1151
|
-
// - s3:GetObject (for downloadFile)
|
|
1152
|
-
// - s3:PutObject (for uploadFile)
|
|
1153
|
-
|
|
1154
|
-
// 2. Test connection with minimal operation
|
|
1155
|
-
try {
|
|
1156
|
-
const files = await s3DataSource.listFiles({ prefix: '', maxKeys: 1 });
|
|
1157
|
-
log.info('S3 connection successful');
|
|
1158
|
-
} catch (error: any) {
|
|
1159
|
-
log.error('S3 access denied', {
|
|
1160
|
-
bucket: config.s3Bucket,
|
|
1161
|
-
region: config.s3Region,
|
|
1162
|
-
error: error.message
|
|
1163
|
-
});
|
|
1164
|
-
throw error;
|
|
1165
|
-
}
|
|
1166
|
-
```
|
|
1167
|
-
|
|
1168
|
-
---
|
|
1169
|
-
|
|
1170
|
-
### Issue 4: SFTP Connection Timeout
|
|
1171
|
-
|
|
1172
|
-
**Error:**
|
|
1173
|
-
```
|
|
1174
|
-
Error: Timeout while connecting to SFTP server
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
**Causes:**
|
|
1178
|
-
- Firewall blocking port 22
|
|
1179
|
-
- Incorrect host/port
|
|
1180
|
-
- Server not responding
|
|
1181
|
-
|
|
1182
|
-
**Solution:**
|
|
1183
|
-
```typescript
|
|
1184
|
-
// 1. Increase timeout
|
|
1185
|
-
sftpConfig: {
|
|
1186
|
-
timeout: 60000, // 60 seconds (default: 30s)
|
|
1187
|
-
keepaliveInterval: 10000
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
// 2. Test connection manually
|
|
1191
|
-
ssh username@sftp.partner.com -p 22
|
|
1192
|
-
|
|
1193
|
-
// 3. Check firewall rules
|
|
1194
|
-
// Ensure Versori platform can reach SFTP server
|
|
1195
|
-
|
|
1196
|
-
// 4. Add retry logic
|
|
1197
|
-
async function connectSftpWithRetry(config, maxRetries = 3) {
|
|
1198
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
1199
|
-
try {
|
|
1200
|
-
const sftp = new SftpDataSource(config, log);
|
|
1201
|
-
await sftp.listFiles({ directory: '/' });
|
|
1202
|
-
return sftp;
|
|
1203
|
-
} catch (error: any) {
|
|
1204
|
-
if (i === maxRetries - 1) throw error;
|
|
1205
|
-
const delay = 2000 * Math.pow(2, i);
|
|
1206
|
-
log.warn(`SFTP connection failed, retrying in ${delay}ms...`);
|
|
1207
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
```
|
|
1212
|
-
|
|
1213
|
-
---
|
|
1214
|
-
|
|
1215
|
-
### Issue 5: SFTP Permission Denied
|
|
1216
|
-
|
|
1217
|
-
**Error:**
|
|
1218
|
-
```
|
|
1219
|
-
Error: Permission denied: /incoming/inventory.csv
|
|
1220
|
-
```
|
|
1221
|
-
|
|
1222
|
-
**Causes:**
|
|
1223
|
-
- User lacks write permissions to directory
|
|
1224
|
-
- Directory doesn't exist
|
|
1225
|
-
- Wrong remote path
|
|
1226
|
-
|
|
1227
|
-
**Solution:**
|
|
1228
|
-
```typescript
|
|
1229
|
-
// 1. Create directory if needed (requires permissions)
|
|
1230
|
-
try {
|
|
1231
|
-
await sftpDataSource.createDirectory(remotePath);
|
|
1232
|
-
} catch (error: any) {
|
|
1233
|
-
log.warn('Could not create directory', { remotePath, error: error.message });
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// 2. Test with read operation first
|
|
1237
|
-
const files = await sftpDataSource.listFiles({ directory: '/' });
|
|
1238
|
-
log.info('SFTP readable directories:', files);
|
|
1239
|
-
|
|
1240
|
-
// 3. Verify path from SFTP root
|
|
1241
|
-
// Remote path should be absolute: /incoming/inventory.csv
|
|
1242
|
-
// NOT relative: incoming/inventory.csv
|
|
1243
|
-
```
|
|
1244
|
-
|
|
1245
|
-
---
|
|
1246
|
-
|
|
1247
|
-
### Issue 6: Connection vs Variable Confusion
|
|
1248
|
-
|
|
1249
|
-
**Problem:** Code expects connection but variables are provided (or vice versa)
|
|
1250
|
-
|
|
1251
|
-
**Solution:** Support both with fallback:
|
|
1252
|
-
|
|
1253
|
-
```typescript
|
|
1254
|
-
// Hybrid approach: Try connection first, fallback to variables
|
|
1255
|
-
const s3Bucket =
|
|
1256
|
-
ctx.activation?.connections?.s3?.params?.bucket || // Try connection
|
|
1257
|
-
ctx.activation?.getVariable('s3BucketName') as string; // Fallback to variable
|
|
1258
|
-
|
|
1259
|
-
const s3AccessKey =
|
|
1260
|
-
ctx.activation?.connections?.s3?.credentials?.accessKeyId ||
|
|
1261
|
-
ctx.activation?.getVariable('awsAccessKeyId') as string;
|
|
1262
|
-
|
|
1263
|
-
// Validate
|
|
1264
|
-
if (!s3Bucket || !s3AccessKey) {
|
|
1265
|
-
throw new Error('S3 configuration required via connection or activation variables');
|
|
1266
|
-
}
|
|
1267
|
-
```
|
|
1268
|
-
|
|
1269
|
-
---
|
|
1270
|
-
|
|
1271
|
-
### Issue 7: Incorrect Credential Access Pattern
|
|
1272
|
-
|
|
1273
|
-
**Error:**
|
|
1274
|
-
```
|
|
1275
|
-
TypeError: credentials is not a function
|
|
1276
|
-
ReferenceError: credentials is not defined
|
|
1277
|
-
```
|
|
1278
|
-
|
|
1279
|
-
**Problem:** Documentation showed importing and calling `credentials` from `@versori/run`, but this export doesn't exist.
|
|
1280
|
-
|
|
1281
|
-
**Incorrect Pattern (OLD - DO NOT USE):**
|
|
1282
|
-
```typescript
|
|
1283
|
-
// ❌ WRONG - This doesn't exist!
|
|
1284
|
-
const cred = await ctx.credentials().get('connection_name');
|
|
1285
|
-
```
|
|
1286
|
-
|
|
1287
|
-
**Correct Patterns:**
|
|
1288
|
-
|
|
1289
|
-
**Option A: Using http task with connection (Recommended):**
|
|
1290
|
-
```typescript
|
|
1291
|
-
// ✅ CORRECT - Use connectionVariables from ctx
|
|
1292
|
-
import { Buffer } from 'node:buffer';
|
|
1293
|
-
import { http } from '@versori/run';
|
|
1294
|
-
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
1295
|
-
|
|
1296
|
-
http("task", { connection: "sftp_server" }, async (ctx) => {
|
|
1297
|
-
const { connectionVariables } = ctx;
|
|
1298
|
-
|
|
1299
|
-
const sftpConfig = {
|
|
1300
|
-
host: connectionVariables.host,
|
|
1301
|
-
port: connectionVariables.port || 22,
|
|
1302
|
-
username: connectionVariables.username,
|
|
1303
|
-
password: Buffer.from(connectionVariables.password, 'base64').toString('utf-8')
|
|
1304
|
-
};
|
|
1305
|
-
|
|
1306
|
-
const sftp = new SftpDataSource({ type: 'SFTP_CSV', settings: sftpConfig }, ctx.log);
|
|
1307
|
-
});
|
|
1308
|
-
```
|
|
1309
|
-
|
|
1310
|
-
**Option B: Using ctx.connections directly:**
|
|
1311
|
-
```typescript
|
|
1312
|
-
// ✅ CORRECT - Use ctx.connections directly
|
|
1313
|
-
import { Buffer } from 'node:buffer';
|
|
1314
|
-
import { fn } from '@versori/run';
|
|
1315
|
-
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
1316
|
-
|
|
1317
|
-
fn("task", async (ctx) => {
|
|
1318
|
-
const { connections } = ctx;
|
|
1319
|
-
const conn = connections.sftp_server;
|
|
1320
|
-
|
|
1321
|
-
const sftpConfig = {
|
|
1322
|
-
host: ctx.activation.getVariable('sftpHost'),
|
|
1323
|
-
port: 22,
|
|
1324
|
-
username: conn.username || Buffer.from(conn.accessToken, 'base64').toString('utf-8').split(':')[0],
|
|
1325
|
-
password: conn.password || Buffer.from(conn.accessToken, 'base64').toString('utf-8').split(':')[1]
|
|
1326
|
-
};
|
|
1327
|
-
|
|
1328
|
-
const sftp = new SftpDataSource({ type: 'SFTP_CSV', settings: sftpConfig }, ctx.log);
|
|
1329
|
-
});
|
|
1330
|
-
```
|
|
1331
|
-
|
|
1332
|
-
**Key Points:**
|
|
1333
|
-
- ✅ No `credentials` export exists from `@versori/run`
|
|
1334
|
-
- ✅ Use `connectionVariables` (http tasks) or `ctx.connections` directly
|
|
1335
|
-
- ✅ Always import `Buffer` from `node:buffer` when decoding base64
|
|
1336
|
-
- ✅ Both patterns are valid - choose based on task type
|
|
1337
|
-
|
|
1338
|
-
**Migration Guide:**
|
|
1339
|
-
1. Remove `credentials` from import statement
|
|
1340
|
-
2. Add `import { Buffer } from 'node:buffer'`
|
|
1341
|
-
3. Replace `ctx.credentials().get()` with `ctx.credentials().getAccessToken('connection_name')` for credential retrieval
|
|
1342
|
-
4. Or use `connectionVariables` directly in http tasks (recommended for http tasks)
|
|
1343
|
-
5. Or use `ctx.connections.connection_name` for direct connection access
|
|
1344
|
-
|
|
1345
|
-
---
|
|
1346
|
-
|
|
1347
|
-
## Related Documentation
|
|
1348
|
-
|
|
1349
|
-
- **[Module 5: Connection Management](./modules/platforms-versori-05-connections.md)** - Detailed Versori connection types
|
|
1350
|
-
- **[S3 CSV Inventory Ingestion](../../../01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md)** - Complete S3 workflow example
|
|
1351
|
-
- **[SFTP CSV Control Ingestion](../../../01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md)** - Complete SFTP workflow example
|
|
1352
|
-
- **[Security Best Practices](./modules/platforms-versori-08-best-practices.md)** - Comprehensive security guide
|
|
1353
|
-
|
|
1354
|
-
---
|
|
1355
|
-
|
|
1356
|
-
## Quick Reference
|
|
1357
|
-
|
|
1358
|
-
### S3 Activation Variables
|
|
1359
|
-
|
|
1360
|
-
```json
|
|
1361
|
-
{
|
|
1362
|
-
"s3BucketName": "string (required)",
|
|
1363
|
-
"awsRegion": "string (default: us-east-1)",
|
|
1364
|
-
"awsAccessKeyId": "string (required)",
|
|
1365
|
-
"awsSecretAccessKey": "string (required)",
|
|
1366
|
-
"s3Prefix": "string (optional, default: empty)"
|
|
1367
|
-
}
|
|
1368
|
-
```
|
|
1369
|
-
|
|
1370
|
-
### SFTP Activation Variables
|
|
1371
|
-
|
|
1372
|
-
```json
|
|
1373
|
-
{
|
|
1374
|
-
"sftpHost": "string (required)",
|
|
1375
|
-
"sftpPort": "number (default: 22)",
|
|
1376
|
-
"sftpUsername": "string (required)",
|
|
1377
|
-
"sftpPassword": "string (required if no privateKey)",
|
|
1378
|
-
"sftpPrivateKey": "string (required if no password)",
|
|
1379
|
-
"sftpPassphrase": "string (optional, for encrypted keys)",
|
|
1380
|
-
"sftpRemotePath": "string (default: /)",
|
|
1381
|
-
"sftpTimeout": "number (default: 30000ms)"
|
|
1382
|
-
}
|
|
1383
|
-
```
|
|
1384
|
-
|
|
1385
|
-
### Versori Connection Structure
|
|
1386
|
-
|
|
1387
|
-
```typescript
|
|
1388
|
-
interface VersoriConnection {
|
|
1389
|
-
id: string;
|
|
1390
|
-
name: string;
|
|
1391
|
-
type: string;
|
|
1392
|
-
params?: {
|
|
1393
|
-
bucket?: string;
|
|
1394
|
-
region?: string;
|
|
1395
|
-
host?: string;
|
|
1396
|
-
port?: string;
|
|
1397
|
-
remotePath?: string;
|
|
1398
|
-
timeout?: string;
|
|
1399
|
-
[key: string]: string | undefined;
|
|
1400
|
-
};
|
|
1401
|
-
credentials?: {
|
|
1402
|
-
accessKeyId?: string;
|
|
1403
|
-
secretAccessKey?: string;
|
|
1404
|
-
username?: string;
|
|
1405
|
-
password?: string;
|
|
1406
|
-
privateKey?: string;
|
|
1407
|
-
passphrase?: string;
|
|
1408
|
-
[key: string]: string | undefined;
|
|
1409
|
-
};
|
|
1410
|
-
}
|
|
1411
|
-
```
|
|
1412
|
-
|
|
1413
|
-
---
|
|
1414
|
-
|
|
1415
|
-
## Summary
|
|
1416
|
-
|
|
1417
|
-
✅ **Two configuration approaches** - Connections (production) vs Variables (development)
|
|
1418
|
-
✅ **Clear decision matrix** - When to use each approach
|
|
1419
|
-
✅ **Complete setup guides** - Step-by-step for both S3 and SFTP
|
|
1420
|
-
✅ **Side-by-side code examples** - Easy comparison
|
|
1421
|
-
✅ **Security best practices** - Never hardcode, least privilege, rotation
|
|
1422
|
-
✅ **Migration guide** - Move from variables to connections
|
|
1423
|
-
✅ **Troubleshooting** - Common issues and solutions
|
|
1424
|
-
|
|
1425
|
-
**Choose the right approach for your use case and follow security best practices!**
|
|
1
|
+
# S3 & SFTP Configuration Guide for Versori
|
|
2
|
+
|
|
3
|
+
**FC Connect SDK | Versori Platform Guide**
|
|
4
|
+
|
|
5
|
+
> **Purpose**: Learn the two ways to configure S3 and SFTP connections in Versori workflows, understand when to use each approach, and implement secure, production-ready configurations.
|
|
6
|
+
|
|
7
|
+
**Complexity**: Intermediate | **Time**: 15 minutes
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Overview](#overview)
|
|
14
|
+
- [Two Configuration Approaches](#two-configuration-approaches)
|
|
15
|
+
- [Decision Matrix](#decision-matrix)
|
|
16
|
+
- [Approach 1: Versori Connections](#approach-1-versori-connections-recommended-for-production)
|
|
17
|
+
- [Approach 2: Activation Variables](#approach-2-activation-variables-development-simple-use-cases)
|
|
18
|
+
- [Side-by-Side Code Examples](#side-by-side-code-examples)
|
|
19
|
+
- [Security Best Practices](#security-best-practices)
|
|
20
|
+
- [Migration Guide](#migration-guide)
|
|
21
|
+
- [Troubleshooting](#troubleshooting)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
When building Versori workflows that interact with **S3** or **SFTP** servers, you have **two ways** to provide credentials and configuration:
|
|
28
|
+
|
|
29
|
+
| Approach | What It Is | Best For |
|
|
30
|
+
|----------|-----------|----------|
|
|
31
|
+
| **Versori Connections** | Centralized credential management in Versori UI | Production, multi-workflow setups, team collaboration |
|
|
32
|
+
| **Activation Variables** | Per-workflow environment variables | Development, prototyping, single-use workflows |
|
|
33
|
+
|
|
34
|
+
**Both approaches work with FC Connect SDK** - you choose based on your requirements.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Two Configuration Approaches
|
|
39
|
+
|
|
40
|
+
### Quick Comparison
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// ❶ VERSORI CONNECTION (Centralized)
|
|
44
|
+
const s3 = new S3DataSource({
|
|
45
|
+
type: 'S3_CSV',
|
|
46
|
+
connectionId: 's3-production', // References centralized connection
|
|
47
|
+
name: 'Production S3',
|
|
48
|
+
s3Config: {
|
|
49
|
+
bucket: ctx.activation?.connection?.params?.bucket,
|
|
50
|
+
region: ctx.activation?.connection?.params?.region,
|
|
51
|
+
credentials: ctx.activation?.connection?.credentials
|
|
52
|
+
}
|
|
53
|
+
}, logger);
|
|
54
|
+
|
|
55
|
+
// ❷ ACTIVATION VARIABLES (Per-workflow)
|
|
56
|
+
const s3 = new S3DataSource({
|
|
57
|
+
type: 'S3_CSV',
|
|
58
|
+
connectionId: 'inline-config',
|
|
59
|
+
name: 'S3 Data Source',
|
|
60
|
+
s3Config: {
|
|
61
|
+
bucket: ctx.activation?.getVariable('s3BucketName') as string,
|
|
62
|
+
region: ctx.activation?.getVariable('awsRegion') as string,
|
|
63
|
+
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
64
|
+
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string
|
|
65
|
+
}
|
|
66
|
+
}, logger);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Key Difference:**
|
|
70
|
+
- **Connections** → Credentials stored once in Versori, referenced by name
|
|
71
|
+
- **Variables** → Credentials stored per-workflow as activation variables
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Decision Matrix
|
|
76
|
+
|
|
77
|
+
Use this table to choose the right approach:
|
|
78
|
+
|
|
79
|
+
| Factor | Versori Connections | Activation Variables |
|
|
80
|
+
|--------|---------------------|---------------------|
|
|
81
|
+
| **Setup Complexity** | ⚠️ Medium (UI setup required) | ✅ Simple (just add variables) |
|
|
82
|
+
| **Credential Security** | ✅✅ Centralized, encrypted, audited | ⚠️ Duplicated across workflows |
|
|
83
|
+
| **Reusability** | ✅✅ Share across multiple workflows | ❌ One workflow only |
|
|
84
|
+
| **Credential Rotation** | ✅✅ Update once, affects all workflows | ❌ Update every workflow individually |
|
|
85
|
+
| **Team Collaboration** | ✅✅ Central management, access control | ⚠️ Each dev needs credentials |
|
|
86
|
+
| **Development Speed** | ⚠️ Slower (create connection first) | ✅ Faster (directly add variables) |
|
|
87
|
+
| **Best For** | **Production environments** | **Development, prototyping, POC** |
|
|
88
|
+
| **SDK Support** | ✅ Full support | ✅ Full support |
|
|
89
|
+
|
|
90
|
+
### 🎯 Recommendations
|
|
91
|
+
|
|
92
|
+
**Use Versori Connections when:**
|
|
93
|
+
- ✅ Deploying to production
|
|
94
|
+
- ✅ Multiple workflows use the same S3 bucket or SFTP server
|
|
95
|
+
- ✅ Working in a team (shared credential management)
|
|
96
|
+
- ✅ Security/compliance requires centralized credential storage
|
|
97
|
+
- ✅ Credentials need to be rotated regularly
|
|
98
|
+
|
|
99
|
+
**Use Activation Variables when:**
|
|
100
|
+
- ✅ Prototyping or proof-of-concept
|
|
101
|
+
- ✅ One-off data migration or testing
|
|
102
|
+
- ✅ Each workflow needs different S3/SFTP credentials
|
|
103
|
+
- ✅ Local development or sandbox environments
|
|
104
|
+
- ✅ Quick iteration without UI configuration
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Approach 1: Versori Connections (Recommended for Production)
|
|
109
|
+
|
|
110
|
+
### Benefits
|
|
111
|
+
|
|
112
|
+
✅ **Centralized Management** - One place to manage credentials
|
|
113
|
+
✅ **Reusable** - Reference the same connection across multiple workflows
|
|
114
|
+
✅ **Secure** - Encrypted storage with access control
|
|
115
|
+
✅ **Auditable** - Track who uses which credentials
|
|
116
|
+
✅ **Easy Rotation** - Update once, all workflows inherit changes
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Credential Management: Connection vs Activation
|
|
121
|
+
|
|
122
|
+
**IMPORTANT:** There are **two secure approaches** to fetch credentials - choose based on your security requirements.
|
|
123
|
+
|
|
124
|
+
**⚠️ Versori/Deno Runtime Note:** When decoding Base64 credentials, always import Buffer:
|
|
125
|
+
```typescript
|
|
126
|
+
import { Buffer } from 'node:buffer';
|
|
127
|
+
```
|
|
128
|
+
This is required in Deno/Versori runtime (unlike Node.js where Buffer is global).
|
|
129
|
+
|
|
130
|
+
### Approach 1A: Fetch from Versori Connection (Most Secure - Production)
|
|
131
|
+
|
|
132
|
+
**Why Use This:**
|
|
133
|
+
- ✅ **Most secure** - Credentials stored in Versori connection vault, not activation variables
|
|
134
|
+
- ✅ **Centralized management** - Update connection in one place
|
|
135
|
+
- ✅ **Access control** - Versori manages who can access credentials
|
|
136
|
+
- ✅ **Audit trail** - Connection usage is logged by platform
|
|
137
|
+
- ✅ **Production-ready** - Best practice for deployed workflows
|
|
138
|
+
|
|
139
|
+
**How It Works:**
|
|
140
|
+
1. **Create Basic Auth connection in Versori:**
|
|
141
|
+
- Connection Name: `versori_ftp_server`
|
|
142
|
+
- Type: Basic Auth
|
|
143
|
+
- Username: your-sftp-username
|
|
144
|
+
- Password: your-sftp-password
|
|
145
|
+
|
|
146
|
+
2. **Versori encodes credentials:**
|
|
147
|
+
- Format: `username:password`
|
|
148
|
+
- Encoding: Base64
|
|
149
|
+
- Stored as `accessToken`
|
|
150
|
+
|
|
151
|
+
3. **Your code decodes:**
|
|
152
|
+
- `ctx.connections.connection_name.accessToken` → get base64 token from connection object
|
|
153
|
+
- Decode base64 → get `username:password` string
|
|
154
|
+
- Split by `:` → extract username and password
|
|
155
|
+
|
|
156
|
+
**Example: SFTP with Connection Credentials**
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { Buffer } from 'node:buffer';
|
|
160
|
+
import { schedule, http } from '@versori/run';
|
|
161
|
+
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
162
|
+
|
|
163
|
+
export const sftpIngestion = schedule('sftp-sync', '0 2 * * *').then(
|
|
164
|
+
http('process-sftp-files', {
|
|
165
|
+
connection: 'fluent_commerce'
|
|
166
|
+
}, async (ctx) => {
|
|
167
|
+
const { log, activation } = ctx;
|
|
168
|
+
|
|
169
|
+
// ✅ APPROACH 1A: Fetch credentials from Versori connection (SECURE - Production)
|
|
170
|
+
// IMPORTANT: Access connections directly via ctx.connections (Versori platform pattern)
|
|
171
|
+
const { connections } = ctx;
|
|
172
|
+
const conn = connections.versori_ftp_server;
|
|
173
|
+
const rawAccessToken = conn.accessToken;
|
|
174
|
+
|
|
175
|
+
// Decode base64-encoded "username:password"
|
|
176
|
+
const rawBasicAuth = Buffer.from(rawAccessToken, 'base64').toString('utf-8');
|
|
177
|
+
const [username, password] = rawBasicAuth.split(':');
|
|
178
|
+
|
|
179
|
+
// Initialize SftpDataSource with connection credentials
|
|
180
|
+
const sftp = new SftpDataSource({
|
|
181
|
+
type: 'SFTP_CSV',
|
|
182
|
+
connectionId: 'versori_ftp_server',
|
|
183
|
+
name: 'FTP Server',
|
|
184
|
+
settings: {
|
|
185
|
+
host: activation.getVariable('sftpHost'),
|
|
186
|
+
port: parseInt(activation.getVariable('sftpPort') || '22'),
|
|
187
|
+
username, // ← From connection (secure)
|
|
188
|
+
password, // ← From connection (secure)
|
|
189
|
+
remotePath: '/incoming',
|
|
190
|
+
filePattern: '*.csv'
|
|
191
|
+
}
|
|
192
|
+
}, log);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Use SFTP normally
|
|
196
|
+
const files = await sftp.listFiles({ directory: '/incoming' });
|
|
197
|
+
log.info(`Found ${files.length} files`);
|
|
198
|
+
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
const content = await sftp.downloadFile(file.path, { encoding: 'utf8' });
|
|
201
|
+
log.info('File downloaded', { file: file.name, size: content.length });
|
|
202
|
+
// Process file...
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { success: true, filesProcessed: files.length };
|
|
206
|
+
} finally {
|
|
207
|
+
await sftp.disconnect();
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Approach 1B: Activation Variables (Development/Testing)
|
|
214
|
+
|
|
215
|
+
**When Use This:**
|
|
216
|
+
- Development/testing environments
|
|
217
|
+
- Quick prototyping
|
|
218
|
+
- When connection setup is not yet complete
|
|
219
|
+
|
|
220
|
+
**Example: SFTP with Activation Variables**
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { Buffer } from 'node:buffer';
|
|
224
|
+
import { schedule, http } from '@versori/run';
|
|
225
|
+
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
226
|
+
|
|
227
|
+
export const sftpIngestion = schedule('sftp-sync', '0 2 * * *').then(
|
|
228
|
+
http('process-sftp-files', {
|
|
229
|
+
connection: 'fluent_commerce'
|
|
230
|
+
}, async (ctx) => {
|
|
231
|
+
const { log, activation } = ctx;
|
|
232
|
+
|
|
233
|
+
// ✅ APPROACH 1B: Activation variables (Development/Testing)
|
|
234
|
+
const sftp = new SftpDataSource({
|
|
235
|
+
type: 'SFTP_CSV',
|
|
236
|
+
connectionId: 'sftp-server',
|
|
237
|
+
name: 'FTP Server',
|
|
238
|
+
settings: {
|
|
239
|
+
host: activation.getVariable('sftpHost'),
|
|
240
|
+
port: parseInt(activation.getVariable('sftpPort') || '22'),
|
|
241
|
+
username: activation.getVariable('sftpUsername'), // ← From activation
|
|
242
|
+
password: activation.getVariable('sftpPassword'), // ← From activation
|
|
243
|
+
remotePath: '/incoming',
|
|
244
|
+
filePattern: '*.csv'
|
|
245
|
+
}
|
|
246
|
+
}, log);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const files = await sftp.listFiles({ directory: '/incoming' });
|
|
250
|
+
log.info(`Found ${files.length} files`);
|
|
251
|
+
return { success: true, filesProcessed: files.length };
|
|
252
|
+
} finally {
|
|
253
|
+
await sftp.disconnect();
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Comparison: Connection vs Activation
|
|
260
|
+
|
|
261
|
+
| Factor | Connection (1A) | Activation (1B) |
|
|
262
|
+
|--------|----------------|-----------------|
|
|
263
|
+
| **Security** | ✅✅ Vault storage | ⚠️ Variable storage |
|
|
264
|
+
| **Setup Complexity** | ⚠️ Requires connection creation | ✅ Quick variable setup |
|
|
265
|
+
| **Credential Rotation** | ✅✅ Update once | ❌ Update each workflow |
|
|
266
|
+
| **Team Collaboration** | ✅✅ Central management | ⚠️ Each dev needs creds |
|
|
267
|
+
| **Audit Trail** | ✅✅ Platform-logged | ⚠️ Variable access only |
|
|
268
|
+
| **Best For** | **Production** | **Development/Testing** |
|
|
269
|
+
|
|
270
|
+
### Complete Working Example: Hybrid Approach
|
|
271
|
+
|
|
272
|
+
**Support both methods with fallback for maximum flexibility:**
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { Buffer } from 'node:buffer';
|
|
276
|
+
import { schedule, http } from '@versori/run';
|
|
277
|
+
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
278
|
+
|
|
279
|
+
export const sftpIngestion = schedule('sftp-sync', '0 2 * * *').then(
|
|
280
|
+
http('process-sftp-files', {
|
|
281
|
+
connection: 'fluent_commerce'
|
|
282
|
+
}, async (ctx) => {
|
|
283
|
+
const { log, activation } = ctx;
|
|
284
|
+
|
|
285
|
+
// Try connection-based credentials first (production), fallback to activation (development)
|
|
286
|
+
let username: string;
|
|
287
|
+
let password: string;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
// ✅ PRIMARY: Try connection credentials (most secure)
|
|
291
|
+
const { connections } = ctx;
|
|
292
|
+
const conn = connections.versori_ftp_server;
|
|
293
|
+
const rawAccessToken = conn.accessToken;
|
|
294
|
+
const rawBasicAuth = Buffer.from(rawAccessToken, 'base64').toString('utf-8');
|
|
295
|
+
[username, password] = rawBasicAuth.split(':');
|
|
296
|
+
|
|
297
|
+
log.info('Using connection credentials for SFTP');
|
|
298
|
+
} catch (error) {
|
|
299
|
+
// ⚠️ FALLBACK: Use activation variables (development/testing)
|
|
300
|
+
username = activation.getVariable('sftpUsername');
|
|
301
|
+
password = activation.getVariable('sftpPassword');
|
|
302
|
+
|
|
303
|
+
log.warn('Connection credentials unavailable, using activation variables');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Validate credentials are available
|
|
307
|
+
if (!username || !password) {
|
|
308
|
+
throw new Error('SFTP credentials not found in connection or activation variables');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Initialize SftpDataSource
|
|
312
|
+
const sftp = new SftpDataSource({
|
|
313
|
+
type: 'SFTP_CSV',
|
|
314
|
+
connectionId: 'versori_ftp_server',
|
|
315
|
+
name: 'FTP Server',
|
|
316
|
+
settings: {
|
|
317
|
+
host: activation.getVariable('sftpHost'),
|
|
318
|
+
port: parseInt(activation.getVariable('sftpPort') || '22'),
|
|
319
|
+
username,
|
|
320
|
+
password,
|
|
321
|
+
remotePath: '/incoming',
|
|
322
|
+
filePattern: '*.csv'
|
|
323
|
+
}
|
|
324
|
+
}, log);
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const files = await sftp.listFiles({ directory: '/incoming' });
|
|
328
|
+
log.info(`Found ${files.length} files`);
|
|
329
|
+
return { success: true, filesProcessed: files.length };
|
|
330
|
+
} finally {
|
|
331
|
+
await sftp.disconnect();
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### Setup: AWS S3 Connection
|
|
340
|
+
|
|
341
|
+
#### Step 1: Create Connection in Versori UI
|
|
342
|
+
|
|
343
|
+
1. **Navigate to Versori Dashboard** → **Connections** tab
|
|
344
|
+
2. Click **"Add Connection"**
|
|
345
|
+
3. **Configure Connection:**
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
Name: s3-production
|
|
349
|
+
Description: Production S3 bucket for inventory data
|
|
350
|
+
Type: AWS S3 (or API Key if S3 not available)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
4. **Authentication Details:**
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
AWS Access Key ID: AKIAIOSFODNN7EXAMPLE
|
|
357
|
+
AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
358
|
+
Region: us-east-1
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
5. **Additional Parameters (Optional):**
|
|
362
|
+
|
|
363
|
+
```json
|
|
364
|
+
{
|
|
365
|
+
"bucket": "my-production-inventory",
|
|
366
|
+
"prefix": "inventory/incoming/"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
6. **Test Connection** → Verify access
|
|
371
|
+
7. **Save Connection**
|
|
372
|
+
|
|
373
|
+
#### Step 2: Use Connection in Workflow Code
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { schedule, http } from '@versori/run';
|
|
377
|
+
import { createClient, S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
378
|
+
|
|
379
|
+
export const inventorySync = schedule('daily-sync', '0 2 * * *').then(
|
|
380
|
+
http('process-s3-files', {
|
|
381
|
+
connection: 'fluent_commerce', // Fluent connection
|
|
382
|
+
additionalConnections: {
|
|
383
|
+
s3: 's3-production' // ← Reference S3 connection by name
|
|
384
|
+
}
|
|
385
|
+
}, async (ctx) => {
|
|
386
|
+
const log = ctx.log;
|
|
387
|
+
|
|
388
|
+
// Access S3 connection
|
|
389
|
+
const s3Connection = ctx.activation?.connections?.s3;
|
|
390
|
+
|
|
391
|
+
// Initialize S3DataSource with connection credentials
|
|
392
|
+
const s3DataSource = new S3DataSource({
|
|
393
|
+
type: 'S3_CSV',
|
|
394
|
+
connectionId: s3Connection?.id || 's3-production',
|
|
395
|
+
name: 'Production S3',
|
|
396
|
+
s3Config: {
|
|
397
|
+
bucket: s3Connection?.params?.bucket || ctx.activation?.getVariable('s3BucketName') as string,
|
|
398
|
+
region: s3Connection?.params?.region || 'us-east-1',
|
|
399
|
+
accessKeyId: s3Connection?.credentials?.accessKeyId,
|
|
400
|
+
secretAccessKey: s3Connection?.credentials?.secretAccessKey
|
|
401
|
+
}
|
|
402
|
+
}, log);
|
|
403
|
+
|
|
404
|
+
// Use S3DataSource normally
|
|
405
|
+
const files = await s3DataSource.listFiles({ prefix: 'inventory/' });
|
|
406
|
+
log.info(`Found ${files.length} files`);
|
|
407
|
+
|
|
408
|
+
return { success: true, filesFound: files.length };
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Key Points:**
|
|
414
|
+
- ✅ `additionalConnections` parameter references the connection name
|
|
415
|
+
- ✅ Access via `ctx.activation?.connections?.s3`
|
|
416
|
+
- ✅ Fallback to activation variables for flexibility
|
|
417
|
+
- ✅ No credentials in code
|
|
418
|
+
|
|
419
|
+
#### Step 3: Test & Deploy
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# Deploy workflow
|
|
423
|
+
versori deploy
|
|
424
|
+
|
|
425
|
+
# Verify connection
|
|
426
|
+
versori logs --workflow daily-sync
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### Setup: SFTP Connection
|
|
432
|
+
|
|
433
|
+
#### Step 1: Create Connection in Versori UI
|
|
434
|
+
|
|
435
|
+
1. **Navigate to** → **Connections** → **"Add Connection"**
|
|
436
|
+
2. **Configure:**
|
|
437
|
+
|
|
438
|
+
```
|
|
439
|
+
Name: sftp-partner-server
|
|
440
|
+
Description: Partner SFTP server for exports
|
|
441
|
+
Type: SFTP (or Custom if not available)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
3. **SFTP Details:**
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
Host: sftp.partner.com
|
|
448
|
+
Port: 22
|
|
449
|
+
Username: inventory_export
|
|
450
|
+
Authentication: Password (or SSH Key)
|
|
451
|
+
Password: ******** (or upload private key)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
4. **Additional Parameters:**
|
|
455
|
+
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"remotePath": "/incoming/inventory/",
|
|
459
|
+
"timeout": 30000,
|
|
460
|
+
"keepalive": 10000
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
5. **Test Connection** → **Save**
|
|
465
|
+
|
|
466
|
+
#### Step 2: Use Connection in Workflow
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import { schedule, http } from '@versori/run';
|
|
470
|
+
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
471
|
+
|
|
472
|
+
export const sftpExport = schedule('daily-export', '0 3 * * *').then(
|
|
473
|
+
http('export-to-sftp', {
|
|
474
|
+
connection: 'fluent_commerce',
|
|
475
|
+
additionalConnections: {
|
|
476
|
+
sftp: 'sftp-partner-server' // ← Reference SFTP connection
|
|
477
|
+
}
|
|
478
|
+
}, async (ctx) => {
|
|
479
|
+
const log = ctx.log;
|
|
480
|
+
|
|
481
|
+
// Access SFTP connection
|
|
482
|
+
const sftpConnection = ctx.activation?.connections?.sftp;
|
|
483
|
+
|
|
484
|
+
// Initialize SftpDataSource with connection credentials
|
|
485
|
+
const sftpDataSource = new SftpDataSource({
|
|
486
|
+
type: 'SFTP_CSV',
|
|
487
|
+
connectionId: sftpConnection?.id || 'sftp-partner',
|
|
488
|
+
name: 'Partner SFTP',
|
|
489
|
+
sftpConfig: {
|
|
490
|
+
host: sftpConnection?.params?.host || ctx.activation?.getVariable('sftpHost') as string,
|
|
491
|
+
port: parseInt(sftpConnection?.params?.port || '22'),
|
|
492
|
+
username: sftpConnection?.credentials?.username,
|
|
493
|
+
password: sftpConnection?.credentials?.password,
|
|
494
|
+
privateKey: sftpConnection?.credentials?.privateKey,
|
|
495
|
+
timeout: parseInt(sftpConnection?.params?.timeout || '30000'),
|
|
496
|
+
keepaliveInterval: parseInt(sftpConnection?.params?.keepalive || '10000')
|
|
497
|
+
}
|
|
498
|
+
}, log);
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
// Upload file to SFTP
|
|
502
|
+
const csvData = 'sku,qty\nSKU-001,100\n';
|
|
503
|
+
await sftpDataSource.uploadFile(
|
|
504
|
+
'/incoming/inventory/inventory-export.csv',
|
|
505
|
+
Buffer.from(csvData, 'utf-8'),
|
|
506
|
+
{ encoding: 'utf-8' }
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
log.info('File uploaded successfully');
|
|
510
|
+
return { success: true };
|
|
511
|
+
} finally {
|
|
512
|
+
// Always disconnect SFTP
|
|
513
|
+
await sftpDataSource.disconnect();
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
);
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**Key Points:**
|
|
520
|
+
- ✅ `additionalConnections.sftp` references connection
|
|
521
|
+
- ✅ Support for password OR SSH key authentication
|
|
522
|
+
- ✅ Always call `disconnect()` in finally block
|
|
523
|
+
- ✅ Timeout and keep-alive from connection config
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Approach 2: Activation Variables (Development & Simple Use Cases)
|
|
528
|
+
|
|
529
|
+
### Benefits
|
|
530
|
+
|
|
531
|
+
✅ **Quick Setup** - No UI configuration needed
|
|
532
|
+
✅ **Per-Workflow Control** - Each workflow has independent config
|
|
533
|
+
✅ **Development-Friendly** - Easy to test with different credentials
|
|
534
|
+
✅ **Explicit** - All configuration visible in code
|
|
535
|
+
|
|
536
|
+
### Setup: AWS S3 with Activation Variables
|
|
537
|
+
|
|
538
|
+
#### Step 1: Define Activation Variables
|
|
539
|
+
|
|
540
|
+
In Versori UI → **Workflow Settings** → **Activation Variables**:
|
|
541
|
+
|
|
542
|
+
```json
|
|
543
|
+
{
|
|
544
|
+
"s3BucketName": "my-inventory-bucket",
|
|
545
|
+
"awsRegion": "us-east-1",
|
|
546
|
+
"awsAccessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
547
|
+
"awsSecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
548
|
+
"s3Prefix": "inventory/incoming/"
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Security Note:** These are encrypted by Versori platform.
|
|
553
|
+
|
|
554
|
+
#### Step 2: Use Variables in Code
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { schedule, http } from '@versori/run';
|
|
558
|
+
import { createClient, S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
559
|
+
|
|
560
|
+
export const inventorySync = schedule('daily-sync', '0 2 * * *').then(
|
|
561
|
+
http('process-s3-files', {
|
|
562
|
+
connection: 'fluent_commerce'
|
|
563
|
+
}, async (ctx) => {
|
|
564
|
+
const log = ctx.log;
|
|
565
|
+
|
|
566
|
+
// Load configuration from activation variables
|
|
567
|
+
const config = {
|
|
568
|
+
s3Bucket: ctx.activation?.getVariable('s3BucketName') as string,
|
|
569
|
+
s3Region: ctx.activation?.getVariable('awsRegion') as string || 'us-east-1',
|
|
570
|
+
s3AccessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
571
|
+
s3SecretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string,
|
|
572
|
+
s3Prefix: ctx.activation?.getVariable('s3Prefix') as string || 'inventory/'
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
// Validate required variables
|
|
576
|
+
const missingVars: string[] = [];
|
|
577
|
+
if (!config.s3Bucket) missingVars.push('s3BucketName');
|
|
578
|
+
if (!config.s3AccessKeyId) missingVars.push('awsAccessKeyId');
|
|
579
|
+
if (!config.s3SecretAccessKey) missingVars.push('awsSecretAccessKey');
|
|
580
|
+
|
|
581
|
+
if (missingVars.length > 0) {
|
|
582
|
+
const errorMsg = `Missing required activation variables: ${missingVars.join(', ')}`;
|
|
583
|
+
log.error(errorMsg);
|
|
584
|
+
return { success: false, error: errorMsg };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Initialize S3DataSource with activation variables
|
|
588
|
+
const s3DataSource = new S3DataSource({
|
|
589
|
+
type: 'S3_CSV',
|
|
590
|
+
connectionId: 'inline-s3-config',
|
|
591
|
+
name: 'S3 Data Source',
|
|
592
|
+
s3Config: {
|
|
593
|
+
bucket: config.s3Bucket,
|
|
594
|
+
region: config.s3Region,
|
|
595
|
+
accessKeyId: config.s3AccessKeyId,
|
|
596
|
+
secretAccessKey: config.s3SecretAccessKey
|
|
597
|
+
}
|
|
598
|
+
}, log);
|
|
599
|
+
|
|
600
|
+
// Use S3DataSource
|
|
601
|
+
const files = await s3DataSource.listFiles({ prefix: config.s3Prefix });
|
|
602
|
+
log.info(`Found ${files.length} files`);
|
|
603
|
+
|
|
604
|
+
return { success: true, filesFound: files.length };
|
|
605
|
+
})
|
|
606
|
+
);
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Key Points:**
|
|
610
|
+
- ✅ All config loaded via `ctx.activation?.getVariable()`
|
|
611
|
+
- ✅ Validation ensures required variables are present
|
|
612
|
+
- ✅ Fallback values for optional variables
|
|
613
|
+
- ✅ Clear error messages for missing config
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
### Setup: SFTP with Activation Variables
|
|
618
|
+
|
|
619
|
+
#### Step 1: Define Activation Variables
|
|
620
|
+
|
|
621
|
+
```json
|
|
622
|
+
{
|
|
623
|
+
"sftpHost": "sftp.partner.com",
|
|
624
|
+
"sftpPort": "22",
|
|
625
|
+
"sftpUsername": "inventory_export",
|
|
626
|
+
"sftpPassword": "********",
|
|
627
|
+
"sftpRemotePath": "/incoming/inventory/",
|
|
628
|
+
"sftpTimeout": "30000"
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**SSH Key Authentication (Alternative):**
|
|
633
|
+
|
|
634
|
+
```json
|
|
635
|
+
{
|
|
636
|
+
"sftpHost": "sftp.partner.com",
|
|
637
|
+
"sftpPort": "22",
|
|
638
|
+
"sftpUsername": "inventory_export",
|
|
639
|
+
"sftpPrivateKey": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----",
|
|
640
|
+
"sftpPassphrase": "keypassphrase",
|
|
641
|
+
"sftpRemotePath": "/incoming/inventory/"
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
#### Step 2: Use Variables in Code
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
import { schedule, http } from '@versori/run';
|
|
649
|
+
import { createClient, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
650
|
+
|
|
651
|
+
export const sftpExport = schedule('daily-export', '0 3 * * *').then(
|
|
652
|
+
http('export-to-sftp', {
|
|
653
|
+
connection: 'fluent_commerce'
|
|
654
|
+
}, async (ctx) => {
|
|
655
|
+
const log = ctx.log;
|
|
656
|
+
|
|
657
|
+
// Load SFTP configuration from activation variables
|
|
658
|
+
const config = {
|
|
659
|
+
sftpHost: ctx.activation?.getVariable('sftpHost') as string,
|
|
660
|
+
sftpPort: parseInt(ctx.activation?.getVariable('sftpPort') as string || '22'),
|
|
661
|
+
sftpUsername: ctx.activation?.getVariable('sftpUsername') as string,
|
|
662
|
+
sftpPassword: ctx.activation?.getVariable('sftpPassword') as string,
|
|
663
|
+
sftpPrivateKey: ctx.activation?.getVariable('sftpPrivateKey') as string,
|
|
664
|
+
sftpPassphrase: ctx.activation?.getVariable('sftpPassphrase') as string,
|
|
665
|
+
sftpRemotePath: ctx.activation?.getVariable('sftpRemotePath') as string || '/',
|
|
666
|
+
sftpTimeout: parseInt(ctx.activation?.getVariable('sftpTimeout') as string || '30000')
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// Validate required variables
|
|
670
|
+
const missingVars: string[] = [];
|
|
671
|
+
if (!config.sftpHost) missingVars.push('sftpHost');
|
|
672
|
+
if (!config.sftpUsername) missingVars.push('sftpUsername');
|
|
673
|
+
if (!config.sftpPassword && !config.sftpPrivateKey) {
|
|
674
|
+
missingVars.push('sftpPassword OR sftpPrivateKey');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (missingVars.length > 0) {
|
|
678
|
+
const errorMsg = `Missing required activation variables: ${missingVars.join(', ')}`;
|
|
679
|
+
log.error(errorMsg);
|
|
680
|
+
return { success: false, error: errorMsg };
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Initialize SftpDataSource
|
|
684
|
+
const sftpDataSource = new SftpDataSource({
|
|
685
|
+
type: 'SFTP_CSV',
|
|
686
|
+
connectionId: 'inline-sftp-config',
|
|
687
|
+
name: 'SFTP Data Source',
|
|
688
|
+
sftpConfig: {
|
|
689
|
+
host: config.sftpHost,
|
|
690
|
+
port: config.sftpPort,
|
|
691
|
+
username: config.sftpUsername,
|
|
692
|
+
password: config.sftpPassword,
|
|
693
|
+
privateKey: config.sftpPrivateKey,
|
|
694
|
+
passphrase: config.sftpPassphrase,
|
|
695
|
+
timeout: config.sftpTimeout,
|
|
696
|
+
keepaliveInterval: 10000,
|
|
697
|
+
retryDelay: 2000,
|
|
698
|
+
maxRetries: 3
|
|
699
|
+
}
|
|
700
|
+
}, log);
|
|
701
|
+
|
|
702
|
+
try {
|
|
703
|
+
// Upload file
|
|
704
|
+
const csvData = 'sku,qty\nSKU-001,100\n';
|
|
705
|
+
const remotePath = `${config.sftpRemotePath}/inventory-export-${Date.now()}.csv`;
|
|
706
|
+
|
|
707
|
+
await sftpDataSource.uploadFile(
|
|
708
|
+
remotePath,
|
|
709
|
+
Buffer.from(csvData, 'utf-8'),
|
|
710
|
+
{ encoding: 'utf-8' }
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
log.info(`File uploaded to ${remotePath}`);
|
|
714
|
+
return { success: true, remotePath };
|
|
715
|
+
} catch (error: any) {
|
|
716
|
+
log.error('SFTP upload failed', { error: error.message });
|
|
717
|
+
return { success: false, error: error.message };
|
|
718
|
+
} finally {
|
|
719
|
+
// Always disconnect
|
|
720
|
+
await sftpDataSource.disconnect();
|
|
721
|
+
}
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Key Points:**
|
|
727
|
+
- ✅ Support for password OR SSH key authentication
|
|
728
|
+
- ✅ Validation for mutually exclusive auth methods
|
|
729
|
+
- ✅ Always disconnect in finally block
|
|
730
|
+
- ✅ Comprehensive error handling
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
## Side-by-Side Code Examples
|
|
735
|
+
|
|
736
|
+
### S3DataSource: Both Approaches
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
// ═══════════════════════════════════════════════════════════════
|
|
740
|
+
// APPROACH 1: VERSORI CONNECTION
|
|
741
|
+
// ═══════════════════════════════════════════════════════════════
|
|
742
|
+
import { S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
743
|
+
|
|
744
|
+
// In workflow with additionalConnections: { s3: 's3-production' }
|
|
745
|
+
const s3Connection = ctx.activation?.connections?.s3;
|
|
746
|
+
|
|
747
|
+
const s3DataSource = new S3DataSource({
|
|
748
|
+
type: 'S3_CSV',
|
|
749
|
+
connectionId: s3Connection?.id,
|
|
750
|
+
name: 'Production S3',
|
|
751
|
+
s3Config: {
|
|
752
|
+
bucket: s3Connection?.params?.bucket, // From connection
|
|
753
|
+
region: s3Connection?.params?.region, // From connection
|
|
754
|
+
accessKeyId: s3Connection?.credentials?.accessKeyId, // From connection
|
|
755
|
+
secretAccessKey: s3Connection?.credentials?.secretAccessKey // From connection
|
|
756
|
+
}
|
|
757
|
+
}, logger);
|
|
758
|
+
|
|
759
|
+
// ═══════════════════════════════════════════════════════════════
|
|
760
|
+
// APPROACH 2: ACTIVATION VARIABLES
|
|
761
|
+
// ═══════════════════════════════════════════════════════════════
|
|
762
|
+
const s3DataSource = new S3DataSource({
|
|
763
|
+
type: 'S3_CSV',
|
|
764
|
+
connectionId: 'inline-config',
|
|
765
|
+
name: 'S3 Data Source',
|
|
766
|
+
s3Config: {
|
|
767
|
+
bucket: ctx.activation?.getVariable('s3BucketName') as string, // From variable
|
|
768
|
+
region: ctx.activation?.getVariable('awsRegion') as string, // From variable
|
|
769
|
+
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string, // From variable
|
|
770
|
+
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string // From variable
|
|
771
|
+
}
|
|
772
|
+
}, logger);
|
|
773
|
+
|
|
774
|
+
// ═══════════════════════════════════════════════════════════════
|
|
775
|
+
// USAGE: SAME FOR BOTH APPROACHES
|
|
776
|
+
// ═══════════════════════════════════════════════════════════════
|
|
777
|
+
const files = await s3DataSource.listFiles({ prefix: 'inventory/' });
|
|
778
|
+
const fileContent = await s3DataSource.downloadFile(files[0].path, { encoding: 'utf8' });
|
|
779
|
+
await s3DataSource.uploadFile('output/result.csv', Buffer.from(csvData), {});
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
### SftpDataSource: Both Approaches
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
// ═══════════════════════════════════════════════════════════════
|
|
788
|
+
// APPROACH 1: VERSORI CONNECTION
|
|
789
|
+
// ═══════════════════════════════════════════════════════════════
|
|
790
|
+
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
791
|
+
|
|
792
|
+
// In workflow with additionalConnections: { sftp: 'sftp-partner' }
|
|
793
|
+
const sftpConnection = ctx.activation?.connections?.sftp;
|
|
794
|
+
|
|
795
|
+
const sftpDataSource = new SftpDataSource({
|
|
796
|
+
type: 'SFTP_CSV',
|
|
797
|
+
connectionId: sftpConnection?.id,
|
|
798
|
+
name: 'Partner SFTP',
|
|
799
|
+
sftpConfig: {
|
|
800
|
+
host: sftpConnection?.params?.host, // From connection
|
|
801
|
+
port: parseInt(sftpConnection?.params?.port || '22'),
|
|
802
|
+
username: sftpConnection?.credentials?.username, // From connection
|
|
803
|
+
password: sftpConnection?.credentials?.password, // From connection
|
|
804
|
+
privateKey: sftpConnection?.credentials?.privateKey, // From connection
|
|
805
|
+
timeout: parseInt(sftpConnection?.params?.timeout || '30000'),
|
|
806
|
+
keepaliveInterval: 10000
|
|
807
|
+
}
|
|
808
|
+
}, logger);
|
|
809
|
+
|
|
810
|
+
// ═══════════════════════════════════════════════════════════════
|
|
811
|
+
// APPROACH 2: ACTIVATION VARIABLES
|
|
812
|
+
// ═══════════════════════════════════════════════════════════════
|
|
813
|
+
const sftpDataSource = new SftpDataSource({
|
|
814
|
+
type: 'SFTP_CSV',
|
|
815
|
+
connectionId: 'inline-config',
|
|
816
|
+
name: 'SFTP Data Source',
|
|
817
|
+
sftpConfig: {
|
|
818
|
+
host: ctx.activation?.getVariable('sftpHost') as string, // From variable
|
|
819
|
+
port: parseInt(ctx.activation?.getVariable('sftpPort') as string || '22'),
|
|
820
|
+
username: ctx.activation?.getVariable('sftpUsername') as string, // From variable
|
|
821
|
+
password: ctx.activation?.getVariable('sftpPassword') as string, // From variable
|
|
822
|
+
privateKey: ctx.activation?.getVariable('sftpPrivateKey') as string, // From variable
|
|
823
|
+
timeout: parseInt(ctx.activation?.getVariable('sftpTimeout') as string || '30000'),
|
|
824
|
+
keepaliveInterval: 10000,
|
|
825
|
+
retryDelay: 2000,
|
|
826
|
+
maxRetries: 3
|
|
827
|
+
}
|
|
828
|
+
}, logger);
|
|
829
|
+
|
|
830
|
+
// ═══════════════════════════════════════════════════════════════
|
|
831
|
+
// USAGE: SAME FOR BOTH APPROACHES
|
|
832
|
+
// ═══════════════════════════════════════════════════════════════
|
|
833
|
+
try {
|
|
834
|
+
const files = await sftpDataSource.listFiles({ directory: '/incoming' });
|
|
835
|
+
const fileContent = await sftpDataSource.downloadFile(files[0].path, { encoding: 'utf8' });
|
|
836
|
+
await sftpDataSource.uploadFile('/outgoing/result.csv', Buffer.from(csvData), {});
|
|
837
|
+
} finally {
|
|
838
|
+
await sftpDataSource.disconnect(); // ⚠️ ALWAYS disconnect SFTP!
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## Security Best Practices
|
|
845
|
+
|
|
846
|
+
### 1. Never Hardcode Credentials
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
// ❌ NEVER DO THIS
|
|
850
|
+
const s3DataSource = new S3DataSource({
|
|
851
|
+
s3Config: {
|
|
852
|
+
accessKeyId: 'AKIAIOSFODNN7EXAMPLE', // ❌ Hardcoded!
|
|
853
|
+
secretAccessKey: 'wJalrXUtnFEMI/K...' // ❌ Hardcoded!
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// ✅ ALWAYS DO THIS (Connection)
|
|
858
|
+
const s3DataSource = new S3DataSource({
|
|
859
|
+
s3Config: {
|
|
860
|
+
accessKeyId: ctx.activation?.connections?.s3?.credentials?.accessKeyId,
|
|
861
|
+
secretAccessKey: ctx.activation?.connections?.s3?.credentials?.secretAccessKey
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
// ✅ OR THIS (Variables)
|
|
866
|
+
const s3DataSource = new S3DataSource({
|
|
867
|
+
s3Config: {
|
|
868
|
+
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
869
|
+
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### 2. Use IAM Roles for S3 (When Possible)
|
|
875
|
+
|
|
876
|
+
If running on AWS infrastructure (EC2, Lambda), use IAM roles instead of access keys:
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
// Best: IAM Role (no credentials needed)
|
|
880
|
+
const s3DataSource = new S3DataSource({
|
|
881
|
+
type: 'S3_CSV',
|
|
882
|
+
s3Config: {
|
|
883
|
+
bucket: 'my-bucket',
|
|
884
|
+
region: 'us-east-1'
|
|
885
|
+
// No accessKeyId/secretAccessKey - uses IAM role
|
|
886
|
+
}
|
|
887
|
+
}, logger);
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### 3. Use SSH Keys for SFTP (Not Passwords)
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
// ⚠️ Password authentication (less secure)
|
|
894
|
+
sftpConfig: {
|
|
895
|
+
username: 'user',
|
|
896
|
+
password: 'password123' // Can be brute-forced
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// ✅ SSH Key authentication (more secure)
|
|
900
|
+
sftpConfig: {
|
|
901
|
+
username: 'user',
|
|
902
|
+
privateKey: ctx.activation?.getVariable('sftpPrivateKey') as string,
|
|
903
|
+
passphrase: ctx.activation?.getVariable('sftpPassphrase') as string
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### 4. Principle of Least Privilege
|
|
908
|
+
|
|
909
|
+
**S3 IAM Policy (Minimal Permissions):**
|
|
910
|
+
|
|
911
|
+
```json
|
|
912
|
+
{
|
|
913
|
+
"Version": "2012-10-17",
|
|
914
|
+
"Statement": [
|
|
915
|
+
{
|
|
916
|
+
"Effect": "Allow",
|
|
917
|
+
"Action": [
|
|
918
|
+
"s3:GetObject",
|
|
919
|
+
"s3:PutObject",
|
|
920
|
+
"s3:ListBucket"
|
|
921
|
+
],
|
|
922
|
+
"Resource": [
|
|
923
|
+
"arn:aws:s3:::my-inventory-bucket",
|
|
924
|
+
"arn:aws:s3:::my-inventory-bucket/*"
|
|
925
|
+
]
|
|
926
|
+
}
|
|
927
|
+
]
|
|
928
|
+
}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
**SFTP User Permissions:**
|
|
932
|
+
- Read-only for ingestion workflows
|
|
933
|
+
- Write-only for extraction workflows
|
|
934
|
+
- Restrict to specific directories via chroot
|
|
935
|
+
|
|
936
|
+
### 5. Credential Rotation
|
|
937
|
+
|
|
938
|
+
**With Versori Connections (Easy):**
|
|
939
|
+
1. Update connection in Versori UI
|
|
940
|
+
2. All workflows automatically use new credentials
|
|
941
|
+
3. Zero code changes needed
|
|
942
|
+
|
|
943
|
+
**With Activation Variables (Manual):**
|
|
944
|
+
1. Update each workflow's activation variables individually
|
|
945
|
+
2. Redeploy each affected workflow
|
|
946
|
+
3. Risk of missed workflows
|
|
947
|
+
|
|
948
|
+
### 6. Audit Logging
|
|
949
|
+
|
|
950
|
+
Enable logging for credential usage:
|
|
951
|
+
|
|
952
|
+
```typescript
|
|
953
|
+
// Log connection info (NOT credentials)
|
|
954
|
+
log.info('Using S3 connection', {
|
|
955
|
+
connectionId: s3Connection?.id,
|
|
956
|
+
bucket: s3Connection?.params?.bucket,
|
|
957
|
+
region: s3Connection?.params?.region,
|
|
958
|
+
// ❌ NEVER log: accessKeyId, secretAccessKey
|
|
959
|
+
});
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
### 7. Environment Separation
|
|
963
|
+
|
|
964
|
+
Use different connections/variables per environment:
|
|
965
|
+
|
|
966
|
+
```
|
|
967
|
+
Development:
|
|
968
|
+
- Connection: s3-dev
|
|
969
|
+
- Variables: s3BucketName=dev-inventory
|
|
970
|
+
|
|
971
|
+
Staging:
|
|
972
|
+
- Connection: s3-staging
|
|
973
|
+
- Variables: s3BucketName=staging-inventory
|
|
974
|
+
|
|
975
|
+
Production:
|
|
976
|
+
- Connection: s3-production
|
|
977
|
+
- Variables: s3BucketName=prod-inventory
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
---
|
|
981
|
+
|
|
982
|
+
## Migration Guide
|
|
983
|
+
|
|
984
|
+
### From Activation Variables → Versori Connections
|
|
985
|
+
|
|
986
|
+
**Step 1: Create Connection**
|
|
987
|
+
|
|
988
|
+
Extract current activation variables and create Versori connection:
|
|
989
|
+
|
|
990
|
+
**Current Activation Variables:**
|
|
991
|
+
```json
|
|
992
|
+
{
|
|
993
|
+
"s3BucketName": "my-inventory-bucket",
|
|
994
|
+
"awsRegion": "us-east-1",
|
|
995
|
+
"awsAccessKeyId": "AKIAIOSFODNN7EXAMPLE",
|
|
996
|
+
"awsSecretAccessKey": "wJalrXUtnFEMI..."
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**New Versori Connection:**
|
|
1001
|
+
- Name: `s3-production`
|
|
1002
|
+
- Type: AWS S3
|
|
1003
|
+
- Access Key ID: `AKIAIOSFODNN7EXAMPLE`
|
|
1004
|
+
- Secret Access Key: `wJalrXUtnFEMI...`
|
|
1005
|
+
- Region: `us-east-1`
|
|
1006
|
+
- Parameters: `{ "bucket": "my-inventory-bucket" }`
|
|
1007
|
+
|
|
1008
|
+
**Step 2: Update Workflow Code**
|
|
1009
|
+
|
|
1010
|
+
**Before (Activation Variables):**
|
|
1011
|
+
```typescript
|
|
1012
|
+
export const sync = schedule('daily', '0 2 * * *').then(
|
|
1013
|
+
http('process', {
|
|
1014
|
+
connection: 'fluent_commerce'
|
|
1015
|
+
}, async (ctx) => {
|
|
1016
|
+
const s3 = new S3DataSource({
|
|
1017
|
+
type: 'S3_CSV',
|
|
1018
|
+
s3Config: {
|
|
1019
|
+
bucket: ctx.activation?.getVariable('s3BucketName') as string,
|
|
1020
|
+
region: ctx.activation?.getVariable('awsRegion') as string,
|
|
1021
|
+
accessKeyId: ctx.activation?.getVariable('awsAccessKeyId') as string,
|
|
1022
|
+
secretAccessKey: ctx.activation?.getVariable('awsSecretAccessKey') as string
|
|
1023
|
+
}
|
|
1024
|
+
}, ctx.log);
|
|
1025
|
+
// ... rest of code
|
|
1026
|
+
})
|
|
1027
|
+
);
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
**After (Versori Connection):**
|
|
1031
|
+
```typescript
|
|
1032
|
+
export const sync = schedule('daily', '0 2 * * *').then(
|
|
1033
|
+
http('process', {
|
|
1034
|
+
connection: 'fluent_commerce',
|
|
1035
|
+
additionalConnections: {
|
|
1036
|
+
s3: 's3-production' // ← Add this
|
|
1037
|
+
}
|
|
1038
|
+
}, async (ctx) => {
|
|
1039
|
+
const s3Conn = ctx.activation?.connections?.s3;
|
|
1040
|
+
const s3 = new S3DataSource({
|
|
1041
|
+
type: 'S3_CSV',
|
|
1042
|
+
s3Config: {
|
|
1043
|
+
bucket: s3Conn?.params?.bucket,
|
|
1044
|
+
region: s3Conn?.params?.region,
|
|
1045
|
+
accessKeyId: s3Conn?.credentials?.accessKeyId,
|
|
1046
|
+
secretAccessKey: s3Conn?.credentials?.secretAccessKey
|
|
1047
|
+
}
|
|
1048
|
+
}, ctx.log);
|
|
1049
|
+
// ... rest of code (unchanged)
|
|
1050
|
+
})
|
|
1051
|
+
);
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**Step 3: Test & Deploy**
|
|
1055
|
+
|
|
1056
|
+
```bash
|
|
1057
|
+
# Test locally
|
|
1058
|
+
versori dev
|
|
1059
|
+
|
|
1060
|
+
# Deploy to production
|
|
1061
|
+
versori deploy
|
|
1062
|
+
|
|
1063
|
+
# Verify logs
|
|
1064
|
+
versori logs --workflow daily
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
**Step 4: Remove Old Activation Variables**
|
|
1068
|
+
|
|
1069
|
+
After verifying the connection works, remove the old activation variables from workflow settings.
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
## Troubleshooting
|
|
1074
|
+
|
|
1075
|
+
### Issue 1: "Connection not found"
|
|
1076
|
+
|
|
1077
|
+
**Error:**
|
|
1078
|
+
```
|
|
1079
|
+
Error: Connection 's3-production' not found
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
**Causes:**
|
|
1083
|
+
- Typo in connection name
|
|
1084
|
+
- Connection not created in Versori UI
|
|
1085
|
+
- Connection not added to `additionalConnections`
|
|
1086
|
+
|
|
1087
|
+
**Solution:**
|
|
1088
|
+
```typescript
|
|
1089
|
+
// 1. Verify connection exists in Versori UI
|
|
1090
|
+
// 2. Check name matches exactly (case-sensitive)
|
|
1091
|
+
additionalConnections: {
|
|
1092
|
+
s3: 's3-production' // ← Must match UI name exactly
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// 3. Add fallback
|
|
1096
|
+
const s3Conn = ctx.activation?.connections?.s3;
|
|
1097
|
+
if (!s3Conn) {
|
|
1098
|
+
log.error('S3 connection not found');
|
|
1099
|
+
return { success: false, error: 'S3 connection required' };
|
|
1100
|
+
}
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
1105
|
+
### Issue 2: "Missing activation variable"
|
|
1106
|
+
|
|
1107
|
+
**Error:**
|
|
1108
|
+
```
|
|
1109
|
+
Missing required activation variables: awsAccessKeyId
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
**Causes:**
|
|
1113
|
+
- Variable not defined in workflow settings
|
|
1114
|
+
- Typo in variable name
|
|
1115
|
+
- Variable name case mismatch
|
|
1116
|
+
|
|
1117
|
+
**Solution:**
|
|
1118
|
+
```typescript
|
|
1119
|
+
// 1. Verify activation variable exists in Versori UI
|
|
1120
|
+
// 2. Check exact name (case-sensitive)
|
|
1121
|
+
const accessKeyId = ctx.activation?.getVariable('awsAccessKeyId') as string;
|
|
1122
|
+
// ^^^^^^^^^^^^^^^
|
|
1123
|
+
// Must match UI exactly
|
|
1124
|
+
|
|
1125
|
+
// 3. Add validation with helpful error
|
|
1126
|
+
if (!accessKeyId) {
|
|
1127
|
+
log.error('Missing activation variable: awsAccessKeyId');
|
|
1128
|
+
log.info('Available variables:', Object.keys(ctx.activation?.variables || {}));
|
|
1129
|
+
throw new Error('Configuration error: awsAccessKeyId required');
|
|
1130
|
+
}
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
---
|
|
1134
|
+
|
|
1135
|
+
### Issue 3: S3 Access Denied
|
|
1136
|
+
|
|
1137
|
+
**Error:**
|
|
1138
|
+
```
|
|
1139
|
+
AccessDenied: User: arn:aws:iam::123456789012:user/app-user is not authorized to perform: s3:ListBucket
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
**Causes:**
|
|
1143
|
+
- Insufficient IAM permissions
|
|
1144
|
+
- Wrong bucket name
|
|
1145
|
+
- Incorrect region
|
|
1146
|
+
|
|
1147
|
+
**Solution:**
|
|
1148
|
+
```typescript
|
|
1149
|
+
// 1. Verify IAM permissions include required actions:
|
|
1150
|
+
// - s3:ListBucket (for listFiles)
|
|
1151
|
+
// - s3:GetObject (for downloadFile)
|
|
1152
|
+
// - s3:PutObject (for uploadFile)
|
|
1153
|
+
|
|
1154
|
+
// 2. Test connection with minimal operation
|
|
1155
|
+
try {
|
|
1156
|
+
const files = await s3DataSource.listFiles({ prefix: '', maxKeys: 1 });
|
|
1157
|
+
log.info('S3 connection successful');
|
|
1158
|
+
} catch (error: any) {
|
|
1159
|
+
log.error('S3 access denied', {
|
|
1160
|
+
bucket: config.s3Bucket,
|
|
1161
|
+
region: config.s3Region,
|
|
1162
|
+
error: error.message
|
|
1163
|
+
});
|
|
1164
|
+
throw error;
|
|
1165
|
+
}
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
---
|
|
1169
|
+
|
|
1170
|
+
### Issue 4: SFTP Connection Timeout
|
|
1171
|
+
|
|
1172
|
+
**Error:**
|
|
1173
|
+
```
|
|
1174
|
+
Error: Timeout while connecting to SFTP server
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Causes:**
|
|
1178
|
+
- Firewall blocking port 22
|
|
1179
|
+
- Incorrect host/port
|
|
1180
|
+
- Server not responding
|
|
1181
|
+
|
|
1182
|
+
**Solution:**
|
|
1183
|
+
```typescript
|
|
1184
|
+
// 1. Increase timeout
|
|
1185
|
+
sftpConfig: {
|
|
1186
|
+
timeout: 60000, // 60 seconds (default: 30s)
|
|
1187
|
+
keepaliveInterval: 10000
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// 2. Test connection manually
|
|
1191
|
+
ssh username@sftp.partner.com -p 22
|
|
1192
|
+
|
|
1193
|
+
// 3. Check firewall rules
|
|
1194
|
+
// Ensure Versori platform can reach SFTP server
|
|
1195
|
+
|
|
1196
|
+
// 4. Add retry logic
|
|
1197
|
+
async function connectSftpWithRetry(config, maxRetries = 3) {
|
|
1198
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
1199
|
+
try {
|
|
1200
|
+
const sftp = new SftpDataSource(config, log);
|
|
1201
|
+
await sftp.listFiles({ directory: '/' });
|
|
1202
|
+
return sftp;
|
|
1203
|
+
} catch (error: any) {
|
|
1204
|
+
if (i === maxRetries - 1) throw error;
|
|
1205
|
+
const delay = 2000 * Math.pow(2, i);
|
|
1206
|
+
log.warn(`SFTP connection failed, retrying in ${delay}ms...`);
|
|
1207
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
---
|
|
1214
|
+
|
|
1215
|
+
### Issue 5: SFTP Permission Denied
|
|
1216
|
+
|
|
1217
|
+
**Error:**
|
|
1218
|
+
```
|
|
1219
|
+
Error: Permission denied: /incoming/inventory.csv
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
**Causes:**
|
|
1223
|
+
- User lacks write permissions to directory
|
|
1224
|
+
- Directory doesn't exist
|
|
1225
|
+
- Wrong remote path
|
|
1226
|
+
|
|
1227
|
+
**Solution:**
|
|
1228
|
+
```typescript
|
|
1229
|
+
// 1. Create directory if needed (requires permissions)
|
|
1230
|
+
try {
|
|
1231
|
+
await sftpDataSource.createDirectory(remotePath);
|
|
1232
|
+
} catch (error: any) {
|
|
1233
|
+
log.warn('Could not create directory', { remotePath, error: error.message });
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// 2. Test with read operation first
|
|
1237
|
+
const files = await sftpDataSource.listFiles({ directory: '/' });
|
|
1238
|
+
log.info('SFTP readable directories:', files);
|
|
1239
|
+
|
|
1240
|
+
// 3. Verify path from SFTP root
|
|
1241
|
+
// Remote path should be absolute: /incoming/inventory.csv
|
|
1242
|
+
// NOT relative: incoming/inventory.csv
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
---
|
|
1246
|
+
|
|
1247
|
+
### Issue 6: Connection vs Variable Confusion
|
|
1248
|
+
|
|
1249
|
+
**Problem:** Code expects connection but variables are provided (or vice versa)
|
|
1250
|
+
|
|
1251
|
+
**Solution:** Support both with fallback:
|
|
1252
|
+
|
|
1253
|
+
```typescript
|
|
1254
|
+
// Hybrid approach: Try connection first, fallback to variables
|
|
1255
|
+
const s3Bucket =
|
|
1256
|
+
ctx.activation?.connections?.s3?.params?.bucket || // Try connection
|
|
1257
|
+
ctx.activation?.getVariable('s3BucketName') as string; // Fallback to variable
|
|
1258
|
+
|
|
1259
|
+
const s3AccessKey =
|
|
1260
|
+
ctx.activation?.connections?.s3?.credentials?.accessKeyId ||
|
|
1261
|
+
ctx.activation?.getVariable('awsAccessKeyId') as string;
|
|
1262
|
+
|
|
1263
|
+
// Validate
|
|
1264
|
+
if (!s3Bucket || !s3AccessKey) {
|
|
1265
|
+
throw new Error('S3 configuration required via connection or activation variables');
|
|
1266
|
+
}
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
---
|
|
1270
|
+
|
|
1271
|
+
### Issue 7: Incorrect Credential Access Pattern
|
|
1272
|
+
|
|
1273
|
+
**Error:**
|
|
1274
|
+
```
|
|
1275
|
+
TypeError: credentials is not a function
|
|
1276
|
+
ReferenceError: credentials is not defined
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
**Problem:** Documentation showed importing and calling `credentials` from `@versori/run`, but this export doesn't exist.
|
|
1280
|
+
|
|
1281
|
+
**Incorrect Pattern (OLD - DO NOT USE):**
|
|
1282
|
+
```typescript
|
|
1283
|
+
// ❌ WRONG - This doesn't exist!
|
|
1284
|
+
const cred = await ctx.credentials().get('connection_name');
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
**Correct Patterns:**
|
|
1288
|
+
|
|
1289
|
+
**Option A: Using http task with connection (Recommended):**
|
|
1290
|
+
```typescript
|
|
1291
|
+
// ✅ CORRECT - Use connectionVariables from ctx
|
|
1292
|
+
import { Buffer } from 'node:buffer';
|
|
1293
|
+
import { http } from '@versori/run';
|
|
1294
|
+
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
1295
|
+
|
|
1296
|
+
http("task", { connection: "sftp_server" }, async (ctx) => {
|
|
1297
|
+
const { connectionVariables } = ctx;
|
|
1298
|
+
|
|
1299
|
+
const sftpConfig = {
|
|
1300
|
+
host: connectionVariables.host,
|
|
1301
|
+
port: connectionVariables.port || 22,
|
|
1302
|
+
username: connectionVariables.username,
|
|
1303
|
+
password: Buffer.from(connectionVariables.password, 'base64').toString('utf-8')
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
const sftp = new SftpDataSource({ type: 'SFTP_CSV', settings: sftpConfig }, ctx.log);
|
|
1307
|
+
});
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
**Option B: Using ctx.connections directly:**
|
|
1311
|
+
```typescript
|
|
1312
|
+
// ✅ CORRECT - Use ctx.connections directly
|
|
1313
|
+
import { Buffer } from 'node:buffer';
|
|
1314
|
+
import { fn } from '@versori/run';
|
|
1315
|
+
import { SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
1316
|
+
|
|
1317
|
+
fn("task", async (ctx) => {
|
|
1318
|
+
const { connections } = ctx;
|
|
1319
|
+
const conn = connections.sftp_server;
|
|
1320
|
+
|
|
1321
|
+
const sftpConfig = {
|
|
1322
|
+
host: ctx.activation.getVariable('sftpHost'),
|
|
1323
|
+
port: 22,
|
|
1324
|
+
username: conn.username || Buffer.from(conn.accessToken, 'base64').toString('utf-8').split(':')[0],
|
|
1325
|
+
password: conn.password || Buffer.from(conn.accessToken, 'base64').toString('utf-8').split(':')[1]
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
const sftp = new SftpDataSource({ type: 'SFTP_CSV', settings: sftpConfig }, ctx.log);
|
|
1329
|
+
});
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
**Key Points:**
|
|
1333
|
+
- ✅ No `credentials` export exists from `@versori/run`
|
|
1334
|
+
- ✅ Use `connectionVariables` (http tasks) or `ctx.connections` directly
|
|
1335
|
+
- ✅ Always import `Buffer` from `node:buffer` when decoding base64
|
|
1336
|
+
- ✅ Both patterns are valid - choose based on task type
|
|
1337
|
+
|
|
1338
|
+
**Migration Guide:**
|
|
1339
|
+
1. Remove `credentials` from import statement
|
|
1340
|
+
2. Add `import { Buffer } from 'node:buffer'`
|
|
1341
|
+
3. Replace `ctx.credentials().get()` with `ctx.credentials().getAccessToken('connection_name')` for credential retrieval
|
|
1342
|
+
4. Or use `connectionVariables` directly in http tasks (recommended for http tasks)
|
|
1343
|
+
5. Or use `ctx.connections.connection_name` for direct connection access
|
|
1344
|
+
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
## Related Documentation
|
|
1348
|
+
|
|
1349
|
+
- **[Module 5: Connection Management](./modules/platforms-versori-05-connections.md)** - Detailed Versori connection types
|
|
1350
|
+
- **[S3 CSV Inventory Ingestion](../../../01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md)** - Complete S3 workflow example
|
|
1351
|
+
- **[SFTP CSV Control Ingestion](../../../01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md)** - Complete SFTP workflow example
|
|
1352
|
+
- **[Security Best Practices](./modules/platforms-versori-08-best-practices.md)** - Comprehensive security guide
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1356
|
+
## Quick Reference
|
|
1357
|
+
|
|
1358
|
+
### S3 Activation Variables
|
|
1359
|
+
|
|
1360
|
+
```json
|
|
1361
|
+
{
|
|
1362
|
+
"s3BucketName": "string (required)",
|
|
1363
|
+
"awsRegion": "string (default: us-east-1)",
|
|
1364
|
+
"awsAccessKeyId": "string (required)",
|
|
1365
|
+
"awsSecretAccessKey": "string (required)",
|
|
1366
|
+
"s3Prefix": "string (optional, default: empty)"
|
|
1367
|
+
}
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
### SFTP Activation Variables
|
|
1371
|
+
|
|
1372
|
+
```json
|
|
1373
|
+
{
|
|
1374
|
+
"sftpHost": "string (required)",
|
|
1375
|
+
"sftpPort": "number (default: 22)",
|
|
1376
|
+
"sftpUsername": "string (required)",
|
|
1377
|
+
"sftpPassword": "string (required if no privateKey)",
|
|
1378
|
+
"sftpPrivateKey": "string (required if no password)",
|
|
1379
|
+
"sftpPassphrase": "string (optional, for encrypted keys)",
|
|
1380
|
+
"sftpRemotePath": "string (default: /)",
|
|
1381
|
+
"sftpTimeout": "number (default: 30000ms)"
|
|
1382
|
+
}
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
### Versori Connection Structure
|
|
1386
|
+
|
|
1387
|
+
```typescript
|
|
1388
|
+
interface VersoriConnection {
|
|
1389
|
+
id: string;
|
|
1390
|
+
name: string;
|
|
1391
|
+
type: string;
|
|
1392
|
+
params?: {
|
|
1393
|
+
bucket?: string;
|
|
1394
|
+
region?: string;
|
|
1395
|
+
host?: string;
|
|
1396
|
+
port?: string;
|
|
1397
|
+
remotePath?: string;
|
|
1398
|
+
timeout?: string;
|
|
1399
|
+
[key: string]: string | undefined;
|
|
1400
|
+
};
|
|
1401
|
+
credentials?: {
|
|
1402
|
+
accessKeyId?: string;
|
|
1403
|
+
secretAccessKey?: string;
|
|
1404
|
+
username?: string;
|
|
1405
|
+
password?: string;
|
|
1406
|
+
privateKey?: string;
|
|
1407
|
+
passphrase?: string;
|
|
1408
|
+
[key: string]: string | undefined;
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
---
|
|
1414
|
+
|
|
1415
|
+
## Summary
|
|
1416
|
+
|
|
1417
|
+
✅ **Two configuration approaches** - Connections (production) vs Variables (development)
|
|
1418
|
+
✅ **Clear decision matrix** - When to use each approach
|
|
1419
|
+
✅ **Complete setup guides** - Step-by-step for both S3 and SFTP
|
|
1420
|
+
✅ **Side-by-side code examples** - Easy comparison
|
|
1421
|
+
✅ **Security best practices** - Never hardcode, least privilege, rotation
|
|
1422
|
+
✅ **Migration guide** - Move from variables to connections
|
|
1423
|
+
✅ **Troubleshooting** - Common issues and solutions
|
|
1424
|
+
|
|
1425
|
+
**Choose the right approach for your use case and follow security best practices!**
|