@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,1167 +1,1167 @@
|
|
|
1
|
-
# Module 5: Connection Management & Security
|
|
2
|
-
|
|
3
|
-
[← Back to Versori Platform Guide](../platforms-versori-readme.md)
|
|
4
|
-
|
|
5
|
-
**Module 5 of 8** | **Level**: Intermediate | **Time**: 20 minutes
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Learning Objectives
|
|
10
|
-
|
|
11
|
-
By the end of this module, you will:
|
|
12
|
-
|
|
13
|
-
- ✅ Understand different connection types in Versori
|
|
14
|
-
- ✅ Configure OAuth2 connections for Fluent Commerce
|
|
15
|
-
- ✅ Manage API key connections for S3 and other services
|
|
16
|
-
- ✅ Validate connections before deployment
|
|
17
|
-
- ✅ Implement connection security best practices
|
|
18
|
-
- ✅ Handle multi-connection scenarios
|
|
19
|
-
- ✅ Troubleshoot connection issues
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Connection Types Overview
|
|
24
|
-
|
|
25
|
-
Versori supports multiple connection types for different authentication methods:
|
|
26
|
-
|
|
27
|
-
| Connection Type | Use Case | Examples | Auth Method |
|
|
28
|
-
| --------------- | ---------------------------------------- | ------------------------------ | ---------------------------------- |
|
|
29
|
-
| **OAuth2** | API authentication with token management | Fluent Commerce, Salesforce | Client credentials, password grant |
|
|
30
|
-
| **API Key** | Simple token-based auth | AWS S3, third-party APIs | Static API key |
|
|
31
|
-
| **Basic Auth** | Username/password auth | Older APIs, internal services | HTTP Basic |
|
|
32
|
-
| **Custom** | Proprietary auth methods | Custom headers, signatures | Custom implementation |
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## OAuth2 Connections
|
|
37
|
-
|
|
38
|
-
### Fluent Commerce OAuth2 Configuration
|
|
39
|
-
|
|
40
|
-
The most common connection type for Fluent Commerce integrations.
|
|
41
|
-
|
|
42
|
-
#### Step-by-Step Configuration
|
|
43
|
-
|
|
44
|
-
1. **Navigate to Versori Dashboard**
|
|
45
|
-
- Go to **Connections** tab
|
|
46
|
-
- Click **"Add Connection"**
|
|
47
|
-
|
|
48
|
-
2. **Basic Information**
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
Name: fluent_commerce
|
|
52
|
-
Description: Fluent Commerce Production API
|
|
53
|
-
Type: OAuth2
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
3. **OAuth2 Settings**
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
Grant Type: client_credentials
|
|
60
|
-
Auth URL: https://api.fluentcommerce.com/oauth/token
|
|
61
|
-
Token URL: https://api.fluentcommerce.com/oauth/token
|
|
62
|
-
Scope: (leave empty for Fluent)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
4. **Client Credentials**
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
Client ID: [Your Fluent OAuth2 Client ID]
|
|
69
|
-
Client Secret: [Your Fluent OAuth2 Client Secret]
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
5. **Additional Configuration**
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
Base URL: https://api.fluentcommerce.com
|
|
76
|
-
Custom Headers: {"Content-Type": "application/json"}
|
|
77
|
-
Custom Parameters: {"retailerId": "1"}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
6. **Test Connection**
|
|
81
|
-
- Click **"Test Connection"**
|
|
82
|
-
- Verify successful token retrieval
|
|
83
|
-
- Check token expiration settings
|
|
84
|
-
|
|
85
|
-
### Accessing OAuth2 Connection in Code
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { http } from '@versori/run';
|
|
89
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
90
|
-
|
|
91
|
-
export const useConnection = http(
|
|
92
|
-
'use-connection',
|
|
93
|
-
{
|
|
94
|
-
connection: 'fluent_commerce', // Must match connection name in UI
|
|
95
|
-
},
|
|
96
|
-
async ctx => {
|
|
97
|
-
// Auto-configured client from connection
|
|
98
|
-
const client = await createClient(ctx);
|
|
99
|
-
|
|
100
|
-
// Access connection metadata
|
|
101
|
-
const connectionInfo = ctx.activation?.connection;
|
|
102
|
-
ctx.log('info', 'Connection details', {
|
|
103
|
-
url: connectionInfo?.url,
|
|
104
|
-
hasHeaders: !!connectionInfo?.headers,
|
|
105
|
-
retailerId: connectionInfo?.params?.retailerId,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Use client normally
|
|
109
|
-
const result = await client.graphql({
|
|
110
|
-
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return result.data;
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### OAuth2 Token Management
|
|
119
|
-
|
|
120
|
-
Versori automatically handles:
|
|
121
|
-
|
|
122
|
-
- **Token acquisition** on first request
|
|
123
|
-
- **Token caching** for subsequent requests
|
|
124
|
-
- **Token refresh** when expired
|
|
125
|
-
- **Error handling** on token failures
|
|
126
|
-
|
|
127
|
-
**You don't need to manage tokens manually!**
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## API Key Connections
|
|
132
|
-
|
|
133
|
-
For services that use simple API key authentication (e.g., AWS S3, third-party APIs).
|
|
134
|
-
|
|
135
|
-
### AWS S3 Connection Example
|
|
136
|
-
|
|
137
|
-
1. **Create Connection**
|
|
138
|
-
|
|
139
|
-
```
|
|
140
|
-
Name: aws_s3
|
|
141
|
-
Description: AWS S3 for inventory files
|
|
142
|
-
Type: API Key
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
2. **Configuration**
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
Authentication: Configure on the connection (no inline headers)
|
|
149
|
-
Additional Headers:
|
|
150
|
-
- x-secret-key: [Your AWS Secret Key]
|
|
151
|
-
- x-region: us-east-1
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
3. **Access in Code**
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
export const readFromS3 = http(
|
|
158
|
-
'read-s3',
|
|
159
|
-
{
|
|
160
|
-
connection: 'aws_s3',
|
|
161
|
-
},
|
|
162
|
-
async ctx => {
|
|
163
|
-
const s3Client = new S3Client({
|
|
164
|
-
credentials: {
|
|
165
|
-
accessKeyId: ctx.activation?.connection?.headers?.['x-api-key'],
|
|
166
|
-
secretAccessKey: ctx.activation?.connection?.headers?.['x-secret-key'],
|
|
167
|
-
},
|
|
168
|
-
region: ctx.activation?.connection?.headers?.['x-region'],
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Use S3 client...
|
|
172
|
-
}
|
|
173
|
-
);
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Third-Party API Key Example
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
export const callThirdParty = http(
|
|
180
|
-
'third-party-api',
|
|
181
|
-
{
|
|
182
|
-
connection: 'third_party_service',
|
|
183
|
-
},
|
|
184
|
-
async ctx => {
|
|
185
|
-
// API key is automatically included in headers
|
|
186
|
-
const response = await ctx.fetch('https://api.thirdparty.com/data', {
|
|
187
|
-
method: 'GET',
|
|
188
|
-
// Headers from connection are automatically included
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const data = await response.json();
|
|
192
|
-
return data;
|
|
193
|
-
}
|
|
194
|
-
);
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## Multi-Connection Scenarios
|
|
200
|
-
|
|
201
|
-
Many integrations require multiple connections (e.g., Fluent + S3).
|
|
202
|
-
|
|
203
|
-
### Pattern 1: Primary Connection + SDK Client
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
import { http } from '@versori/run';
|
|
207
|
-
import { createClient, S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Fetch from S3, send to Fluent
|
|
211
|
-
*
|
|
212
|
-
* Uses: fluent_commerce (primary connection) + S3 credentials from env vars
|
|
213
|
-
*/
|
|
214
|
-
export const s3ToFluent = http(
|
|
215
|
-
's3-to-fluent',
|
|
216
|
-
{
|
|
217
|
-
connection: 'fluent_commerce', // Primary connection
|
|
218
|
-
},
|
|
219
|
-
async ctx => {
|
|
220
|
-
// Fluent client from primary connection
|
|
221
|
-
const client = await createClient(ctx);
|
|
222
|
-
|
|
223
|
-
// S3 client from environment variables
|
|
224
|
-
const s3 = new S3DataSource({
|
|
225
|
-
region: ctx.vars?.AWS_REGION || 'us-east-1',
|
|
226
|
-
credentials: {
|
|
227
|
-
accessKeyId: ctx.vars?.AWS_ACCESS_KEY_ID,
|
|
228
|
-
secretAccessKey: ctx.vars?.AWS_SECRET_ACCESS_KEY,
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Fetch from S3
|
|
233
|
-
const csvData = await s3.readFile('inventory-bucket', 'inventory.csv');
|
|
234
|
-
|
|
235
|
-
// Parse and send to Fluent
|
|
236
|
-
const records = parseCSV(csvData);
|
|
237
|
-
const job = await client.createJob({ name: 'S3 Import' });
|
|
238
|
-
await client.sendBatch(job.id, {
|
|
239
|
-
action: 'UPSERT',
|
|
240
|
-
entityType: 'INVENTORY',
|
|
241
|
-
entities: records,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
return { success: true, recordCount: records.length };
|
|
245
|
-
}
|
|
246
|
-
);
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### Pattern 2: Multiple Connections via ctx.connections
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
export const multiConnection = http(
|
|
253
|
-
'multi-connection',
|
|
254
|
-
{
|
|
255
|
-
connection: 'fluent_commerce',
|
|
256
|
-
},
|
|
257
|
-
async ctx => {
|
|
258
|
-
// Primary connection (Fluent)
|
|
259
|
-
const fluentClient = await createClient(ctx);
|
|
260
|
-
|
|
261
|
-
// Access other connections
|
|
262
|
-
const s3Connection = ctx.connections?.aws_s3;
|
|
263
|
-
const sfccConnection = ctx.connections?.sfcc_api;
|
|
264
|
-
|
|
265
|
-
// Use multiple services
|
|
266
|
-
const fluentData = await fluentClient.graphql({ query: '...' });
|
|
267
|
-
const s3Data = await fetchFromS3WithConnection(s3Connection);
|
|
268
|
-
const sfccData = await fetchFromSFCCWithConnection(sfccConnection);
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
fluent: fluentData,
|
|
272
|
-
s3: s3Data,
|
|
273
|
-
sfcc: sfccData,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
);
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
---
|
|
280
|
-
|
|
281
|
-
## Connection Validation
|
|
282
|
-
|
|
283
|
-
### Pre-Deployment Validation
|
|
284
|
-
|
|
285
|
-
Before deploying, validate all connections:
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
import { fn } from '@versori/run';
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Validate all required connections
|
|
292
|
-
*
|
|
293
|
-
* Run manually before deployment
|
|
294
|
-
*/
|
|
295
|
-
export const validateConnections = fn('validate-connections', async ctx => {
|
|
296
|
-
const requiredConnections = ['fluent_commerce', 'aws_s3', 'sfcc_api'];
|
|
297
|
-
|
|
298
|
-
const results = {};
|
|
299
|
-
|
|
300
|
-
for (const connName of requiredConnections) {
|
|
301
|
-
const conn = ctx.connections?.[connName];
|
|
302
|
-
|
|
303
|
-
if (!conn) {
|
|
304
|
-
results[connName] = {
|
|
305
|
-
status: 'missing',
|
|
306
|
-
error: 'Connection not configured',
|
|
307
|
-
};
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Basic validation
|
|
312
|
-
results[connName] = {
|
|
313
|
-
status: 'configured',
|
|
314
|
-
hasUrl: !!conn.url,
|
|
315
|
-
hasHeaders: !!conn.headers,
|
|
316
|
-
hasParams: !!conn.params,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
ctx.log('info', 'Connection validation complete', results);
|
|
321
|
-
|
|
322
|
-
return results;
|
|
323
|
-
});
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Runtime Connection Testing
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
import { http } from '@versori/run';
|
|
330
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Test Fluent Commerce connection
|
|
334
|
-
*
|
|
335
|
-
* GET https://{workspace}.versori.run/test-connection
|
|
336
|
-
*/
|
|
337
|
-
export const testConnection = http(
|
|
338
|
-
'test-connection',
|
|
339
|
-
{
|
|
340
|
-
connection: 'fluent_commerce',
|
|
341
|
-
},
|
|
342
|
-
async ctx => {
|
|
343
|
-
try {
|
|
344
|
-
const client = await createClient(ctx);
|
|
345
|
-
|
|
346
|
-
// Simple query to test connection
|
|
347
|
-
const result = await client.graphql({
|
|
348
|
-
query: `query { __typename }`,
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
if (result.errors?.length) {
|
|
352
|
-
return {
|
|
353
|
-
success: false,
|
|
354
|
-
error: 'GraphQL error',
|
|
355
|
-
details: result.errors[0].message,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
ctx.log('info', 'Connection test successful');
|
|
360
|
-
|
|
361
|
-
return {
|
|
362
|
-
success: true,
|
|
363
|
-
message: 'Connection working',
|
|
364
|
-
timestamp: new Date().toISOString(),
|
|
365
|
-
};
|
|
366
|
-
} catch (error) {
|
|
367
|
-
ctx.log('error', 'Connection test failed', {
|
|
368
|
-
error: error instanceof Error ? error.message : String(error),
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
return {
|
|
372
|
-
success: false,
|
|
373
|
-
error: error instanceof Error ? error.message : 'Connection failed',
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
);
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
## Security Best Practices
|
|
383
|
-
|
|
384
|
-
### 1. Never Hardcode Credentials
|
|
385
|
-
|
|
386
|
-
```typescript
|
|
387
|
-
// ❌ WRONG - Hardcoded credentials
|
|
388
|
-
export const bad = http(
|
|
389
|
-
'bad',
|
|
390
|
-
{
|
|
391
|
-
connection: 'fluent',
|
|
392
|
-
},
|
|
393
|
-
async ctx => {
|
|
394
|
-
const clientId = 'my-client-id'; // NEVER do this!
|
|
395
|
-
const clientSecret = 'my-secret'; // NEVER do this!
|
|
396
|
-
}
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
// ✅ CORRECT - Use connection or environment variables
|
|
400
|
-
export const good = http(
|
|
401
|
-
'good',
|
|
402
|
-
{
|
|
403
|
-
connection: 'fluent_commerce',
|
|
404
|
-
},
|
|
405
|
-
async ctx => {
|
|
406
|
-
const client = await createClient(ctx); // Credentials from connection
|
|
407
|
-
}
|
|
408
|
-
);
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 2. Use Connector Variables for Secrets
|
|
412
|
-
|
|
413
|
-
Configure sensitive values in Versori UI:
|
|
414
|
-
|
|
415
|
-
**Versori Dashboard → Connector → Variables**:
|
|
416
|
-
|
|
417
|
-
```
|
|
418
|
-
Name: FLUENT_WEBHOOK_PUBLIC_KEY
|
|
419
|
-
Value: -----BEGIN PUBLIC KEY-----...
|
|
420
|
-
Type: Secret
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
**Access in code**:
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
export const useSecrets = webhook('use-secrets', async ctx => {
|
|
427
|
-
// Access secret variable
|
|
428
|
-
const publicKey = ctx.vars?.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
429
|
-
|
|
430
|
-
if (!publicKey) {
|
|
431
|
-
return { error: 'Missing public key configuration' };
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Use securely
|
|
435
|
-
const validator = new WebhookValidationService({ publicKey });
|
|
436
|
-
// ...
|
|
437
|
-
});
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
### 3. Environment-Specific Connections
|
|
441
|
-
|
|
442
|
-
Maintain separate connections for each environment:
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
Development:
|
|
446
|
-
- fluent_commerce_dev
|
|
447
|
-
- aws_s3_dev
|
|
448
|
-
|
|
449
|
-
Staging:
|
|
450
|
-
- fluent_commerce_staging
|
|
451
|
-
- aws_s3_staging
|
|
452
|
-
|
|
453
|
-
Production:
|
|
454
|
-
- fluent_commerce_prod
|
|
455
|
-
- aws_s3_prod
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
**Code pattern**:
|
|
459
|
-
|
|
460
|
-
```typescript
|
|
461
|
-
export const envAwareWorkflow = http(
|
|
462
|
-
'env-aware',
|
|
463
|
-
{
|
|
464
|
-
connection:
|
|
465
|
-
process.env.NODE_ENV === 'production' ? 'fluent_commerce_prod' : 'fluent_commerce_dev',
|
|
466
|
-
},
|
|
467
|
-
async ctx => {
|
|
468
|
-
const client = await createClient(ctx);
|
|
469
|
-
// ...
|
|
470
|
-
}
|
|
471
|
-
);
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### 4. Rotate Credentials Regularly
|
|
475
|
-
|
|
476
|
-
- **OAuth2 Client Secrets**: Rotate every 90 days
|
|
477
|
-
- **API Keys**: Rotate every 180 days
|
|
478
|
-
- **Webhook Public Keys**: Rotate on security events
|
|
479
|
-
|
|
480
|
-
**Update process**:
|
|
481
|
-
|
|
482
|
-
1. Generate new credentials in source system
|
|
483
|
-
2. Update connection in Versori UI
|
|
484
|
-
3. Test connection
|
|
485
|
-
4. Deactivate old credentials after confirmation
|
|
486
|
-
|
|
487
|
-
### 5. Audit Connection Access
|
|
488
|
-
|
|
489
|
-
```typescript
|
|
490
|
-
export const auditedWorkflow = http(
|
|
491
|
-
'audited',
|
|
492
|
-
{
|
|
493
|
-
connection: 'fluent_commerce',
|
|
494
|
-
},
|
|
495
|
-
async ctx => {
|
|
496
|
-
// Log connection usage
|
|
497
|
-
ctx.log('info', 'Connection accessed', {
|
|
498
|
-
workflow: 'audited',
|
|
499
|
-
connection: 'fluent_commerce',
|
|
500
|
-
timestamp: Date.now(),
|
|
501
|
-
executionId: ctx.executionId,
|
|
502
|
-
user: ctx.activation?.user || 'system',
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
const client = await createClient(ctx);
|
|
506
|
-
// ... rest of workflow
|
|
507
|
-
}
|
|
508
|
-
);
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
## Accessing All Connections (v0.4.x+)
|
|
514
|
-
|
|
515
|
-
### NEW: activation.connections Array
|
|
516
|
-
|
|
517
|
-
As of @versori/run v0.4.x, you can access **all configured connections** for the current activation via `ctx.activation.connections`.
|
|
518
|
-
|
|
519
|
-
#### Connection Structure
|
|
520
|
-
|
|
521
|
-
```typescript
|
|
522
|
-
type Connection = {
|
|
523
|
-
id: string; // Unique connection ID
|
|
524
|
-
name: string; // Connection name (matches UI)
|
|
525
|
-
templateId: string; // Connection template/type ID
|
|
526
|
-
dynamic: boolean; // Whether connection is dynamic
|
|
527
|
-
baseUrl: string; // Base URL for the connection
|
|
528
|
-
createdAt: string; // ISO timestamp
|
|
529
|
-
updatedAt: string; // ISO timestamp
|
|
530
|
-
credentials: ConnectionCredential[]; // Credential details
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
#### Listing All Available Connections
|
|
535
|
-
|
|
536
|
-
```typescript
|
|
537
|
-
import { fn } from '@versori/run';
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* List all connections for current activation
|
|
541
|
-
*/
|
|
542
|
-
export const listConnections = fn('list-connections', ctx => {
|
|
543
|
-
const connections = ctx.activation.connections;
|
|
544
|
-
|
|
545
|
-
if (!connections || connections.length === 0) {
|
|
546
|
-
return { message: 'No connections configured', count: 0 };
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
ctx.log.info('Available connections', {
|
|
550
|
-
count: connections.length,
|
|
551
|
-
names: connections.map(c => c.name)
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
return {
|
|
555
|
-
count: connections.length,
|
|
556
|
-
connections: connections.map(c => ({
|
|
557
|
-
name: c.name,
|
|
558
|
-
id: c.id,
|
|
559
|
-
baseUrl: c.baseUrl,
|
|
560
|
-
dynamic: c.dynamic,
|
|
561
|
-
createdAt: c.createdAt
|
|
562
|
-
}))
|
|
563
|
-
};
|
|
564
|
-
});
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
#### Finding Specific Connection
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
import { fn } from '@versori/run';
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Check if Fluent Commerce connection exists
|
|
574
|
-
*/
|
|
575
|
-
export const checkFluentConnection = fn('check-fluent-connection', ctx => {
|
|
576
|
-
const connections = ctx.activation.connections;
|
|
577
|
-
const fluentConn = connections?.find(c => c.name === 'fluent_commerce');
|
|
578
|
-
|
|
579
|
-
if (!fluentConn) {
|
|
580
|
-
throw new Error('Fluent Commerce connection not configured');
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
ctx.log.info('Fluent connection found', {
|
|
584
|
-
id: fluentConn.id,
|
|
585
|
-
baseUrl: fluentConn.baseUrl,
|
|
586
|
-
dynamic: fluentConn.dynamic
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
return {
|
|
590
|
-
found: true,
|
|
591
|
-
connectionId: fluentConn.id,
|
|
592
|
-
baseUrl: fluentConn.baseUrl
|
|
593
|
-
};
|
|
594
|
-
});
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
### Multi-Tenant Pattern with activation.connections
|
|
598
|
-
|
|
599
|
-
**Use Case**: One connector serves multiple customers, each with their own Fluent instance.
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
import { webhook, fn, http } from '@versori/run';
|
|
603
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Multi-tenant order creation
|
|
607
|
-
*
|
|
608
|
-
* POST https://{workspace}.versori.run/create-order
|
|
609
|
-
* Body: { customerId: "customer-a", orderData: {...} }
|
|
610
|
-
*/
|
|
611
|
-
export const multiTenantOrderCreate = webhook('multi-tenant-order-create', {
|
|
612
|
-
response: { mode: 'sync' },
|
|
613
|
-
})
|
|
614
|
-
// Step 1: Route to correct Fluent connection (fn)
|
|
615
|
-
.then(fn('route-connection', ctx => {
|
|
616
|
-
const { customerId, orderData } = ctx.data;
|
|
617
|
-
|
|
618
|
-
if (!customerId) {
|
|
619
|
-
throw new Error('customerId is required');
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Access all connections
|
|
623
|
-
const connections = ctx.activation.connections;
|
|
624
|
-
|
|
625
|
-
if (!connections || connections.length === 0) {
|
|
626
|
-
throw new Error('No connections configured for this activation');
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Find customer-specific connection
|
|
630
|
-
// Convention: connections named fluent_customer-a, fluent_customer-b, etc.
|
|
631
|
-
const connectionName = `fluent_${customerId.toLowerCase()}`;
|
|
632
|
-
const connection = connections.find(c => c.name === connectionName);
|
|
633
|
-
|
|
634
|
-
if (!connection) {
|
|
635
|
-
throw new Error(`No Fluent connection found for customer: ${customerId}`);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
ctx.log.info('Routed to customer connection', {
|
|
639
|
-
customerId,
|
|
640
|
-
connectionName: connection.name,
|
|
641
|
-
connectionId: connection.id,
|
|
642
|
-
baseUrl: connection.baseUrl
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
return {
|
|
646
|
-
connectionName: connection.name,
|
|
647
|
-
customerId,
|
|
648
|
-
orderData
|
|
649
|
-
};
|
|
650
|
-
}))
|
|
651
|
-
|
|
652
|
-
// Step 2: Create order in customer's Fluent instance (http)
|
|
653
|
-
.then(http('create-order', (ctx) => {
|
|
654
|
-
// Return dynamic connection based on routing
|
|
655
|
-
return { connection: ctx.data.connectionName };
|
|
656
|
-
}, async ctx => {
|
|
657
|
-
const { customerId, orderData } = ctx.data;
|
|
658
|
-
|
|
659
|
-
// Create client with customer-specific connection
|
|
660
|
-
const client = await createClient(ctx);
|
|
661
|
-
|
|
662
|
-
// Create order
|
|
663
|
-
const result = await client.graphql({
|
|
664
|
-
query: `
|
|
665
|
-
mutation CreateOrder($input: CreateOrderInput!) {
|
|
666
|
-
createOrder(input: $input) {
|
|
667
|
-
id
|
|
668
|
-
ref
|
|
669
|
-
status
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
`,
|
|
673
|
-
variables: { input: orderData }
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
if (result.errors?.length) {
|
|
677
|
-
throw new Error(`Order creation failed: ${result.errors[0].message}`);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
ctx.log.info('Order created', {
|
|
681
|
-
customerId,
|
|
682
|
-
orderId: result.data.createOrder.id
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
return {
|
|
686
|
-
success: true,
|
|
687
|
-
customerId,
|
|
688
|
-
orderId: result.data.createOrder.id,
|
|
689
|
-
orderRef: result.data.createOrder.ref
|
|
690
|
-
};
|
|
691
|
-
}))
|
|
692
|
-
|
|
693
|
-
.catch(({ data }) => ({
|
|
694
|
-
success: false,
|
|
695
|
-
error: data instanceof Error ? data.message : String(data),
|
|
696
|
-
status: 500
|
|
697
|
-
}));
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
### Iterating Through Multiple Connections
|
|
701
|
-
|
|
702
|
-
**Use Case**: Health check across all Fluent connections.
|
|
703
|
-
|
|
704
|
-
```typescript
|
|
705
|
-
import { fn, http } from '@versori/run';
|
|
706
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Test all Fluent connections
|
|
710
|
-
*
|
|
711
|
-
* GET https://{workspace}.versori.run/test-all-connections
|
|
712
|
-
*/
|
|
713
|
-
export const testAllConnections = fn('test-all-fluent', async ctx => {
|
|
714
|
-
const connections = ctx.activation.connections;
|
|
715
|
-
|
|
716
|
-
// Filter to only Fluent connections (by name convention)
|
|
717
|
-
const fluentConnections = connections?.filter(c =>
|
|
718
|
-
c.name.startsWith('fluent_')
|
|
719
|
-
) || [];
|
|
720
|
-
|
|
721
|
-
if (fluentConnections.length === 0) {
|
|
722
|
-
return { message: 'No Fluent connections found', count: 0 };
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
ctx.log.info('Testing Fluent connections', {
|
|
726
|
-
count: fluentConnections.length
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
const results = [];
|
|
730
|
-
|
|
731
|
-
// Test each connection
|
|
732
|
-
for (const conn of fluentConnections) {
|
|
733
|
-
try {
|
|
734
|
-
// Note: To actually test, we'd need to use http() with the connection
|
|
735
|
-
// This example shows discovery only
|
|
736
|
-
results.push({
|
|
737
|
-
name: conn.name,
|
|
738
|
-
id: conn.id,
|
|
739
|
-
baseUrl: conn.baseUrl,
|
|
740
|
-
status: 'configured',
|
|
741
|
-
available: true
|
|
742
|
-
});
|
|
743
|
-
} catch (error) {
|
|
744
|
-
results.push({
|
|
745
|
-
name: conn.name,
|
|
746
|
-
id: conn.id,
|
|
747
|
-
status: 'error',
|
|
748
|
-
available: false,
|
|
749
|
-
error: error instanceof Error ? error.message : String(error)
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return {
|
|
755
|
-
totalConnections: fluentConnections.length,
|
|
756
|
-
results
|
|
757
|
-
};
|
|
758
|
-
});
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
### Validation Pattern: Required Connections
|
|
762
|
-
|
|
763
|
-
```typescript
|
|
764
|
-
import { fn } from '@versori/run';
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* Validate required connections exist
|
|
768
|
-
*
|
|
769
|
-
* Recommended to run at connector startup
|
|
770
|
-
*/
|
|
771
|
-
export const validateRequiredConnections = fn('validate-required', ctx => {
|
|
772
|
-
const required = ['fluent_commerce', 'aws_s3', 'sftp_warehouse'];
|
|
773
|
-
const connections = ctx.activation.connections || [];
|
|
774
|
-
const connectionNames = new Set(connections.map(c => c.name));
|
|
775
|
-
|
|
776
|
-
const missing = required.filter(name => !connectionNames.has(name));
|
|
777
|
-
|
|
778
|
-
if (missing.length > 0) {
|
|
779
|
-
throw new Error(`Missing required connections: ${missing.join(', ')}`);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
ctx.log.info('All required connections configured', {
|
|
783
|
-
required: required.length,
|
|
784
|
-
configured: connections.length
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
return {
|
|
788
|
-
valid: true,
|
|
789
|
-
requiredConnections: required,
|
|
790
|
-
totalConnections: connections.length
|
|
791
|
-
};
|
|
792
|
-
});
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
---
|
|
796
|
-
|
|
797
|
-
## Activation Environment & Dynamic Variables (v0.4.x+)
|
|
798
|
-
|
|
799
|
-
### activation.environment
|
|
800
|
-
|
|
801
|
-
Access the current activation's environment information:
|
|
802
|
-
|
|
803
|
-
```typescript
|
|
804
|
-
import { fn } from '@versori/run';
|
|
805
|
-
|
|
806
|
-
/**
|
|
807
|
-
* Check environment and apply environment-specific logic
|
|
808
|
-
*/
|
|
809
|
-
export const envAwareWorkflow = fn('env-aware', ctx => {
|
|
810
|
-
const env = ctx.activation.environment;
|
|
811
|
-
|
|
812
|
-
if (!env) {
|
|
813
|
-
ctx.log.warn('No environment information available');
|
|
814
|
-
return { environment: 'unknown' };
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
ctx.log.info('Environment details', {
|
|
818
|
-
id: env.id,
|
|
819
|
-
name: env.name,
|
|
820
|
-
type: env.type // e.g., 'development', 'staging', 'production'
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// Conditional logic based on environment
|
|
824
|
-
if (env.name === 'production') {
|
|
825
|
-
// Use production-specific settings
|
|
826
|
-
return {
|
|
827
|
-
environment: 'production',
|
|
828
|
-
batchSize: 1000,
|
|
829
|
-
retryAttempts: 5,
|
|
830
|
-
timeout: 60000
|
|
831
|
-
};
|
|
832
|
-
} else if (env.name === 'staging') {
|
|
833
|
-
return {
|
|
834
|
-
environment: 'staging',
|
|
835
|
-
batchSize: 100,
|
|
836
|
-
retryAttempts: 3,
|
|
837
|
-
timeout: 30000
|
|
838
|
-
};
|
|
839
|
-
} else {
|
|
840
|
-
return {
|
|
841
|
-
environment: 'development',
|
|
842
|
-
batchSize: 10,
|
|
843
|
-
retryAttempts: 1,
|
|
844
|
-
timeout: 10000
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
### activation.dynamicVariables
|
|
851
|
-
|
|
852
|
-
Access dynamic variables set at the activation level:
|
|
853
|
-
|
|
854
|
-
```typescript
|
|
855
|
-
import { fn } from '@versori/run';
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Access activation-level dynamic variables
|
|
859
|
-
*/
|
|
860
|
-
export const useDynamicVars = fn('use-dynamic-vars', ctx => {
|
|
861
|
-
// Get variables using getVariable
|
|
862
|
-
const apiKey = ctx.activation.getVariable<string>('API_KEY');
|
|
863
|
-
const retailerId = ctx.activation.getVariable<number>('RETAILER_ID');
|
|
864
|
-
const region = ctx.activation.getVariable<string>('AWS_REGION');
|
|
865
|
-
|
|
866
|
-
ctx.log.info('Dynamic variables', {
|
|
867
|
-
hasApiKey: !!apiKey,
|
|
868
|
-
retailerId,
|
|
869
|
-
region
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
// Use variables in your workflow
|
|
873
|
-
return {
|
|
874
|
-
retailerId,
|
|
875
|
-
region,
|
|
876
|
-
configured: !!apiKey && !!retailerId
|
|
877
|
-
};
|
|
878
|
-
});
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
### Setting Variables Programmatically
|
|
882
|
-
|
|
883
|
-
```typescript
|
|
884
|
-
import { http } from '@versori/run';
|
|
885
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* Cache frequently-used data in activation variables
|
|
889
|
-
*/
|
|
890
|
-
export const cacheRetailerConfig = http(
|
|
891
|
-
'cache-retailer-config',
|
|
892
|
-
{ connection: 'fluent_commerce' },
|
|
893
|
-
async ctx => {
|
|
894
|
-
const client = await createClient(ctx);
|
|
895
|
-
|
|
896
|
-
// Query Fluent for retailer configuration
|
|
897
|
-
const result = await client.graphql({
|
|
898
|
-
query: `
|
|
899
|
-
query GetRetailer($id: Int!) {
|
|
900
|
-
retailer(id: $id) {
|
|
901
|
-
id
|
|
902
|
-
name
|
|
903
|
-
tradingName
|
|
904
|
-
supportEmail
|
|
905
|
-
primaryLocation {
|
|
906
|
-
ref
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
`,
|
|
911
|
-
variables: { id: 1 }
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
const retailer = result.data.retailer;
|
|
915
|
-
|
|
916
|
-
// Cache in activation variables for future workflows
|
|
917
|
-
await ctx.activation.setVariable('CACHED_RETAILER_ID', retailer.id);
|
|
918
|
-
await ctx.activation.setVariable('CACHED_RETAILER_NAME', retailer.name);
|
|
919
|
-
await ctx.activation.setVariable('CACHED_PRIMARY_LOCATION', retailer.primaryLocation.ref);
|
|
920
|
-
await ctx.activation.setVariable('LAST_CONFIG_REFRESH', Date.now());
|
|
921
|
-
|
|
922
|
-
ctx.log.info('Retailer config cached', {
|
|
923
|
-
retailerId: retailer.id,
|
|
924
|
-
retailerName: retailer.name
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
return {
|
|
928
|
-
cached: true,
|
|
929
|
-
retailer: {
|
|
930
|
-
id: retailer.id,
|
|
931
|
-
name: retailer.name
|
|
932
|
-
}
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
);
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
### Complete ActivationImpl Reference
|
|
939
|
-
|
|
940
|
-
```typescript
|
|
941
|
-
interface ActivationImpl {
|
|
942
|
-
// Properties
|
|
943
|
-
get id(): string; // Activation ID
|
|
944
|
-
get user(): EndUser; // User info
|
|
945
|
-
get environment(): ProjectEnvironment | undefined; // ✅ NEW in v0.4.x
|
|
946
|
-
get connections(): Array<Connection> | undefined; // ✅ NEW in v0.4.x
|
|
947
|
-
get dynamicVariables(): DynamicVariables | undefined; // ✅ NEW in v0.4.x
|
|
948
|
-
|
|
949
|
-
// Methods
|
|
950
|
-
getVariable<T = unknown>(name: string): T | undefined;
|
|
951
|
-
setVariable(name: string, value: unknown): Promise<void>;
|
|
952
|
-
}
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
---
|
|
956
|
-
|
|
957
|
-
## Troubleshooting Connection Issues
|
|
958
|
-
|
|
959
|
-
### Common Issues & Solutions
|
|
960
|
-
|
|
961
|
-
| Issue | Symptoms | Solution |
|
|
962
|
-
| ------------------------ | ------------------------------------ | ---------------------------------------------------------- |
|
|
963
|
-
| **Connection not found** | `Error: Connection 'xyz' not found` | Verify connection name matches UI exactly (case-sensitive) |
|
|
964
|
-
| **Token expired** | `401 Unauthorized` | Versori should auto-refresh; check token URL configuration |
|
|
965
|
-
| **Invalid credentials** | `403 Forbidden` | Verify Client ID/Secret in Fluent dashboard |
|
|
966
|
-
| **Missing retailerId** | `retailerId is required for job creation` | Use `client.setRetailerId('1')` or pass in method call. See [retailerId Configuration Guide](../../../../00-START-HERE/retailerid-configuration.md) |
|
|
967
|
-
| **CORS errors** | `CORS policy blocked` | Enable CORS in webhook configuration |
|
|
968
|
-
| **Rate limiting** | `429 Too Many Requests` | Implement retry with backoff, check Fluent API limits |
|
|
969
|
-
|
|
970
|
-
### Debug Connection Configuration
|
|
971
|
-
|
|
972
|
-
```typescript
|
|
973
|
-
export const debugConnection = http(
|
|
974
|
-
'debug-connection',
|
|
975
|
-
{
|
|
976
|
-
connection: 'fluent_commerce',
|
|
977
|
-
},
|
|
978
|
-
async ctx => {
|
|
979
|
-
const conn = ctx.activation?.connection;
|
|
980
|
-
|
|
981
|
-
// Log all connection details (except secrets)
|
|
982
|
-
ctx.log('debug', 'Connection configuration', {
|
|
983
|
-
hasConnection: !!conn,
|
|
984
|
-
url: conn?.url,
|
|
985
|
-
headerKeys: conn?.headers ? Object.keys(conn.headers) : [],
|
|
986
|
-
paramKeys: conn?.params ? Object.keys(conn.params) : [],
|
|
987
|
-
retailerId: conn?.params?.retailerId,
|
|
988
|
-
});
|
|
989
|
-
|
|
990
|
-
// Test basic connectivity
|
|
991
|
-
try {
|
|
992
|
-
const response = await ctx.fetch(conn?.url || 'https://api.fluentcommerce.com', {
|
|
993
|
-
method: 'HEAD',
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
return {
|
|
997
|
-
connectionStatus: 'reachable',
|
|
998
|
-
statusCode: response.status,
|
|
999
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
1000
|
-
};
|
|
1001
|
-
} catch (error) {
|
|
1002
|
-
return {
|
|
1003
|
-
connectionStatus: 'unreachable',
|
|
1004
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
);
|
|
1009
|
-
```
|
|
1010
|
-
|
|
1011
|
-
### Validate OAuth2 Token
|
|
1012
|
-
|
|
1013
|
-
```typescript
|
|
1014
|
-
export const validateToken = http(
|
|
1015
|
-
'validate-token',
|
|
1016
|
-
{
|
|
1017
|
-
connection: 'fluent_commerce',
|
|
1018
|
-
},
|
|
1019
|
-
async ctx => {
|
|
1020
|
-
const conn = ctx.activation?.connection;
|
|
1021
|
-
|
|
1022
|
-
// Extract token from headers (Versori auto-includes)
|
|
1023
|
-
const authHeader = conn?.headers?.['Authorization'];
|
|
1024
|
-
|
|
1025
|
-
if (!authHeader) {
|
|
1026
|
-
return { error: 'No authorization header found' };
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// Parse token (Bearer <token>)
|
|
1030
|
-
const token = authHeader.replace('Bearer ', '');
|
|
1031
|
-
|
|
1032
|
-
// Decode JWT (if applicable)
|
|
1033
|
-
try {
|
|
1034
|
-
const parts = token.split('.');
|
|
1035
|
-
if (parts.length === 3) {
|
|
1036
|
-
const payload = JSON.parse(atob(parts[1]));
|
|
1037
|
-
|
|
1038
|
-
return {
|
|
1039
|
-
tokenValid: true,
|
|
1040
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1000).toISOString() : 'unknown',
|
|
1041
|
-
issuedAt: payload.iat ? new Date(payload.iat * 1000).toISOString() : 'unknown',
|
|
1042
|
-
scopes: payload.scope || [],
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
return {
|
|
1047
|
-
tokenValid: false,
|
|
1048
|
-
error: 'Invalid JWT format',
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
return { tokenValid: true, tokenLength: token.length };
|
|
1053
|
-
}
|
|
1054
|
-
);
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
---
|
|
1058
|
-
|
|
1059
|
-
## Advanced Connection Patterns
|
|
1060
|
-
|
|
1061
|
-
### Pattern 1: Connection Fallback
|
|
1062
|
-
|
|
1063
|
-
```typescript
|
|
1064
|
-
export const withFallback = http(
|
|
1065
|
-
'connection-fallback',
|
|
1066
|
-
{
|
|
1067
|
-
connection: 'fluent_commerce_primary',
|
|
1068
|
-
},
|
|
1069
|
-
async ctx => {
|
|
1070
|
-
try {
|
|
1071
|
-
// Try primary connection
|
|
1072
|
-
const client = await createClient(ctx);
|
|
1073
|
-
return await client.graphql({ query: '...' });
|
|
1074
|
-
} catch (primaryError) {
|
|
1075
|
-
ctx.log('warn', 'Primary connection failed, trying fallback');
|
|
1076
|
-
|
|
1077
|
-
try {
|
|
1078
|
-
// Fallback to secondary connection
|
|
1079
|
-
const fallbackClient = await createClient({
|
|
1080
|
-
connection: ctx.connections?.fluent_commerce_secondary,
|
|
1081
|
-
log: ctx.log,
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
return await fallbackClient.graphql({ query: '...' });
|
|
1085
|
-
} catch (fallbackError) {
|
|
1086
|
-
throw new Error('All connections failed');
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
);
|
|
1091
|
-
```
|
|
1092
|
-
|
|
1093
|
-
### Pattern 2: Dynamic Connection Selection
|
|
1094
|
-
|
|
1095
|
-
```typescript
|
|
1096
|
-
export const dynamicConnection = http('dynamic-connection', async ctx => {
|
|
1097
|
-
// Select connection based on request parameter
|
|
1098
|
-
const environment = ctx.query?.env || 'production';
|
|
1099
|
-
|
|
1100
|
-
const connectionName =
|
|
1101
|
-
environment === 'staging' ? 'fluent_commerce_staging' : 'fluent_commerce_prod';
|
|
1102
|
-
|
|
1103
|
-
ctx.log('info', 'Selected connection', { connectionName, environment });
|
|
1104
|
-
|
|
1105
|
-
// Note: This pattern requires accessing ctx.connections
|
|
1106
|
-
const client = await createClient({
|
|
1107
|
-
connection: ctx.connections?.[connectionName],
|
|
1108
|
-
log: ctx.log,
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
return await client.graphql({ query: '...' });
|
|
1112
|
-
});
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
---
|
|
1116
|
-
|
|
1117
|
-
## Key Takeaways
|
|
1118
|
-
|
|
1119
|
-
- 🎯 **OAuth2 connections** are managed by Versori - automatic token refresh
|
|
1120
|
-
- 🎯 **Connection name** in code must match UI exactly (case-sensitive)
|
|
1121
|
-
- 🎯 **Never hardcode** credentials - use connections or environment variables
|
|
1122
|
-
- 🎯 **Multi-connection scenarios** use primary connection + ctx.connections
|
|
1123
|
-
- 🎯 **Validate connections** before deployment with test workflows
|
|
1124
|
-
- 🎯 **Security best practices**: rotate credentials, use secrets, audit access
|
|
1125
|
-
- 🎯 **Troubleshooting**: check name match, token expiration, credential validity
|
|
1126
|
-
|
|
1127
|
-
---
|
|
1128
|
-
|
|
1129
|
-
## Practice Exercise
|
|
1130
|
-
|
|
1131
|
-
Create a workflow that:
|
|
1132
|
-
|
|
1133
|
-
1. Uses OAuth2 connection to Fluent Commerce
|
|
1134
|
-
2. Validates connection on startup
|
|
1135
|
-
3. Falls back to secondary connection on failure
|
|
1136
|
-
4. Logs all connection attempts for audit
|
|
1137
|
-
5. Returns connection health status
|
|
1138
|
-
|
|
1139
|
-
**Hints**:
|
|
1140
|
-
|
|
1141
|
-
- Use `http()` with primary connection
|
|
1142
|
-
- Implement try/catch for fallback
|
|
1143
|
-
- Use `ctx.log()` for audit trail
|
|
1144
|
-
- Test token validity before GraphQL calls
|
|
1145
|
-
|
|
1146
|
-
**Solution** available in [Module 8: Best Practices](./platforms-versori-08-best-practices.md#practice-solutions)
|
|
1147
|
-
|
|
1148
|
-
---
|
|
1149
|
-
|
|
1150
|
-
## Next Steps
|
|
1151
|
-
|
|
1152
|
-
Now that you understand connection management, let's explore state management with KV storage.
|
|
1153
|
-
|
|
1154
|
-
Continue to [Module 6: KV Storage →](./platforms-versori-06-kv-storage.md) to learn about distributed state, file tracking, and caching patterns.
|
|
1155
|
-
|
|
1156
|
-
---
|
|
1157
|
-
|
|
1158
|
-
## Related Documentation
|
|
1159
|
-
|
|
1160
|
-
- [Module 3: Authentication](./platforms-versori-03-authentication.md) - OAuth2 basics
|
|
1161
|
-
- [Module 4: Workflows](./platforms-versori-04-workflows.md) - Using connections in workflows
|
|
1162
|
-
- [Module 6: KV Storage](./platforms-versori-06-kv-storage.md) - State management
|
|
1163
|
-
- [Module 8: Best Practices](./platforms-versori-08-best-practices.md) - Security hardening
|
|
1164
|
-
|
|
1165
|
-
---
|
|
1166
|
-
|
|
1167
|
-
[← Previous: Module 4](./platforms-versori-04-workflows.md) | [Back to Guide](../platforms-versori-readme.md) | [Next: Module 6: KV Storage →](./platforms-versori-06-kv-storage.md)
|
|
1
|
+
# Module 5: Connection Management & Security
|
|
2
|
+
|
|
3
|
+
[← Back to Versori Platform Guide](../platforms-versori-readme.md)
|
|
4
|
+
|
|
5
|
+
**Module 5 of 8** | **Level**: Intermediate | **Time**: 20 minutes
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Learning Objectives
|
|
10
|
+
|
|
11
|
+
By the end of this module, you will:
|
|
12
|
+
|
|
13
|
+
- ✅ Understand different connection types in Versori
|
|
14
|
+
- ✅ Configure OAuth2 connections for Fluent Commerce
|
|
15
|
+
- ✅ Manage API key connections for S3 and other services
|
|
16
|
+
- ✅ Validate connections before deployment
|
|
17
|
+
- ✅ Implement connection security best practices
|
|
18
|
+
- ✅ Handle multi-connection scenarios
|
|
19
|
+
- ✅ Troubleshoot connection issues
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Connection Types Overview
|
|
24
|
+
|
|
25
|
+
Versori supports multiple connection types for different authentication methods:
|
|
26
|
+
|
|
27
|
+
| Connection Type | Use Case | Examples | Auth Method |
|
|
28
|
+
| --------------- | ---------------------------------------- | ------------------------------ | ---------------------------------- |
|
|
29
|
+
| **OAuth2** | API authentication with token management | Fluent Commerce, Salesforce | Client credentials, password grant |
|
|
30
|
+
| **API Key** | Simple token-based auth | AWS S3, third-party APIs | Static API key |
|
|
31
|
+
| **Basic Auth** | Username/password auth | Older APIs, internal services | HTTP Basic |
|
|
32
|
+
| **Custom** | Proprietary auth methods | Custom headers, signatures | Custom implementation |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## OAuth2 Connections
|
|
37
|
+
|
|
38
|
+
### Fluent Commerce OAuth2 Configuration
|
|
39
|
+
|
|
40
|
+
The most common connection type for Fluent Commerce integrations.
|
|
41
|
+
|
|
42
|
+
#### Step-by-Step Configuration
|
|
43
|
+
|
|
44
|
+
1. **Navigate to Versori Dashboard**
|
|
45
|
+
- Go to **Connections** tab
|
|
46
|
+
- Click **"Add Connection"**
|
|
47
|
+
|
|
48
|
+
2. **Basic Information**
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Name: fluent_commerce
|
|
52
|
+
Description: Fluent Commerce Production API
|
|
53
|
+
Type: OAuth2
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
3. **OAuth2 Settings**
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Grant Type: client_credentials
|
|
60
|
+
Auth URL: https://api.fluentcommerce.com/oauth/token
|
|
61
|
+
Token URL: https://api.fluentcommerce.com/oauth/token
|
|
62
|
+
Scope: (leave empty for Fluent)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. **Client Credentials**
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Client ID: [Your Fluent OAuth2 Client ID]
|
|
69
|
+
Client Secret: [Your Fluent OAuth2 Client Secret]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
5. **Additional Configuration**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Base URL: https://api.fluentcommerce.com
|
|
76
|
+
Custom Headers: {"Content-Type": "application/json"}
|
|
77
|
+
Custom Parameters: {"retailerId": "1"}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
6. **Test Connection**
|
|
81
|
+
- Click **"Test Connection"**
|
|
82
|
+
- Verify successful token retrieval
|
|
83
|
+
- Check token expiration settings
|
|
84
|
+
|
|
85
|
+
### Accessing OAuth2 Connection in Code
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { http } from '@versori/run';
|
|
89
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
90
|
+
|
|
91
|
+
export const useConnection = http(
|
|
92
|
+
'use-connection',
|
|
93
|
+
{
|
|
94
|
+
connection: 'fluent_commerce', // Must match connection name in UI
|
|
95
|
+
},
|
|
96
|
+
async ctx => {
|
|
97
|
+
// Auto-configured client from connection
|
|
98
|
+
const client = await createClient(ctx);
|
|
99
|
+
|
|
100
|
+
// Access connection metadata
|
|
101
|
+
const connectionInfo = ctx.activation?.connection;
|
|
102
|
+
ctx.log('info', 'Connection details', {
|
|
103
|
+
url: connectionInfo?.url,
|
|
104
|
+
hasHeaders: !!connectionInfo?.headers,
|
|
105
|
+
retailerId: connectionInfo?.params?.retailerId,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Use client normally
|
|
109
|
+
const result = await client.graphql({
|
|
110
|
+
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return result.data;
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### OAuth2 Token Management
|
|
119
|
+
|
|
120
|
+
Versori automatically handles:
|
|
121
|
+
|
|
122
|
+
- **Token acquisition** on first request
|
|
123
|
+
- **Token caching** for subsequent requests
|
|
124
|
+
- **Token refresh** when expired
|
|
125
|
+
- **Error handling** on token failures
|
|
126
|
+
|
|
127
|
+
**You don't need to manage tokens manually!**
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## API Key Connections
|
|
132
|
+
|
|
133
|
+
For services that use simple API key authentication (e.g., AWS S3, third-party APIs).
|
|
134
|
+
|
|
135
|
+
### AWS S3 Connection Example
|
|
136
|
+
|
|
137
|
+
1. **Create Connection**
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
Name: aws_s3
|
|
141
|
+
Description: AWS S3 for inventory files
|
|
142
|
+
Type: API Key
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
2. **Configuration**
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Authentication: Configure on the connection (no inline headers)
|
|
149
|
+
Additional Headers:
|
|
150
|
+
- x-secret-key: [Your AWS Secret Key]
|
|
151
|
+
- x-region: us-east-1
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
3. **Access in Code**
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
export const readFromS3 = http(
|
|
158
|
+
'read-s3',
|
|
159
|
+
{
|
|
160
|
+
connection: 'aws_s3',
|
|
161
|
+
},
|
|
162
|
+
async ctx => {
|
|
163
|
+
const s3Client = new S3Client({
|
|
164
|
+
credentials: {
|
|
165
|
+
accessKeyId: ctx.activation?.connection?.headers?.['x-api-key'],
|
|
166
|
+
secretAccessKey: ctx.activation?.connection?.headers?.['x-secret-key'],
|
|
167
|
+
},
|
|
168
|
+
region: ctx.activation?.connection?.headers?.['x-region'],
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Use S3 client...
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Third-Party API Key Example
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
export const callThirdParty = http(
|
|
180
|
+
'third-party-api',
|
|
181
|
+
{
|
|
182
|
+
connection: 'third_party_service',
|
|
183
|
+
},
|
|
184
|
+
async ctx => {
|
|
185
|
+
// API key is automatically included in headers
|
|
186
|
+
const response = await ctx.fetch('https://api.thirdparty.com/data', {
|
|
187
|
+
method: 'GET',
|
|
188
|
+
// Headers from connection are automatically included
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
return data;
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Multi-Connection Scenarios
|
|
200
|
+
|
|
201
|
+
Many integrations require multiple connections (e.g., Fluent + S3).
|
|
202
|
+
|
|
203
|
+
### Pattern 1: Primary Connection + SDK Client
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { http } from '@versori/run';
|
|
207
|
+
import { createClient, S3DataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Fetch from S3, send to Fluent
|
|
211
|
+
*
|
|
212
|
+
* Uses: fluent_commerce (primary connection) + S3 credentials from env vars
|
|
213
|
+
*/
|
|
214
|
+
export const s3ToFluent = http(
|
|
215
|
+
's3-to-fluent',
|
|
216
|
+
{
|
|
217
|
+
connection: 'fluent_commerce', // Primary connection
|
|
218
|
+
},
|
|
219
|
+
async ctx => {
|
|
220
|
+
// Fluent client from primary connection
|
|
221
|
+
const client = await createClient(ctx);
|
|
222
|
+
|
|
223
|
+
// S3 client from environment variables
|
|
224
|
+
const s3 = new S3DataSource({
|
|
225
|
+
region: ctx.vars?.AWS_REGION || 'us-east-1',
|
|
226
|
+
credentials: {
|
|
227
|
+
accessKeyId: ctx.vars?.AWS_ACCESS_KEY_ID,
|
|
228
|
+
secretAccessKey: ctx.vars?.AWS_SECRET_ACCESS_KEY,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Fetch from S3
|
|
233
|
+
const csvData = await s3.readFile('inventory-bucket', 'inventory.csv');
|
|
234
|
+
|
|
235
|
+
// Parse and send to Fluent
|
|
236
|
+
const records = parseCSV(csvData);
|
|
237
|
+
const job = await client.createJob({ name: 'S3 Import' });
|
|
238
|
+
await client.sendBatch(job.id, {
|
|
239
|
+
action: 'UPSERT',
|
|
240
|
+
entityType: 'INVENTORY',
|
|
241
|
+
entities: records,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return { success: true, recordCount: records.length };
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Pattern 2: Multiple Connections via ctx.connections
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
export const multiConnection = http(
|
|
253
|
+
'multi-connection',
|
|
254
|
+
{
|
|
255
|
+
connection: 'fluent_commerce',
|
|
256
|
+
},
|
|
257
|
+
async ctx => {
|
|
258
|
+
// Primary connection (Fluent)
|
|
259
|
+
const fluentClient = await createClient(ctx);
|
|
260
|
+
|
|
261
|
+
// Access other connections
|
|
262
|
+
const s3Connection = ctx.connections?.aws_s3;
|
|
263
|
+
const sfccConnection = ctx.connections?.sfcc_api;
|
|
264
|
+
|
|
265
|
+
// Use multiple services
|
|
266
|
+
const fluentData = await fluentClient.graphql({ query: '...' });
|
|
267
|
+
const s3Data = await fetchFromS3WithConnection(s3Connection);
|
|
268
|
+
const sfccData = await fetchFromSFCCWithConnection(sfccConnection);
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
fluent: fluentData,
|
|
272
|
+
s3: s3Data,
|
|
273
|
+
sfcc: sfccData,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Connection Validation
|
|
282
|
+
|
|
283
|
+
### Pre-Deployment Validation
|
|
284
|
+
|
|
285
|
+
Before deploying, validate all connections:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { fn } from '@versori/run';
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Validate all required connections
|
|
292
|
+
*
|
|
293
|
+
* Run manually before deployment
|
|
294
|
+
*/
|
|
295
|
+
export const validateConnections = fn('validate-connections', async ctx => {
|
|
296
|
+
const requiredConnections = ['fluent_commerce', 'aws_s3', 'sfcc_api'];
|
|
297
|
+
|
|
298
|
+
const results = {};
|
|
299
|
+
|
|
300
|
+
for (const connName of requiredConnections) {
|
|
301
|
+
const conn = ctx.connections?.[connName];
|
|
302
|
+
|
|
303
|
+
if (!conn) {
|
|
304
|
+
results[connName] = {
|
|
305
|
+
status: 'missing',
|
|
306
|
+
error: 'Connection not configured',
|
|
307
|
+
};
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Basic validation
|
|
312
|
+
results[connName] = {
|
|
313
|
+
status: 'configured',
|
|
314
|
+
hasUrl: !!conn.url,
|
|
315
|
+
hasHeaders: !!conn.headers,
|
|
316
|
+
hasParams: !!conn.params,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
ctx.log('info', 'Connection validation complete', results);
|
|
321
|
+
|
|
322
|
+
return results;
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Runtime Connection Testing
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { http } from '@versori/run';
|
|
330
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Test Fluent Commerce connection
|
|
334
|
+
*
|
|
335
|
+
* GET https://{workspace}.versori.run/test-connection
|
|
336
|
+
*/
|
|
337
|
+
export const testConnection = http(
|
|
338
|
+
'test-connection',
|
|
339
|
+
{
|
|
340
|
+
connection: 'fluent_commerce',
|
|
341
|
+
},
|
|
342
|
+
async ctx => {
|
|
343
|
+
try {
|
|
344
|
+
const client = await createClient(ctx);
|
|
345
|
+
|
|
346
|
+
// Simple query to test connection
|
|
347
|
+
const result = await client.graphql({
|
|
348
|
+
query: `query { __typename }`,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
if (result.errors?.length) {
|
|
352
|
+
return {
|
|
353
|
+
success: false,
|
|
354
|
+
error: 'GraphQL error',
|
|
355
|
+
details: result.errors[0].message,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
ctx.log('info', 'Connection test successful');
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
success: true,
|
|
363
|
+
message: 'Connection working',
|
|
364
|
+
timestamp: new Date().toISOString(),
|
|
365
|
+
};
|
|
366
|
+
} catch (error) {
|
|
367
|
+
ctx.log('error', 'Connection test failed', {
|
|
368
|
+
error: error instanceof Error ? error.message : String(error),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: error instanceof Error ? error.message : 'Connection failed',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Security Best Practices
|
|
383
|
+
|
|
384
|
+
### 1. Never Hardcode Credentials
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// ❌ WRONG - Hardcoded credentials
|
|
388
|
+
export const bad = http(
|
|
389
|
+
'bad',
|
|
390
|
+
{
|
|
391
|
+
connection: 'fluent',
|
|
392
|
+
},
|
|
393
|
+
async ctx => {
|
|
394
|
+
const clientId = 'my-client-id'; // NEVER do this!
|
|
395
|
+
const clientSecret = 'my-secret'; // NEVER do this!
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// ✅ CORRECT - Use connection or environment variables
|
|
400
|
+
export const good = http(
|
|
401
|
+
'good',
|
|
402
|
+
{
|
|
403
|
+
connection: 'fluent_commerce',
|
|
404
|
+
},
|
|
405
|
+
async ctx => {
|
|
406
|
+
const client = await createClient(ctx); // Credentials from connection
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 2. Use Connector Variables for Secrets
|
|
412
|
+
|
|
413
|
+
Configure sensitive values in Versori UI:
|
|
414
|
+
|
|
415
|
+
**Versori Dashboard → Connector → Variables**:
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
Name: FLUENT_WEBHOOK_PUBLIC_KEY
|
|
419
|
+
Value: -----BEGIN PUBLIC KEY-----...
|
|
420
|
+
Type: Secret
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Access in code**:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
export const useSecrets = webhook('use-secrets', async ctx => {
|
|
427
|
+
// Access secret variable
|
|
428
|
+
const publicKey = ctx.vars?.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
429
|
+
|
|
430
|
+
if (!publicKey) {
|
|
431
|
+
return { error: 'Missing public key configuration' };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Use securely
|
|
435
|
+
const validator = new WebhookValidationService({ publicKey });
|
|
436
|
+
// ...
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 3. Environment-Specific Connections
|
|
441
|
+
|
|
442
|
+
Maintain separate connections for each environment:
|
|
443
|
+
|
|
444
|
+
```
|
|
445
|
+
Development:
|
|
446
|
+
- fluent_commerce_dev
|
|
447
|
+
- aws_s3_dev
|
|
448
|
+
|
|
449
|
+
Staging:
|
|
450
|
+
- fluent_commerce_staging
|
|
451
|
+
- aws_s3_staging
|
|
452
|
+
|
|
453
|
+
Production:
|
|
454
|
+
- fluent_commerce_prod
|
|
455
|
+
- aws_s3_prod
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Code pattern**:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
export const envAwareWorkflow = http(
|
|
462
|
+
'env-aware',
|
|
463
|
+
{
|
|
464
|
+
connection:
|
|
465
|
+
process.env.NODE_ENV === 'production' ? 'fluent_commerce_prod' : 'fluent_commerce_dev',
|
|
466
|
+
},
|
|
467
|
+
async ctx => {
|
|
468
|
+
const client = await createClient(ctx);
|
|
469
|
+
// ...
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 4. Rotate Credentials Regularly
|
|
475
|
+
|
|
476
|
+
- **OAuth2 Client Secrets**: Rotate every 90 days
|
|
477
|
+
- **API Keys**: Rotate every 180 days
|
|
478
|
+
- **Webhook Public Keys**: Rotate on security events
|
|
479
|
+
|
|
480
|
+
**Update process**:
|
|
481
|
+
|
|
482
|
+
1. Generate new credentials in source system
|
|
483
|
+
2. Update connection in Versori UI
|
|
484
|
+
3. Test connection
|
|
485
|
+
4. Deactivate old credentials after confirmation
|
|
486
|
+
|
|
487
|
+
### 5. Audit Connection Access
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
export const auditedWorkflow = http(
|
|
491
|
+
'audited',
|
|
492
|
+
{
|
|
493
|
+
connection: 'fluent_commerce',
|
|
494
|
+
},
|
|
495
|
+
async ctx => {
|
|
496
|
+
// Log connection usage
|
|
497
|
+
ctx.log('info', 'Connection accessed', {
|
|
498
|
+
workflow: 'audited',
|
|
499
|
+
connection: 'fluent_commerce',
|
|
500
|
+
timestamp: Date.now(),
|
|
501
|
+
executionId: ctx.executionId,
|
|
502
|
+
user: ctx.activation?.user || 'system',
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const client = await createClient(ctx);
|
|
506
|
+
// ... rest of workflow
|
|
507
|
+
}
|
|
508
|
+
);
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Accessing All Connections (v0.4.x+)
|
|
514
|
+
|
|
515
|
+
### NEW: activation.connections Array
|
|
516
|
+
|
|
517
|
+
As of @versori/run v0.4.x, you can access **all configured connections** for the current activation via `ctx.activation.connections`.
|
|
518
|
+
|
|
519
|
+
#### Connection Structure
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
type Connection = {
|
|
523
|
+
id: string; // Unique connection ID
|
|
524
|
+
name: string; // Connection name (matches UI)
|
|
525
|
+
templateId: string; // Connection template/type ID
|
|
526
|
+
dynamic: boolean; // Whether connection is dynamic
|
|
527
|
+
baseUrl: string; // Base URL for the connection
|
|
528
|
+
createdAt: string; // ISO timestamp
|
|
529
|
+
updatedAt: string; // ISO timestamp
|
|
530
|
+
credentials: ConnectionCredential[]; // Credential details
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
#### Listing All Available Connections
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import { fn } from '@versori/run';
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* List all connections for current activation
|
|
541
|
+
*/
|
|
542
|
+
export const listConnections = fn('list-connections', ctx => {
|
|
543
|
+
const connections = ctx.activation.connections;
|
|
544
|
+
|
|
545
|
+
if (!connections || connections.length === 0) {
|
|
546
|
+
return { message: 'No connections configured', count: 0 };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
ctx.log.info('Available connections', {
|
|
550
|
+
count: connections.length,
|
|
551
|
+
names: connections.map(c => c.name)
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
count: connections.length,
|
|
556
|
+
connections: connections.map(c => ({
|
|
557
|
+
name: c.name,
|
|
558
|
+
id: c.id,
|
|
559
|
+
baseUrl: c.baseUrl,
|
|
560
|
+
dynamic: c.dynamic,
|
|
561
|
+
createdAt: c.createdAt
|
|
562
|
+
}))
|
|
563
|
+
};
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
#### Finding Specific Connection
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
import { fn } from '@versori/run';
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Check if Fluent Commerce connection exists
|
|
574
|
+
*/
|
|
575
|
+
export const checkFluentConnection = fn('check-fluent-connection', ctx => {
|
|
576
|
+
const connections = ctx.activation.connections;
|
|
577
|
+
const fluentConn = connections?.find(c => c.name === 'fluent_commerce');
|
|
578
|
+
|
|
579
|
+
if (!fluentConn) {
|
|
580
|
+
throw new Error('Fluent Commerce connection not configured');
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
ctx.log.info('Fluent connection found', {
|
|
584
|
+
id: fluentConn.id,
|
|
585
|
+
baseUrl: fluentConn.baseUrl,
|
|
586
|
+
dynamic: fluentConn.dynamic
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
found: true,
|
|
591
|
+
connectionId: fluentConn.id,
|
|
592
|
+
baseUrl: fluentConn.baseUrl
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Multi-Tenant Pattern with activation.connections
|
|
598
|
+
|
|
599
|
+
**Use Case**: One connector serves multiple customers, each with their own Fluent instance.
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
import { webhook, fn, http } from '@versori/run';
|
|
603
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Multi-tenant order creation
|
|
607
|
+
*
|
|
608
|
+
* POST https://{workspace}.versori.run/create-order
|
|
609
|
+
* Body: { customerId: "customer-a", orderData: {...} }
|
|
610
|
+
*/
|
|
611
|
+
export const multiTenantOrderCreate = webhook('multi-tenant-order-create', {
|
|
612
|
+
response: { mode: 'sync' },
|
|
613
|
+
})
|
|
614
|
+
// Step 1: Route to correct Fluent connection (fn)
|
|
615
|
+
.then(fn('route-connection', ctx => {
|
|
616
|
+
const { customerId, orderData } = ctx.data;
|
|
617
|
+
|
|
618
|
+
if (!customerId) {
|
|
619
|
+
throw new Error('customerId is required');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Access all connections
|
|
623
|
+
const connections = ctx.activation.connections;
|
|
624
|
+
|
|
625
|
+
if (!connections || connections.length === 0) {
|
|
626
|
+
throw new Error('No connections configured for this activation');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Find customer-specific connection
|
|
630
|
+
// Convention: connections named fluent_customer-a, fluent_customer-b, etc.
|
|
631
|
+
const connectionName = `fluent_${customerId.toLowerCase()}`;
|
|
632
|
+
const connection = connections.find(c => c.name === connectionName);
|
|
633
|
+
|
|
634
|
+
if (!connection) {
|
|
635
|
+
throw new Error(`No Fluent connection found for customer: ${customerId}`);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
ctx.log.info('Routed to customer connection', {
|
|
639
|
+
customerId,
|
|
640
|
+
connectionName: connection.name,
|
|
641
|
+
connectionId: connection.id,
|
|
642
|
+
baseUrl: connection.baseUrl
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
connectionName: connection.name,
|
|
647
|
+
customerId,
|
|
648
|
+
orderData
|
|
649
|
+
};
|
|
650
|
+
}))
|
|
651
|
+
|
|
652
|
+
// Step 2: Create order in customer's Fluent instance (http)
|
|
653
|
+
.then(http('create-order', (ctx) => {
|
|
654
|
+
// Return dynamic connection based on routing
|
|
655
|
+
return { connection: ctx.data.connectionName };
|
|
656
|
+
}, async ctx => {
|
|
657
|
+
const { customerId, orderData } = ctx.data;
|
|
658
|
+
|
|
659
|
+
// Create client with customer-specific connection
|
|
660
|
+
const client = await createClient(ctx);
|
|
661
|
+
|
|
662
|
+
// Create order
|
|
663
|
+
const result = await client.graphql({
|
|
664
|
+
query: `
|
|
665
|
+
mutation CreateOrder($input: CreateOrderInput!) {
|
|
666
|
+
createOrder(input: $input) {
|
|
667
|
+
id
|
|
668
|
+
ref
|
|
669
|
+
status
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
`,
|
|
673
|
+
variables: { input: orderData }
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
if (result.errors?.length) {
|
|
677
|
+
throw new Error(`Order creation failed: ${result.errors[0].message}`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
ctx.log.info('Order created', {
|
|
681
|
+
customerId,
|
|
682
|
+
orderId: result.data.createOrder.id
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
success: true,
|
|
687
|
+
customerId,
|
|
688
|
+
orderId: result.data.createOrder.id,
|
|
689
|
+
orderRef: result.data.createOrder.ref
|
|
690
|
+
};
|
|
691
|
+
}))
|
|
692
|
+
|
|
693
|
+
.catch(({ data }) => ({
|
|
694
|
+
success: false,
|
|
695
|
+
error: data instanceof Error ? data.message : String(data),
|
|
696
|
+
status: 500
|
|
697
|
+
}));
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Iterating Through Multiple Connections
|
|
701
|
+
|
|
702
|
+
**Use Case**: Health check across all Fluent connections.
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
import { fn, http } from '@versori/run';
|
|
706
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Test all Fluent connections
|
|
710
|
+
*
|
|
711
|
+
* GET https://{workspace}.versori.run/test-all-connections
|
|
712
|
+
*/
|
|
713
|
+
export const testAllConnections = fn('test-all-fluent', async ctx => {
|
|
714
|
+
const connections = ctx.activation.connections;
|
|
715
|
+
|
|
716
|
+
// Filter to only Fluent connections (by name convention)
|
|
717
|
+
const fluentConnections = connections?.filter(c =>
|
|
718
|
+
c.name.startsWith('fluent_')
|
|
719
|
+
) || [];
|
|
720
|
+
|
|
721
|
+
if (fluentConnections.length === 0) {
|
|
722
|
+
return { message: 'No Fluent connections found', count: 0 };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
ctx.log.info('Testing Fluent connections', {
|
|
726
|
+
count: fluentConnections.length
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
const results = [];
|
|
730
|
+
|
|
731
|
+
// Test each connection
|
|
732
|
+
for (const conn of fluentConnections) {
|
|
733
|
+
try {
|
|
734
|
+
// Note: To actually test, we'd need to use http() with the connection
|
|
735
|
+
// This example shows discovery only
|
|
736
|
+
results.push({
|
|
737
|
+
name: conn.name,
|
|
738
|
+
id: conn.id,
|
|
739
|
+
baseUrl: conn.baseUrl,
|
|
740
|
+
status: 'configured',
|
|
741
|
+
available: true
|
|
742
|
+
});
|
|
743
|
+
} catch (error) {
|
|
744
|
+
results.push({
|
|
745
|
+
name: conn.name,
|
|
746
|
+
id: conn.id,
|
|
747
|
+
status: 'error',
|
|
748
|
+
available: false,
|
|
749
|
+
error: error instanceof Error ? error.message : String(error)
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
totalConnections: fluentConnections.length,
|
|
756
|
+
results
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
### Validation Pattern: Required Connections
|
|
762
|
+
|
|
763
|
+
```typescript
|
|
764
|
+
import { fn } from '@versori/run';
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Validate required connections exist
|
|
768
|
+
*
|
|
769
|
+
* Recommended to run at connector startup
|
|
770
|
+
*/
|
|
771
|
+
export const validateRequiredConnections = fn('validate-required', ctx => {
|
|
772
|
+
const required = ['fluent_commerce', 'aws_s3', 'sftp_warehouse'];
|
|
773
|
+
const connections = ctx.activation.connections || [];
|
|
774
|
+
const connectionNames = new Set(connections.map(c => c.name));
|
|
775
|
+
|
|
776
|
+
const missing = required.filter(name => !connectionNames.has(name));
|
|
777
|
+
|
|
778
|
+
if (missing.length > 0) {
|
|
779
|
+
throw new Error(`Missing required connections: ${missing.join(', ')}`);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
ctx.log.info('All required connections configured', {
|
|
783
|
+
required: required.length,
|
|
784
|
+
configured: connections.length
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
return {
|
|
788
|
+
valid: true,
|
|
789
|
+
requiredConnections: required,
|
|
790
|
+
totalConnections: connections.length
|
|
791
|
+
};
|
|
792
|
+
});
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
---
|
|
796
|
+
|
|
797
|
+
## Activation Environment & Dynamic Variables (v0.4.x+)
|
|
798
|
+
|
|
799
|
+
### activation.environment
|
|
800
|
+
|
|
801
|
+
Access the current activation's environment information:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
import { fn } from '@versori/run';
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Check environment and apply environment-specific logic
|
|
808
|
+
*/
|
|
809
|
+
export const envAwareWorkflow = fn('env-aware', ctx => {
|
|
810
|
+
const env = ctx.activation.environment;
|
|
811
|
+
|
|
812
|
+
if (!env) {
|
|
813
|
+
ctx.log.warn('No environment information available');
|
|
814
|
+
return { environment: 'unknown' };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
ctx.log.info('Environment details', {
|
|
818
|
+
id: env.id,
|
|
819
|
+
name: env.name,
|
|
820
|
+
type: env.type // e.g., 'development', 'staging', 'production'
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Conditional logic based on environment
|
|
824
|
+
if (env.name === 'production') {
|
|
825
|
+
// Use production-specific settings
|
|
826
|
+
return {
|
|
827
|
+
environment: 'production',
|
|
828
|
+
batchSize: 1000,
|
|
829
|
+
retryAttempts: 5,
|
|
830
|
+
timeout: 60000
|
|
831
|
+
};
|
|
832
|
+
} else if (env.name === 'staging') {
|
|
833
|
+
return {
|
|
834
|
+
environment: 'staging',
|
|
835
|
+
batchSize: 100,
|
|
836
|
+
retryAttempts: 3,
|
|
837
|
+
timeout: 30000
|
|
838
|
+
};
|
|
839
|
+
} else {
|
|
840
|
+
return {
|
|
841
|
+
environment: 'development',
|
|
842
|
+
batchSize: 10,
|
|
843
|
+
retryAttempts: 1,
|
|
844
|
+
timeout: 10000
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### activation.dynamicVariables
|
|
851
|
+
|
|
852
|
+
Access dynamic variables set at the activation level:
|
|
853
|
+
|
|
854
|
+
```typescript
|
|
855
|
+
import { fn } from '@versori/run';
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Access activation-level dynamic variables
|
|
859
|
+
*/
|
|
860
|
+
export const useDynamicVars = fn('use-dynamic-vars', ctx => {
|
|
861
|
+
// Get variables using getVariable
|
|
862
|
+
const apiKey = ctx.activation.getVariable<string>('API_KEY');
|
|
863
|
+
const retailerId = ctx.activation.getVariable<number>('RETAILER_ID');
|
|
864
|
+
const region = ctx.activation.getVariable<string>('AWS_REGION');
|
|
865
|
+
|
|
866
|
+
ctx.log.info('Dynamic variables', {
|
|
867
|
+
hasApiKey: !!apiKey,
|
|
868
|
+
retailerId,
|
|
869
|
+
region
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// Use variables in your workflow
|
|
873
|
+
return {
|
|
874
|
+
retailerId,
|
|
875
|
+
region,
|
|
876
|
+
configured: !!apiKey && !!retailerId
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
### Setting Variables Programmatically
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
import { http } from '@versori/run';
|
|
885
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Cache frequently-used data in activation variables
|
|
889
|
+
*/
|
|
890
|
+
export const cacheRetailerConfig = http(
|
|
891
|
+
'cache-retailer-config',
|
|
892
|
+
{ connection: 'fluent_commerce' },
|
|
893
|
+
async ctx => {
|
|
894
|
+
const client = await createClient(ctx);
|
|
895
|
+
|
|
896
|
+
// Query Fluent for retailer configuration
|
|
897
|
+
const result = await client.graphql({
|
|
898
|
+
query: `
|
|
899
|
+
query GetRetailer($id: Int!) {
|
|
900
|
+
retailer(id: $id) {
|
|
901
|
+
id
|
|
902
|
+
name
|
|
903
|
+
tradingName
|
|
904
|
+
supportEmail
|
|
905
|
+
primaryLocation {
|
|
906
|
+
ref
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
`,
|
|
911
|
+
variables: { id: 1 }
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
const retailer = result.data.retailer;
|
|
915
|
+
|
|
916
|
+
// Cache in activation variables for future workflows
|
|
917
|
+
await ctx.activation.setVariable('CACHED_RETAILER_ID', retailer.id);
|
|
918
|
+
await ctx.activation.setVariable('CACHED_RETAILER_NAME', retailer.name);
|
|
919
|
+
await ctx.activation.setVariable('CACHED_PRIMARY_LOCATION', retailer.primaryLocation.ref);
|
|
920
|
+
await ctx.activation.setVariable('LAST_CONFIG_REFRESH', Date.now());
|
|
921
|
+
|
|
922
|
+
ctx.log.info('Retailer config cached', {
|
|
923
|
+
retailerId: retailer.id,
|
|
924
|
+
retailerName: retailer.name
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
return {
|
|
928
|
+
cached: true,
|
|
929
|
+
retailer: {
|
|
930
|
+
id: retailer.id,
|
|
931
|
+
name: retailer.name
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
);
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
### Complete ActivationImpl Reference
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
interface ActivationImpl {
|
|
942
|
+
// Properties
|
|
943
|
+
get id(): string; // Activation ID
|
|
944
|
+
get user(): EndUser; // User info
|
|
945
|
+
get environment(): ProjectEnvironment | undefined; // ✅ NEW in v0.4.x
|
|
946
|
+
get connections(): Array<Connection> | undefined; // ✅ NEW in v0.4.x
|
|
947
|
+
get dynamicVariables(): DynamicVariables | undefined; // ✅ NEW in v0.4.x
|
|
948
|
+
|
|
949
|
+
// Methods
|
|
950
|
+
getVariable<T = unknown>(name: string): T | undefined;
|
|
951
|
+
setVariable(name: string, value: unknown): Promise<void>;
|
|
952
|
+
}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Troubleshooting Connection Issues
|
|
958
|
+
|
|
959
|
+
### Common Issues & Solutions
|
|
960
|
+
|
|
961
|
+
| Issue | Symptoms | Solution |
|
|
962
|
+
| ------------------------ | ------------------------------------ | ---------------------------------------------------------- |
|
|
963
|
+
| **Connection not found** | `Error: Connection 'xyz' not found` | Verify connection name matches UI exactly (case-sensitive) |
|
|
964
|
+
| **Token expired** | `401 Unauthorized` | Versori should auto-refresh; check token URL configuration |
|
|
965
|
+
| **Invalid credentials** | `403 Forbidden` | Verify Client ID/Secret in Fluent dashboard |
|
|
966
|
+
| **Missing retailerId** | `retailerId is required for job creation` | Use `client.setRetailerId('1')` or pass in method call. See [retailerId Configuration Guide](../../../../00-START-HERE/retailerid-configuration.md) |
|
|
967
|
+
| **CORS errors** | `CORS policy blocked` | Enable CORS in webhook configuration |
|
|
968
|
+
| **Rate limiting** | `429 Too Many Requests` | Implement retry with backoff, check Fluent API limits |
|
|
969
|
+
|
|
970
|
+
### Debug Connection Configuration
|
|
971
|
+
|
|
972
|
+
```typescript
|
|
973
|
+
export const debugConnection = http(
|
|
974
|
+
'debug-connection',
|
|
975
|
+
{
|
|
976
|
+
connection: 'fluent_commerce',
|
|
977
|
+
},
|
|
978
|
+
async ctx => {
|
|
979
|
+
const conn = ctx.activation?.connection;
|
|
980
|
+
|
|
981
|
+
// Log all connection details (except secrets)
|
|
982
|
+
ctx.log('debug', 'Connection configuration', {
|
|
983
|
+
hasConnection: !!conn,
|
|
984
|
+
url: conn?.url,
|
|
985
|
+
headerKeys: conn?.headers ? Object.keys(conn.headers) : [],
|
|
986
|
+
paramKeys: conn?.params ? Object.keys(conn.params) : [],
|
|
987
|
+
retailerId: conn?.params?.retailerId,
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
// Test basic connectivity
|
|
991
|
+
try {
|
|
992
|
+
const response = await ctx.fetch(conn?.url || 'https://api.fluentcommerce.com', {
|
|
993
|
+
method: 'HEAD',
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
return {
|
|
997
|
+
connectionStatus: 'reachable',
|
|
998
|
+
statusCode: response.status,
|
|
999
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
1000
|
+
};
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
return {
|
|
1003
|
+
connectionStatus: 'unreachable',
|
|
1004
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
### Validate OAuth2 Token
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
export const validateToken = http(
|
|
1015
|
+
'validate-token',
|
|
1016
|
+
{
|
|
1017
|
+
connection: 'fluent_commerce',
|
|
1018
|
+
},
|
|
1019
|
+
async ctx => {
|
|
1020
|
+
const conn = ctx.activation?.connection;
|
|
1021
|
+
|
|
1022
|
+
// Extract token from headers (Versori auto-includes)
|
|
1023
|
+
const authHeader = conn?.headers?.['Authorization'];
|
|
1024
|
+
|
|
1025
|
+
if (!authHeader) {
|
|
1026
|
+
return { error: 'No authorization header found' };
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Parse token (Bearer <token>)
|
|
1030
|
+
const token = authHeader.replace('Bearer ', '');
|
|
1031
|
+
|
|
1032
|
+
// Decode JWT (if applicable)
|
|
1033
|
+
try {
|
|
1034
|
+
const parts = token.split('.');
|
|
1035
|
+
if (parts.length === 3) {
|
|
1036
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
1037
|
+
|
|
1038
|
+
return {
|
|
1039
|
+
tokenValid: true,
|
|
1040
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1000).toISOString() : 'unknown',
|
|
1041
|
+
issuedAt: payload.iat ? new Date(payload.iat * 1000).toISOString() : 'unknown',
|
|
1042
|
+
scopes: payload.scope || [],
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
return {
|
|
1047
|
+
tokenValid: false,
|
|
1048
|
+
error: 'Invalid JWT format',
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return { tokenValid: true, tokenLength: token.length };
|
|
1053
|
+
}
|
|
1054
|
+
);
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
## Advanced Connection Patterns
|
|
1060
|
+
|
|
1061
|
+
### Pattern 1: Connection Fallback
|
|
1062
|
+
|
|
1063
|
+
```typescript
|
|
1064
|
+
export const withFallback = http(
|
|
1065
|
+
'connection-fallback',
|
|
1066
|
+
{
|
|
1067
|
+
connection: 'fluent_commerce_primary',
|
|
1068
|
+
},
|
|
1069
|
+
async ctx => {
|
|
1070
|
+
try {
|
|
1071
|
+
// Try primary connection
|
|
1072
|
+
const client = await createClient(ctx);
|
|
1073
|
+
return await client.graphql({ query: '...' });
|
|
1074
|
+
} catch (primaryError) {
|
|
1075
|
+
ctx.log('warn', 'Primary connection failed, trying fallback');
|
|
1076
|
+
|
|
1077
|
+
try {
|
|
1078
|
+
// Fallback to secondary connection
|
|
1079
|
+
const fallbackClient = await createClient({
|
|
1080
|
+
connection: ctx.connections?.fluent_commerce_secondary,
|
|
1081
|
+
log: ctx.log,
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
return await fallbackClient.graphql({ query: '...' });
|
|
1085
|
+
} catch (fallbackError) {
|
|
1086
|
+
throw new Error('All connections failed');
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
);
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
### Pattern 2: Dynamic Connection Selection
|
|
1094
|
+
|
|
1095
|
+
```typescript
|
|
1096
|
+
export const dynamicConnection = http('dynamic-connection', async ctx => {
|
|
1097
|
+
// Select connection based on request parameter
|
|
1098
|
+
const environment = ctx.query?.env || 'production';
|
|
1099
|
+
|
|
1100
|
+
const connectionName =
|
|
1101
|
+
environment === 'staging' ? 'fluent_commerce_staging' : 'fluent_commerce_prod';
|
|
1102
|
+
|
|
1103
|
+
ctx.log('info', 'Selected connection', { connectionName, environment });
|
|
1104
|
+
|
|
1105
|
+
// Note: This pattern requires accessing ctx.connections
|
|
1106
|
+
const client = await createClient({
|
|
1107
|
+
connection: ctx.connections?.[connectionName],
|
|
1108
|
+
log: ctx.log,
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
return await client.graphql({ query: '...' });
|
|
1112
|
+
});
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
---
|
|
1116
|
+
|
|
1117
|
+
## Key Takeaways
|
|
1118
|
+
|
|
1119
|
+
- 🎯 **OAuth2 connections** are managed by Versori - automatic token refresh
|
|
1120
|
+
- 🎯 **Connection name** in code must match UI exactly (case-sensitive)
|
|
1121
|
+
- 🎯 **Never hardcode** credentials - use connections or environment variables
|
|
1122
|
+
- 🎯 **Multi-connection scenarios** use primary connection + ctx.connections
|
|
1123
|
+
- 🎯 **Validate connections** before deployment with test workflows
|
|
1124
|
+
- 🎯 **Security best practices**: rotate credentials, use secrets, audit access
|
|
1125
|
+
- 🎯 **Troubleshooting**: check name match, token expiration, credential validity
|
|
1126
|
+
|
|
1127
|
+
---
|
|
1128
|
+
|
|
1129
|
+
## Practice Exercise
|
|
1130
|
+
|
|
1131
|
+
Create a workflow that:
|
|
1132
|
+
|
|
1133
|
+
1. Uses OAuth2 connection to Fluent Commerce
|
|
1134
|
+
2. Validates connection on startup
|
|
1135
|
+
3. Falls back to secondary connection on failure
|
|
1136
|
+
4. Logs all connection attempts for audit
|
|
1137
|
+
5. Returns connection health status
|
|
1138
|
+
|
|
1139
|
+
**Hints**:
|
|
1140
|
+
|
|
1141
|
+
- Use `http()` with primary connection
|
|
1142
|
+
- Implement try/catch for fallback
|
|
1143
|
+
- Use `ctx.log()` for audit trail
|
|
1144
|
+
- Test token validity before GraphQL calls
|
|
1145
|
+
|
|
1146
|
+
**Solution** available in [Module 8: Best Practices](./platforms-versori-08-best-practices.md#practice-solutions)
|
|
1147
|
+
|
|
1148
|
+
---
|
|
1149
|
+
|
|
1150
|
+
## Next Steps
|
|
1151
|
+
|
|
1152
|
+
Now that you understand connection management, let's explore state management with KV storage.
|
|
1153
|
+
|
|
1154
|
+
Continue to [Module 6: KV Storage →](./platforms-versori-06-kv-storage.md) to learn about distributed state, file tracking, and caching patterns.
|
|
1155
|
+
|
|
1156
|
+
---
|
|
1157
|
+
|
|
1158
|
+
## Related Documentation
|
|
1159
|
+
|
|
1160
|
+
- [Module 3: Authentication](./platforms-versori-03-authentication.md) - OAuth2 basics
|
|
1161
|
+
- [Module 4: Workflows](./platforms-versori-04-workflows.md) - Using connections in workflows
|
|
1162
|
+
- [Module 6: KV Storage](./platforms-versori-06-kv-storage.md) - State management
|
|
1163
|
+
- [Module 8: Best Practices](./platforms-versori-08-best-practices.md) - Security hardening
|
|
1164
|
+
|
|
1165
|
+
---
|
|
1166
|
+
|
|
1167
|
+
[← Previous: Module 4](./platforms-versori-04-workflows.md) | [Back to Guide](../platforms-versori-readme.md) | [Next: Module 6: KV Storage →](./platforms-versori-06-kv-storage.md)
|