@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -2
- package/README.md +39 -0
- package/dist/cjs/auth/index.d.ts +3 -0
- package/dist/cjs/auth/index.js +13 -0
- package/dist/cjs/auth/profile-loader.d.ts +18 -0
- package/dist/cjs/auth/profile-loader.js +208 -0
- package/dist/cjs/client-factory.d.ts +4 -0
- package/dist/cjs/client-factory.js +10 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/auth/index.d.ts +3 -0
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/profile-loader.d.ts +18 -0
- package/dist/esm/auth/profile-loader.js +169 -0
- package/dist/esm/client-factory.d.ts +4 -0
- package/dist/esm/client-factory.js +9 -0
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/auth/index.d.ts +3 -0
- package/dist/types/auth/profile-loader.d.ts +18 -0
- package/dist/types/client-factory.d.ts +4 -0
- package/dist/types/index.d.ts +3 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -482
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
|
@@ -1,1160 +1,1160 @@
|
|
|
1
|
-
# Versori Webhook: XML/HTML Response Patterns
|
|
2
|
-
|
|
3
|
-
**FC Connect SDK Use Case Guide**
|
|
4
|
-
|
|
5
|
-
> **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
|
|
6
|
-
> **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
|
|
7
|
-
|
|
8
|
-
**Context**: Return non-JSON content (XML, HTML, CSV) from Versori HTTP webhooks
|
|
9
|
-
|
|
10
|
-
**Complexity**: Low-Medium
|
|
11
|
-
|
|
12
|
-
**Runtime**: Versori Platform
|
|
13
|
-
|
|
14
|
-
**Estimated Lines**: ~300 lines
|
|
15
|
-
|
|
16
|
-
## What You'll Build
|
|
17
|
-
|
|
18
|
-
- Versori webhook with XML response
|
|
19
|
-
- Custom onSuccess/onError handlers
|
|
20
|
-
- Response object with proper Content-Type
|
|
21
|
-
- XMLBuilder for generating XML
|
|
22
|
-
- Error responses in XML format
|
|
23
|
-
|
|
24
|
-
## SDK Methods Used
|
|
25
|
-
|
|
26
|
-
- `webhook('name', { response: { mode, onSuccess, onError } })` - Versori webhook config
|
|
27
|
-
- `XMLBuilder(options)` - Build XML responses
|
|
28
|
-
- `builder.build(data)` - Generate XML string
|
|
29
|
-
- `new Response(body, { status, headers })` - Custom response objects
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Versori Workflows Structure
|
|
34
|
-
|
|
35
|
-
**Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
|
|
36
|
-
|
|
37
|
-
**Trigger Types:**
|
|
38
|
-
- **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
|
|
39
|
-
- **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
|
|
40
|
-
- **`http()`** → External API calls (chained from webhook/schedule)
|
|
41
|
-
- **`fn()`** → Internal processing (chained from webhook/schedule)
|
|
42
|
-
|
|
43
|
-
### Recommended Project Structure
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
xml-response-patterns/
|
|
47
|
-
├── index.ts # Entry point - exports all workflows
|
|
48
|
-
└── src/
|
|
49
|
-
├── workflows/
|
|
50
|
-
│ └── webhook/
|
|
51
|
-
│ └── xml-response.ts # Webhook: Return XML responses
|
|
52
|
-
│
|
|
53
|
-
├── services/
|
|
54
|
-
│ └── xml-builder.service.ts # Shared XML building logic (reusable)
|
|
55
|
-
│
|
|
56
|
-
└── config/
|
|
57
|
-
└── xml-templates.json # XML templates
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**Benefits:**
|
|
61
|
-
- ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
|
|
62
|
-
- ✅ Descriptive file names (easy to browse and understand)
|
|
63
|
-
- ✅ Scalable (add new workflows without cluttering)
|
|
64
|
-
- ✅ Reusable code in `services/` (DRY principle)
|
|
65
|
-
- ✅ Easy to modify individual workflows without affecting others
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## The Critical Problem
|
|
70
|
-
|
|
71
|
-
By default, Versori webhooks **JSON-encode all responses**. This breaks non-JSON content types like XML, HTML, and CSV.
|
|
72
|
-
|
|
73
|
-
### The Wrong Way (Default Behavior)
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
import { webhook, fn } from '@versori/run';
|
|
77
|
-
|
|
78
|
-
// ❌ PROBLEM: Returns JSON-encoded XML string
|
|
79
|
-
export const badExample = webhook('xml-endpoint', async (ctx) => {
|
|
80
|
-
return '<?xml version="1.0"?><order><id>123</id></order>';
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Response Body: "<?xml version=\"1.0\"?><order><id>123</id></order>"
|
|
84
|
-
// Content-Type: application/json
|
|
85
|
-
// Problem: XML is wrapped in JSON string with escaped quotes!
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**What happens:**
|
|
89
|
-
|
|
90
|
-
1. Your workflow returns an XML string
|
|
91
|
-
2. Versori's default handler runs `JSON.stringify()` on it
|
|
92
|
-
3. Client receives a JSON-encoded string instead of raw XML
|
|
93
|
-
4. Content-Type is set to `application/json` instead of `application/xml`
|
|
94
|
-
|
|
95
|
-
### The Right Way (Custom Response Objects)
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import { webhook, fn } from '@versori/run';
|
|
99
|
-
|
|
100
|
-
// ✅ SOLUTION: Returns raw XML
|
|
101
|
-
export const goodExample = webhook('xml-endpoint', {
|
|
102
|
-
response: {
|
|
103
|
-
mode: 'sync',
|
|
104
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
105
|
-
status: 200,
|
|
106
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
107
|
-
}),
|
|
108
|
-
onError: (ctx) => new Response(ctx.data, {
|
|
109
|
-
status: 500,
|
|
110
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
.then(fn('generate-xml', () => {
|
|
115
|
-
return '<?xml version="1.0"?><order><id>123</id></order>';
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
|
-
// Response Body: <?xml version="1.0"?><order><id>123</id></order>
|
|
119
|
-
// Content-Type: application/xml; charset=utf-8
|
|
120
|
-
// Success: Raw XML streamed directly to client!
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Why this works:**
|
|
124
|
-
|
|
125
|
-
- Custom handlers return `Response` objects
|
|
126
|
-
- Versori's `sendResponse()` function streams Response bodies directly via Node.js `pipeline()`
|
|
127
|
-
- No JSON encoding is applied to Response objects
|
|
128
|
-
- Content-Type header is preserved exactly as specified
|
|
129
|
-
|
|
130
|
-
## Complete Working Examples
|
|
131
|
-
|
|
132
|
-
### Example 1: Basic JSON Response (Default Behavior)
|
|
133
|
-
|
|
134
|
-
For JSON responses, no custom handlers are needed:
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
import { webhook, fn } from '@versori/run';
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Default JSON response - no custom handlers needed
|
|
141
|
-
* Versori automatically JSON-encodes and sets Content-Type: application/json
|
|
142
|
-
*/
|
|
143
|
-
export const jsonEndpoint = webhook('json-data', async (ctx) => {
|
|
144
|
-
return {
|
|
145
|
-
orderId: '12345',
|
|
146
|
-
status: 'COMPLETED',
|
|
147
|
-
items: [
|
|
148
|
-
{ sku: 'ABC123', quantity: 2 },
|
|
149
|
-
{ sku: 'DEF456', quantity: 1 }
|
|
150
|
-
]
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Response Body: {"orderId":"12345","status":"COMPLETED","items":[...]}
|
|
155
|
-
// Content-Type: application/json
|
|
156
|
-
// ✅ Perfect for JSON data!
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Example 2: XML Response with XMLBuilder
|
|
160
|
-
|
|
161
|
-
Full XML response workflow with proper error handling:
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import { webhook, fn, http } from '@versori/run';
|
|
165
|
-
// FC Connect SDK
|
|
166
|
-
// Install: npm install @fluentcommerce/fc-connect-sdk@latest
|
|
167
|
-
// Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
|
|
168
|
-
// GitHub: https://github.com/fluentcommerce/fc-connect-sdk
|
|
169
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
170
|
-
import { XMLBuilder } from 'fast-xml-parser';
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* XML Response Webhook
|
|
174
|
-
*
|
|
175
|
-
* CRITICAL PATTERNS:
|
|
176
|
-
* 1. Configure custom onSuccess/onError handlers
|
|
177
|
-
* 2. Both handlers return Response objects
|
|
178
|
-
* 3. Set Content-Type: application/xml
|
|
179
|
-
* 4. Workflow steps return raw strings (not Response objects)
|
|
180
|
-
* 5. Error handler also returns XML
|
|
181
|
-
*/
|
|
182
|
-
export const orderXmlEndpoint = webhook('order-xml', {
|
|
183
|
-
response: {
|
|
184
|
-
mode: 'sync',
|
|
185
|
-
// Success handler - wraps XML string in Response object
|
|
186
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
187
|
-
status: 200,
|
|
188
|
-
headers: {
|
|
189
|
-
'Content-Type': 'application/xml; charset=utf-8',
|
|
190
|
-
'X-Execution-Id': ctx.executionId,
|
|
191
|
-
'Cache-Control': 'no-cache'
|
|
192
|
-
}
|
|
193
|
-
}),
|
|
194
|
-
// Error handler - also returns XML format
|
|
195
|
-
onError: (ctx) => new Response(ctx.data, {
|
|
196
|
-
status: 500,
|
|
197
|
-
headers: {
|
|
198
|
-
'Content-Type': 'application/xml; charset=utf-8',
|
|
199
|
-
'X-Execution-Id': ctx.executionId
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
},
|
|
203
|
-
cors: true
|
|
204
|
-
})
|
|
205
|
-
// Step 1: Extract order ID from request
|
|
206
|
-
.then(fn('parse-request', ({ data }) => {
|
|
207
|
-
// Versori auto-parses XML when Content-Type is application/xml
|
|
208
|
-
const orderId = data?.OrderRequest?.OrderId;
|
|
209
|
-
if (!orderId) {
|
|
210
|
-
throw new Error('OrderId missing from request');
|
|
211
|
-
}
|
|
212
|
-
return { orderId };
|
|
213
|
-
}))
|
|
214
|
-
// Step 2: Fetch order from Fluent Commerce
|
|
215
|
-
.then(http('fetch-order', {
|
|
216
|
-
connection: 'fluent_commerce'
|
|
217
|
-
}, async (ctx) => {
|
|
218
|
-
const client = await createClient(ctx);
|
|
219
|
-
const result = await client.graphql({
|
|
220
|
-
query: `query GetOrder($ref: String) {
|
|
221
|
-
order(ref: $ref) {
|
|
222
|
-
id
|
|
223
|
-
ref
|
|
224
|
-
status
|
|
225
|
-
totalPrice
|
|
226
|
-
customer {
|
|
227
|
-
firstName
|
|
228
|
-
lastName
|
|
229
|
-
username
|
|
230
|
-
}
|
|
231
|
-
items(first: 50) {
|
|
232
|
-
edges {
|
|
233
|
-
node {
|
|
234
|
-
ref
|
|
235
|
-
quantity
|
|
236
|
-
price
|
|
237
|
-
product {
|
|
238
|
-
ref
|
|
239
|
-
name
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}`,
|
|
246
|
-
variables: { ref: ctx.data.orderId }
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
if (!result.data?.order) {
|
|
250
|
-
throw new Error(`Order not found: ${ctx.data.orderId}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return { order: result.data.order };
|
|
254
|
-
}))
|
|
255
|
-
// Step 3: Transform to XML format
|
|
256
|
-
.then(fn('build-xml', ({ data }) => {
|
|
257
|
-
const { order } = data;
|
|
258
|
-
|
|
259
|
-
// Initialize XML Builder
|
|
260
|
-
const builder = new XMLBuilder({
|
|
261
|
-
ignoreAttributes: false,
|
|
262
|
-
attributeNamePrefix: '@',
|
|
263
|
-
format: true,
|
|
264
|
-
indentBy: ' ',
|
|
265
|
-
suppressEmptyNode: true
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Build XML structure
|
|
269
|
-
const xmlObject = {
|
|
270
|
-
'?xml': {
|
|
271
|
-
'@version': '1.0',
|
|
272
|
-
'@encoding': 'UTF-8'
|
|
273
|
-
},
|
|
274
|
-
OrderResponse: {
|
|
275
|
-
'@xmlns': 'http://api.example.com/schema/order/1.0',
|
|
276
|
-
Order: {
|
|
277
|
-
OrderId: order.ref,
|
|
278
|
-
Status: order.status,
|
|
279
|
-
TotalPrice: {
|
|
280
|
-
'@currency': 'USD',
|
|
281
|
-
'#text': order.totalPrice
|
|
282
|
-
},
|
|
283
|
-
Customer: {
|
|
284
|
-
FirstName: order.customer.firstName,
|
|
285
|
-
LastName: order.customer.lastName,
|
|
286
|
-
Email: order.customer.username
|
|
287
|
-
},
|
|
288
|
-
Items: {
|
|
289
|
-
Item: order.items.edges.map(edge => ({
|
|
290
|
-
SKU: edge.node.product.ref,
|
|
291
|
-
ProductName: edge.node.product.name,
|
|
292
|
-
Quantity: edge.node.quantity,
|
|
293
|
-
Price: edge.node.price
|
|
294
|
-
}))
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
// Generate XML string
|
|
301
|
-
const xmlString = builder.build(xmlObject);
|
|
302
|
-
|
|
303
|
-
// Return XML string - onSuccess handler will wrap it
|
|
304
|
-
return xmlString;
|
|
305
|
-
}))
|
|
306
|
-
// Error handling - return XML error format
|
|
307
|
-
.catch(({ data }) => {
|
|
308
|
-
const errorMessage = data instanceof Error ? data.message : String(data);
|
|
309
|
-
|
|
310
|
-
// Return error XML string - onError handler will wrap it
|
|
311
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
312
|
-
<OrderResponse xmlns="http://api.example.com/schema/order/1.0">
|
|
313
|
-
<Error>
|
|
314
|
-
<Code>PROCESSING_ERROR</Code>
|
|
315
|
-
<Message>${errorMessage}</Message>
|
|
316
|
-
<Timestamp>${new Date().toISOString()}</Timestamp>
|
|
317
|
-
</Error>
|
|
318
|
-
</OrderResponse>`;
|
|
319
|
-
});
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Testing this endpoint:**
|
|
323
|
-
|
|
324
|
-
```bash
|
|
325
|
-
# Send XML request
|
|
326
|
-
curl -X POST https://your-workspace.versori.run/order-xml \
|
|
327
|
-
-H "Content-Type: application/xml" \
|
|
328
|
-
-d '<?xml version="1.0"?>
|
|
329
|
-
<OrderRequest>
|
|
330
|
-
<OrderId>ORD-12345</OrderId>
|
|
331
|
-
</OrderRequest>'
|
|
332
|
-
|
|
333
|
-
# Success Response (200 OK):
|
|
334
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
335
|
-
<OrderResponse xmlns="http://api.example.com/schema/order/1.0">
|
|
336
|
-
<Order>
|
|
337
|
-
<OrderId>ORD-12345</OrderId>
|
|
338
|
-
<Status>COMPLETED</Status>
|
|
339
|
-
<TotalPrice currency="USD">199.99</TotalPrice>
|
|
340
|
-
...
|
|
341
|
-
</Order>
|
|
342
|
-
</OrderResponse>
|
|
343
|
-
|
|
344
|
-
# Error Response (500 Internal Server Error):
|
|
345
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
346
|
-
<OrderResponse xmlns="http://api.example.com/schema/order/1.0">
|
|
347
|
-
<Error>
|
|
348
|
-
<Code>PROCESSING_ERROR</Code>
|
|
349
|
-
<Message>Order not found: ORD-12345</Message>
|
|
350
|
-
<Timestamp>2025-01-15T10:30:00Z</Timestamp>
|
|
351
|
-
</Error>
|
|
352
|
-
</OrderResponse>
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
### Example 3: HTML Response
|
|
356
|
-
|
|
357
|
-
Return HTML pages from webhooks (useful for status pages, documentation):
|
|
358
|
-
|
|
359
|
-
```typescript
|
|
360
|
-
import { webhook, fn } from '@versori/run';
|
|
361
|
-
|
|
362
|
-
export const statusPage = webhook('status', {
|
|
363
|
-
response: {
|
|
364
|
-
mode: 'sync',
|
|
365
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
366
|
-
status: 200,
|
|
367
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
368
|
-
})
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
.then(fn('generate-html', () => {
|
|
372
|
-
return `<!DOCTYPE html>
|
|
373
|
-
<html lang="en">
|
|
374
|
-
<head>
|
|
375
|
-
<meta charset="UTF-8">
|
|
376
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
377
|
-
<title>Service Status</title>
|
|
378
|
-
<style>
|
|
379
|
-
body {
|
|
380
|
-
font-family: Arial, sans-serif;
|
|
381
|
-
max-width: 800px;
|
|
382
|
-
margin: 50px auto;
|
|
383
|
-
padding: 20px;
|
|
384
|
-
}
|
|
385
|
-
.status {
|
|
386
|
-
padding: 20px;
|
|
387
|
-
background-color: #4CAF50;
|
|
388
|
-
color: white;
|
|
389
|
-
border-radius: 5px;
|
|
390
|
-
}
|
|
391
|
-
.service {
|
|
392
|
-
margin: 10px 0;
|
|
393
|
-
padding: 10px;
|
|
394
|
-
border: 1px solid #ddd;
|
|
395
|
-
border-radius: 3px;
|
|
396
|
-
}
|
|
397
|
-
</style>
|
|
398
|
-
</head>
|
|
399
|
-
<body>
|
|
400
|
-
<h1>Service Status Dashboard</h1>
|
|
401
|
-
<div class="status">
|
|
402
|
-
<h2>✓ All Systems Operational</h2>
|
|
403
|
-
</div>
|
|
404
|
-
<div class="service">
|
|
405
|
-
<h3>Inventory Ingestion</h3>
|
|
406
|
-
<p>Status: <strong>Operational</strong></p>
|
|
407
|
-
<p>Last Run: ${new Date().toISOString()}</p>
|
|
408
|
-
</div>
|
|
409
|
-
<div class="service">
|
|
410
|
-
<h3>Order Processing</h3>
|
|
411
|
-
<p>Status: <strong>Operational</strong></p>
|
|
412
|
-
</div>
|
|
413
|
-
</body>
|
|
414
|
-
</html>`;
|
|
415
|
-
}));
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### Example 4: CSV Response
|
|
419
|
-
|
|
420
|
-
Download data as CSV files:
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
import { webhook, fn, http } from '@versori/run';
|
|
424
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
425
|
-
|
|
426
|
-
export const exportInventory = webhook('export-inventory-csv', {
|
|
427
|
-
response: {
|
|
428
|
-
mode: 'sync',
|
|
429
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
430
|
-
status: 200,
|
|
431
|
-
headers: {
|
|
432
|
-
'Content-Type': 'text/csv; charset=utf-8',
|
|
433
|
-
'Content-Disposition': 'attachment; filename="inventory-export.csv"'
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
}
|
|
437
|
-
})
|
|
438
|
-
.then(http('fetch-inventory', {
|
|
439
|
-
connection: 'fluent_commerce'
|
|
440
|
-
}, async (ctx) => {
|
|
441
|
-
const client = await createClient(ctx);
|
|
442
|
-
const result = await client.graphql({
|
|
443
|
-
query: `query {
|
|
444
|
-
inventoryQuantities(first: 100) {
|
|
445
|
-
edges {
|
|
446
|
-
node {
|
|
447
|
-
ref
|
|
448
|
-
quantity
|
|
449
|
-
location { ref }
|
|
450
|
-
product { ref name }
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}`
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
return { items: result.data.inventoryQuantities.edges };
|
|
458
|
-
}))
|
|
459
|
-
.then(fn('build-csv', ({ data }) => {
|
|
460
|
-
// CSV Header
|
|
461
|
-
let csv = 'SKU,Product Name,Location,Quantity\n';
|
|
462
|
-
|
|
463
|
-
// CSV Rows
|
|
464
|
-
data.items.forEach(edge => {
|
|
465
|
-
const item = edge.node;
|
|
466
|
-
const sku = item.product?.ref || '';
|
|
467
|
-
const name = (item.product?.name || '').replace(/"/g, '""'); // Escape quotes
|
|
468
|
-
const location = item.location?.ref || '';
|
|
469
|
-
const qty = item.quantity || 0;
|
|
470
|
-
|
|
471
|
-
csv += `"${sku}","${name}","${location}",${qty}\n`;
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
return csv;
|
|
475
|
-
}));
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### Example 5: Plain Text Response
|
|
479
|
-
|
|
480
|
-
Health check or simple text endpoints:
|
|
481
|
-
|
|
482
|
-
```typescript
|
|
483
|
-
import { webhook, fn } from '@versori/run';
|
|
484
|
-
|
|
485
|
-
export const healthCheck = webhook('health', {
|
|
486
|
-
response: {
|
|
487
|
-
mode: 'sync',
|
|
488
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
489
|
-
status: 200,
|
|
490
|
-
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
|
|
491
|
-
})
|
|
492
|
-
}
|
|
493
|
-
})
|
|
494
|
-
.then(fn('check-health', () => {
|
|
495
|
-
const uptime = process.uptime();
|
|
496
|
-
const timestamp = new Date().toISOString();
|
|
497
|
-
|
|
498
|
-
return `Service Status: Operational
|
|
499
|
-
Uptime: ${uptime.toFixed(0)} seconds
|
|
500
|
-
Timestamp: ${timestamp}
|
|
501
|
-
}));
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
## Key Patterns Explained
|
|
505
|
-
|
|
506
|
-
### Pattern 1: Why Custom Response Objects Are Needed
|
|
507
|
-
|
|
508
|
-
**The Technical Details:**
|
|
509
|
-
|
|
510
|
-
Versori's `sendResponse()` function (from @versori/run v0.4.4):
|
|
511
|
-
|
|
512
|
-
```typescript
|
|
513
|
-
function sendResponse(res, response) {
|
|
514
|
-
res.status(response.status);
|
|
515
|
-
response.headers.forEach((value, key) => {
|
|
516
|
-
res.setHeader(key, value);
|
|
517
|
-
});
|
|
518
|
-
if (response.body) {
|
|
519
|
-
pipeline(response.body, res); // Streams directly without encoding
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
**Key insight:** When you return a `Response` object, Versori uses Node.js `pipeline()` to stream the body directly to the HTTP response **without any JSON encoding**.
|
|
525
|
-
|
|
526
|
-
**Default handlers** (when you don't provide custom ones):
|
|
527
|
-
|
|
528
|
-
```typescript
|
|
529
|
-
// Simplified version of default behavior
|
|
530
|
-
const defaultOnSuccess = (ctx) => {
|
|
531
|
-
return JSON.stringify(ctx.data); // ❌ Encodes everything as JSON
|
|
532
|
-
};
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### Pattern 2: onSuccess Handler Pattern
|
|
536
|
-
|
|
537
|
-
The `onSuccess` handler receives context and must return a `Response` object:
|
|
538
|
-
|
|
539
|
-
```typescript
|
|
540
|
-
onSuccess: (ctx) => {
|
|
541
|
-
// ctx.data = output from final .then() step
|
|
542
|
-
// ctx.executionId = unique execution ID
|
|
543
|
-
// ctx.metadata = any metadata from workflow
|
|
544
|
-
|
|
545
|
-
return new Response(ctx.data, {
|
|
546
|
-
status: 200, // HTTP status code
|
|
547
|
-
headers: {
|
|
548
|
-
'Content-Type': 'application/xml; charset=utf-8',
|
|
549
|
-
'X-Execution-Id': ctx.executionId,
|
|
550
|
-
'Cache-Control': 'no-cache',
|
|
551
|
-
// Any custom headers...
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
**Important:** Your workflow steps should return **raw strings**, not Response objects. The handler wraps them.
|
|
558
|
-
|
|
559
|
-
### Pattern 3: onError Handler Pattern
|
|
560
|
-
|
|
561
|
-
The `onError` handler handles exceptions and should match the content type:
|
|
562
|
-
|
|
563
|
-
```typescript
|
|
564
|
-
onError: (ctx) => {
|
|
565
|
-
// ctx.data = error from .catch() or thrown exception
|
|
566
|
-
// ctx.executionId = unique execution ID
|
|
567
|
-
|
|
568
|
-
return new Response(ctx.data, {
|
|
569
|
-
status: 500, // or 400, 404, etc.
|
|
570
|
-
headers: {
|
|
571
|
-
'Content-Type': 'application/xml; charset=utf-8', // Match success type!
|
|
572
|
-
'X-Execution-Id': ctx.executionId
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
**Best practice:** Use the same Content-Type for errors as for success responses. If your API returns XML on success, return XML errors too.
|
|
579
|
-
|
|
580
|
-
### Pattern 4: Content-Type Headers
|
|
581
|
-
|
|
582
|
-
Always include charset for text-based formats:
|
|
583
|
-
|
|
584
|
-
```typescript
|
|
585
|
-
// ✅ CORRECT - Includes charset
|
|
586
|
-
'Content-Type': 'application/xml; charset=utf-8'
|
|
587
|
-
'Content-Type': 'text/html; charset=utf-8'
|
|
588
|
-
'Content-Type': 'text/csv; charset=utf-8'
|
|
589
|
-
'Content-Type': 'text/plain; charset=utf-8'
|
|
590
|
-
|
|
591
|
-
// ❌ INCOMPLETE - Missing charset (may work but not recommended)
|
|
592
|
-
'Content-Type': 'application/xml'
|
|
593
|
-
|
|
594
|
-
// ✅ CORRECT - Binary formats don't need charset
|
|
595
|
-
'Content-Type': 'application/pdf'
|
|
596
|
-
'Content-Type': 'image/png'
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Pattern 5: XMLBuilder Integration
|
|
600
|
-
|
|
601
|
-
Using `fast-xml-parser` to generate clean XML:
|
|
602
|
-
|
|
603
|
-
```typescript
|
|
604
|
-
import { XMLBuilder } from 'fast-xml-parser';
|
|
605
|
-
|
|
606
|
-
// Initialize builder with options
|
|
607
|
-
const builder = new XMLBuilder({
|
|
608
|
-
ignoreAttributes: false, // Include XML attributes
|
|
609
|
-
attributeNamePrefix: '@', // Prefix for attributes
|
|
610
|
-
textNodeName: '#text', // Key for text content
|
|
611
|
-
format: true, // Pretty-print
|
|
612
|
-
indentBy: ' ', // Indentation
|
|
613
|
-
suppressEmptyNode: true // Don't output <tag></tag> for nulls
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
// Define XML structure
|
|
617
|
-
const xmlObject = {
|
|
618
|
-
'?xml': { // XML declaration
|
|
619
|
-
'@version': '1.0',
|
|
620
|
-
'@encoding': 'UTF-8'
|
|
621
|
-
},
|
|
622
|
-
Root: {
|
|
623
|
-
'@xmlns': 'http://...', // Root attributes start with @
|
|
624
|
-
Child: 'value',
|
|
625
|
-
Another: {
|
|
626
|
-
'@id': '123', // Element with attribute and text
|
|
627
|
-
'#text': 'content'
|
|
628
|
-
},
|
|
629
|
-
List: {
|
|
630
|
-
Item: ['item1', 'item2'] // Arrays become multiple elements
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
// Generate XML string
|
|
636
|
-
const xmlString = builder.build(xmlObject);
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
**Generated Output:**
|
|
640
|
-
|
|
641
|
-
```xml
|
|
642
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
643
|
-
<Root xmlns="http://...">
|
|
644
|
-
<Child>value</Child>
|
|
645
|
-
<Another id="123">content</Another>
|
|
646
|
-
<List>
|
|
647
|
-
<Item>item1</Item>
|
|
648
|
-
<Item>item2</Item>
|
|
649
|
-
</List>
|
|
650
|
-
</Root>
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
## The JSON Encoding Problem
|
|
654
|
-
|
|
655
|
-
### Side-by-Side Comparison
|
|
656
|
-
|
|
657
|
-
#### ❌ WRONG: Default Behavior (JSON-Encoded)
|
|
658
|
-
|
|
659
|
-
```typescript
|
|
660
|
-
export const badXmlEndpoint = webhook('bad-xml', async (ctx) => {
|
|
661
|
-
return '<?xml version="1.0"?><order><id>123</id></order>';
|
|
662
|
-
});
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
**What the client receives:**
|
|
666
|
-
|
|
667
|
-
```bash
|
|
668
|
-
$ curl https://workspace.versori.run/bad-xml
|
|
669
|
-
|
|
670
|
-
# Response:
|
|
671
|
-
"<?xml version=\"1.0\"?><order><id>123</id></order>"
|
|
672
|
-
|
|
673
|
-
# Response Headers:
|
|
674
|
-
Content-Type: application/json
|
|
675
|
-
Content-Length: 52
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
**Problems:**
|
|
679
|
-
|
|
680
|
-
1. XML is wrapped in quotes
|
|
681
|
-
2. Special characters are escaped (`\n`, `\"`)
|
|
682
|
-
3. Content-Type is JSON, not XML
|
|
683
|
-
4. Cannot be parsed as XML by clients
|
|
684
|
-
5. Requires client-side JSON parsing then XML parsing
|
|
685
|
-
|
|
686
|
-
#### ✅ RIGHT: Custom Response Objects
|
|
687
|
-
|
|
688
|
-
```typescript
|
|
689
|
-
export const goodXmlEndpoint = webhook('good-xml', {
|
|
690
|
-
response: {
|
|
691
|
-
mode: 'sync',
|
|
692
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
693
|
-
status: 200,
|
|
694
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
695
|
-
})
|
|
696
|
-
}
|
|
697
|
-
})
|
|
698
|
-
.then(fn('gen', () => '<?xml version="1.0"?><order><id>123</id></order>'));
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
**What the client receives:**
|
|
702
|
-
|
|
703
|
-
```bash
|
|
704
|
-
$ curl https://workspace.versori.run/good-xml
|
|
705
|
-
|
|
706
|
-
# Response:
|
|
707
|
-
<?xml version="1.0"?><order><id>123</id></order>
|
|
708
|
-
|
|
709
|
-
# Response Headers:
|
|
710
|
-
Content-Type: application/xml; charset=utf-8
|
|
711
|
-
Content-Length: 47
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
**Benefits:**
|
|
715
|
-
|
|
716
|
-
1. Raw XML delivered to client
|
|
717
|
-
2. No escaping or encoding
|
|
718
|
-
3. Correct Content-Type header
|
|
719
|
-
4. Client can parse directly as XML
|
|
720
|
-
5. Standard HTTP semantics
|
|
721
|
-
|
|
722
|
-
### Visual Example: What Gets Sent
|
|
723
|
-
|
|
724
|
-
**Bad (JSON-encoded):**
|
|
725
|
-
|
|
726
|
-
```
|
|
727
|
-
HTTP/1.1 200 OK
|
|
728
|
-
Content-Type: application/json
|
|
729
|
-
|
|
730
|
-
"<?xml version=\"1.0\"?><order><id>123</id></order>"
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
**Good (Raw XML):**
|
|
734
|
-
|
|
735
|
-
```
|
|
736
|
-
HTTP/1.1 200 OK
|
|
737
|
-
Content-Type: application/xml; charset=utf-8
|
|
738
|
-
|
|
739
|
-
<?xml version="1.0"?><order><id>123</id></order>
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
## Common Issues and Solutions
|
|
743
|
-
|
|
744
|
-
### Issue 1: XML Still Being JSON-Encoded
|
|
745
|
-
|
|
746
|
-
**Symptom:** Response is wrapped in quotes with escaped characters.
|
|
747
|
-
|
|
748
|
-
**Cause:** Missing custom response handlers.
|
|
749
|
-
|
|
750
|
-
**Solution:**
|
|
751
|
-
|
|
752
|
-
```typescript
|
|
753
|
-
// ❌ Missing handlers
|
|
754
|
-
export const endpoint = webhook('test')
|
|
755
|
-
.then(fn('gen', () => '<xml>data</xml>'));
|
|
756
|
-
|
|
757
|
-
// ✅ Add custom handlers
|
|
758
|
-
export const endpoint = webhook('test', {
|
|
759
|
-
response: {
|
|
760
|
-
mode: 'sync',
|
|
761
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
762
|
-
status: 200,
|
|
763
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
764
|
-
})
|
|
765
|
-
}
|
|
766
|
-
})
|
|
767
|
-
.then(fn('gen', () => '<xml>data</xml>'));
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
### Issue 2: Wrong Content-Type in Response
|
|
771
|
-
|
|
772
|
-
**Symptom:** Browser tries to download file or displays incorrectly.
|
|
773
|
-
|
|
774
|
-
**Cause:** Content-Type doesn't match actual content.
|
|
775
|
-
|
|
776
|
-
**Solution:**
|
|
777
|
-
|
|
778
|
-
```typescript
|
|
779
|
-
// ❌ WRONG - XML with JSON content type
|
|
780
|
-
headers: { 'Content-Type': 'application/json' }
|
|
781
|
-
|
|
782
|
-
// ✅ RIGHT - Match content type to content
|
|
783
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
784
|
-
```
|
|
785
|
-
|
|
786
|
-
**Content-Type Reference:**
|
|
787
|
-
|
|
788
|
-
- XML: `application/xml; charset=utf-8`
|
|
789
|
-
- HTML: `text/html; charset=utf-8`
|
|
790
|
-
- CSV: `text/csv; charset=utf-8`
|
|
791
|
-
- Plain text: `text/plain; charset=utf-8`
|
|
792
|
-
- JSON: `application/json` (default, no custom handler needed)
|
|
793
|
-
|
|
794
|
-
### Issue 3: Error Responses Return JSON Instead of XML
|
|
795
|
-
|
|
796
|
-
**Symptom:** Success returns XML, but errors return JSON.
|
|
797
|
-
|
|
798
|
-
**Cause:** Missing `onError` handler.
|
|
799
|
-
|
|
800
|
-
**Solution:**
|
|
801
|
-
|
|
802
|
-
```typescript
|
|
803
|
-
export const endpoint = webhook('test', {
|
|
804
|
-
response: {
|
|
805
|
-
mode: 'sync',
|
|
806
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
807
|
-
status: 200,
|
|
808
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
809
|
-
}),
|
|
810
|
-
// ✅ Add error handler with same content type
|
|
811
|
-
onError: (ctx) => new Response(ctx.data, {
|
|
812
|
-
status: 500,
|
|
813
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
814
|
-
})
|
|
815
|
-
}
|
|
816
|
-
})
|
|
817
|
-
.then(fn('gen', () => '<success/>'))
|
|
818
|
-
.catch(() => '<error/>'); // Make sure .catch() returns XML string
|
|
819
|
-
```
|
|
820
|
-
|
|
821
|
-
### Issue 4: Accidental Double-Encoding
|
|
822
|
-
|
|
823
|
-
**Symptom:** Response has extra quotes or escaped characters.
|
|
824
|
-
|
|
825
|
-
**Cause:** Calling `JSON.stringify()` on data before returning.
|
|
826
|
-
|
|
827
|
-
**Solution:**
|
|
828
|
-
|
|
829
|
-
```typescript
|
|
830
|
-
// ❌ WRONG - Don't stringify in handler
|
|
831
|
-
onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
|
|
832
|
-
status: 200,
|
|
833
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
// ✅ RIGHT - Pass raw data
|
|
837
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
838
|
-
status: 200,
|
|
839
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
840
|
-
})
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
## Testing Your Implementation
|
|
844
|
-
|
|
845
|
-
### Test 1: Verify Content-Type Header
|
|
846
|
-
|
|
847
|
-
```bash
|
|
848
|
-
# Check response headers
|
|
849
|
-
curl -v https://your-workspace.versori.run/your-endpoint
|
|
850
|
-
|
|
851
|
-
# Look for:
|
|
852
|
-
# ✅ Content-Type: application/xml; charset=utf-8
|
|
853
|
-
# ❌ Content-Type: application/json
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
### Test 2: Verify Raw Content
|
|
857
|
-
|
|
858
|
-
```bash
|
|
859
|
-
# Get response body
|
|
860
|
-
curl https://your-workspace.versori.run/your-endpoint
|
|
861
|
-
|
|
862
|
-
# Expected (raw XML):
|
|
863
|
-
# <?xml version="1.0" encoding="UTF-8"?>
|
|
864
|
-
# <order><id>123</id></order>
|
|
865
|
-
|
|
866
|
-
# NOT expected (JSON-encoded):
|
|
867
|
-
# "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<order><id>123</id></order>"
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
### Test 3: Verify Error Handling
|
|
871
|
-
|
|
872
|
-
```bash
|
|
873
|
-
# Trigger an error (send invalid request)
|
|
874
|
-
curl -X POST https://your-workspace.versori.run/your-endpoint \
|
|
875
|
-
-H "Content-Type: application/xml" \
|
|
876
|
-
-d '<?xml version="1.0"?><Invalid/>'
|
|
877
|
-
|
|
878
|
-
# Should return:
|
|
879
|
-
# - Status: 500 (or appropriate error code)
|
|
880
|
-
# - Content-Type: application/xml (same as success)
|
|
881
|
-
# - Body: XML error message
|
|
882
|
-
```
|
|
883
|
-
|
|
884
|
-
### Test 4: XML Parser Validation
|
|
885
|
-
|
|
886
|
-
```bash
|
|
887
|
-
# Pipe response through xmllint to validate XML
|
|
888
|
-
curl https://your-workspace.versori.run/your-endpoint | xmllint --format -
|
|
889
|
-
|
|
890
|
-
# Should format successfully without errors
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
## Implementation Checklist
|
|
894
|
-
|
|
895
|
-
When implementing non-JSON webhooks, verify:
|
|
896
|
-
|
|
897
|
-
- [ ] Webhook configured with `response.mode: 'sync'`
|
|
898
|
-
- [ ] `onSuccess` handler defined and returns `Response` object
|
|
899
|
-
- [ ] `onError` handler defined and returns `Response` object
|
|
900
|
-
- [ ] Both handlers set correct `Content-Type` header
|
|
901
|
-
- [ ] Content-Type includes `charset=utf-8` for text formats
|
|
902
|
-
- [ ] Workflow `.then()` steps return raw strings (not Response objects)
|
|
903
|
-
- [ ] Workflow `.catch()` returns error string in correct format
|
|
904
|
-
- [ ] Status codes are appropriate (200, 400, 500, etc.)
|
|
905
|
-
- [ ] Error handler uses same Content-Type as success handler
|
|
906
|
-
- [ ] Tested with curl to verify raw output (no JSON encoding)
|
|
907
|
-
- [ ] Tested error scenarios return proper format
|
|
908
|
-
- [ ] No `JSON.stringify()` calls in custom handlers
|
|
909
|
-
- [ ] XML validated with parser (if XML response)
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
## Versori Syntax Validation
|
|
913
|
-
|
|
914
|
-
### Critical: Buffer Import Required
|
|
915
|
-
|
|
916
|
-
**For Deno/Versori runtime, ALWAYS import Buffer explicitly:**
|
|
917
|
-
|
|
918
|
-
```typescript
|
|
919
|
-
// ✅ CORRECT - Explicit import required
|
|
920
|
-
import { Buffer } from 'node:buffer';
|
|
921
|
-
|
|
922
|
-
// Use Buffer for encoding operations
|
|
923
|
-
await sftp.uploadFile('utf8', Buffer.from(xmlContent), '/path/file.xml');
|
|
924
|
-
const decoded = Buffer.from(base64String, 'base64').toString('utf-8');
|
|
925
|
-
```
|
|
926
|
-
|
|
927
|
-
**Why:** Deno runtime doesn't have `Buffer` as a global (unlike Node.js). Missing this import will cause runtime errors.
|
|
928
|
-
|
|
929
|
-
### Validated Syntax Patterns for SDK ^0.1.27
|
|
930
|
-
|
|
931
|
-
#### 1. Webhook Response Configuration
|
|
932
|
-
|
|
933
|
-
```typescript
|
|
934
|
-
// ✅ CORRECT - Webhook with custom response handlers
|
|
935
|
-
export const endpoint = webhook('name', {
|
|
936
|
-
response: {
|
|
937
|
-
mode: 'sync', // Required for custom handlers
|
|
938
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
939
|
-
status: 200,
|
|
940
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
941
|
-
}),
|
|
942
|
-
onError: (ctx) => new Response(ctx.data, {
|
|
943
|
-
status: 500,
|
|
944
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
945
|
-
})
|
|
946
|
-
},
|
|
947
|
-
cors: true // Optional CORS configuration
|
|
948
|
-
})
|
|
949
|
-
.then(fn('step', () => '<xml/>'));
|
|
950
|
-
|
|
951
|
-
// ❌ WRONG - Missing response configuration
|
|
952
|
-
export const endpoint = webhook('name', async (ctx) => {
|
|
953
|
-
return '<xml/>'; // Will be JSON-encoded!
|
|
954
|
-
});
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
#### 2. Client Factory Usage
|
|
958
|
-
|
|
959
|
-
```typescript
|
|
960
|
-
// ✅ CORRECT - Use createClient() factory
|
|
961
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
962
|
-
|
|
963
|
-
export const endpoint = webhook('name', {
|
|
964
|
-
connection: 'fluent_commerce'
|
|
965
|
-
}, async (ctx) => {
|
|
966
|
-
const client = await createClient(ctx);
|
|
967
|
-
// Client automatically configured from connection
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
// ❌ WRONG - Direct instantiation bypasses context detection
|
|
971
|
-
import { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
972
|
-
const client = new FluentClient(config); // Don't do this in Versori
|
|
973
|
-
```
|
|
974
|
-
|
|
975
|
-
#### 3. Logging Best Practices
|
|
976
|
-
|
|
977
|
-
```typescript
|
|
978
|
-
// ✅ CORRECT - Use native log from context
|
|
979
|
-
export const endpoint = webhook('name', {
|
|
980
|
-
connection: 'fluent_commerce'
|
|
981
|
-
}, async (ctx) => {
|
|
982
|
-
const { log } = ctx;
|
|
983
|
-
log('Processing started', { orderId: '123' });
|
|
984
|
-
|
|
985
|
-
// SDK client receives log automatically
|
|
986
|
-
const client = await createClient(ctx);
|
|
987
|
-
});
|
|
988
|
-
|
|
989
|
-
// ❌ WRONG - Don't use LoggingService in Versori
|
|
990
|
-
import { LoggingService } from '@fluentcommerce/fc-connect-sdk';
|
|
991
|
-
const logger = new LoggingService(); // Not needed in Versori
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
#### 4. Auto-Pagination Syntax
|
|
995
|
-
|
|
996
|
-
```typescript
|
|
997
|
-
// ✅ CORRECT - pagination object (NOT config)
|
|
998
|
-
const result = await client.graphql({
|
|
999
|
-
query,
|
|
1000
|
-
variables: { first: 100 },
|
|
1001
|
-
pagination: { // Use 'pagination', not 'config'
|
|
1002
|
-
maxPages: 50,
|
|
1003
|
-
pageSize: 100
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
// ❌ WRONG - Old syntax with 'config'
|
|
1008
|
-
const result = await client.graphql({
|
|
1009
|
-
query,
|
|
1010
|
-
variables: { first: 100 },
|
|
1011
|
-
config: { // Use 'pagination' parameter instead
|
|
1012
|
-
maxPages: 50
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
#### 5. SDK Version Specification
|
|
1018
|
-
|
|
1019
|
-
```json
|
|
1020
|
-
{
|
|
1021
|
-
"dependencies": {
|
|
1022
|
-
"@fluentcommerce/fc-connect-sdk": "^0.1.39",
|
|
1023
|
-
"@versori/run": "latest",
|
|
1024
|
-
"fast-xml-parser": "^4.3.0"
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
```
|
|
1028
|
-
|
|
1029
|
-
#### 6. Workflow Step Return Values
|
|
1030
|
-
|
|
1031
|
-
```typescript
|
|
1032
|
-
// ✅ CORRECT - Return raw data, let handlers wrap it
|
|
1033
|
-
export const endpoint = webhook('name', {
|
|
1034
|
-
response: {
|
|
1035
|
-
mode: 'sync',
|
|
1036
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
1037
|
-
status: 200,
|
|
1038
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
1039
|
-
})
|
|
1040
|
-
}
|
|
1041
|
-
})
|
|
1042
|
-
.then(fn('generate', () => {
|
|
1043
|
-
return '<xml>data</xml>'; // Return raw string
|
|
1044
|
-
}));
|
|
1045
|
-
|
|
1046
|
-
// ❌ WRONG - Don't return Response objects from workflow steps
|
|
1047
|
-
export const endpoint = webhook('name', {
|
|
1048
|
-
response: { mode: 'sync', onSuccess: (ctx) => ctx.data }
|
|
1049
|
-
})
|
|
1050
|
-
.then(fn('generate', () => {
|
|
1051
|
-
return new Response('<xml>data</xml>'); // Wrong! Handler should do this
|
|
1052
|
-
}));
|
|
1053
|
-
```
|
|
1054
|
-
|
|
1055
|
-
#### 7. Error Handling Pattern
|
|
1056
|
-
|
|
1057
|
-
```typescript
|
|
1058
|
-
// ✅ CORRECT - .catch() returns error data, onError wraps it
|
|
1059
|
-
export const endpoint = webhook('name', {
|
|
1060
|
-
response: {
|
|
1061
|
-
mode: 'sync',
|
|
1062
|
-
onSuccess: (ctx) => new Response(ctx.data, {
|
|
1063
|
-
status: 200,
|
|
1064
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
1065
|
-
}),
|
|
1066
|
-
onError: (ctx) => new Response(ctx.data, {
|
|
1067
|
-
status: 500,
|
|
1068
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
1069
|
-
})
|
|
1070
|
-
}
|
|
1071
|
-
})
|
|
1072
|
-
.then(fn('process', () => '<success/>'))
|
|
1073
|
-
.catch((error) => {
|
|
1074
|
-
// Return error XML string - onError handler will wrap it
|
|
1075
|
-
return `<error>${error.message}</error>`;
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
// ❌ WRONG - Throwing without catch or returning Response
|
|
1079
|
-
export const endpoint = webhook('name')
|
|
1080
|
-
.then(fn('process', () => {
|
|
1081
|
-
throw new Error('Failed'); // Will cause 500 with JSON error
|
|
1082
|
-
}));
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
### Validation Checklist
|
|
1086
|
-
|
|
1087
|
-
Before deploying to Versori, verify:
|
|
1088
|
-
|
|
1089
|
-
**Syntax:**
|
|
1090
|
-
- [ ] Buffer explicitly imported: `import { Buffer } from 'node:buffer';`
|
|
1091
|
-
- [ ] webhook() has response.mode: 'sync' for custom handlers
|
|
1092
|
-
- [ ] onSuccess and onError both defined
|
|
1093
|
-
- [ ] Both handlers return Response objects
|
|
1094
|
-
- [ ] Workflow steps return raw data (not Response objects)
|
|
1095
|
-
|
|
1096
|
-
**SDK Integration:**
|
|
1097
|
-
- [ ] createClient() used (not direct instantiation)
|
|
1098
|
-
- [ ] Native ctx.log used (not LoggingService)
|
|
1099
|
-
- [ ] Auto-pagination uses 'pagination' object (not 'config')
|
|
1100
|
-
- [ ] SDK version is ^0.1.27 or later
|
|
1101
|
-
|
|
1102
|
-
**Response Handling:**
|
|
1103
|
-
- [ ] Content-Type headers match actual content
|
|
1104
|
-
- [ ] charset=utf-8 included for text formats
|
|
1105
|
-
- [ ] onError uses same Content-Type as onSuccess
|
|
1106
|
-
- [ ] Error .catch() returns proper format string
|
|
1107
|
-
|
|
1108
|
-
**Testing:**
|
|
1109
|
-
- [ ] Tested with curl to verify raw output
|
|
1110
|
-
- [ ] Verified Content-Type header is correct
|
|
1111
|
-
- [ ] Tested error scenarios return proper format
|
|
1112
|
-
- [ ] Validated XML with parser (if XML)
|
|
1113
|
-
|
|
1114
|
-
## Version Compatibility
|
|
1115
|
-
|
|
1116
|
-
This pattern works with:
|
|
1117
|
-
|
|
1118
|
-
**Versori Runtime:**
|
|
1119
|
-
- @versori/run v0.4.4 (latest stable)
|
|
1120
|
-
- @versori/run v0.4.3
|
|
1121
|
-
- @versori/run v0.4.2
|
|
1122
|
-
- @versori/run v0.4.1
|
|
1123
|
-
- @versori/run v0.4.0
|
|
1124
|
-
- @versori/run v0.4.0-alpha.2
|
|
1125
|
-
|
|
1126
|
-
All v0.4.x versions use the same `sendResponse()` implementation that streams Response objects directly.
|
|
1127
|
-
|
|
1128
|
-
**FC Connect SDK:**
|
|
1129
|
-
- @fluentcommerce/fc-connect-sdk v0.1.27+
|
|
1130
|
-
- @fluentcommerce/fc-connect-sdk v0.1.26
|
|
1131
|
-
- @fluentcommerce/fc-connect-sdk v0.1.25
|
|
1132
|
-
- All v0.1.x versions support Versori platform integration
|
|
1133
|
-
|
|
1134
|
-
## Related Guides
|
|
1135
|
-
|
|
1136
|
-
- **[Versori Platform Integration](../../../04-REFERENCE/platforms/versori/)** - Complete Versori platform guide
|
|
1137
|
-
- **[GraphQL Mutation Mapping Guide](../../../02-CORE-GUIDES/mapping/graphql-mutation-mapping/)** - XML/JSON to GraphQL mutations
|
|
1138
|
-
- **[Universal Mapping Guide](../../../02-CORE-GUIDES/mapping/)** - Field transformation patterns
|
|
1139
|
-
- **[Webhook Validation Guide](../../../02-CORE-GUIDES/webhook-validation/)** - Webhook signature validation
|
|
1140
|
-
|
|
1141
|
-
## Real-World Example Reference
|
|
1142
|
-
|
|
1143
|
-
See complete production implementation:
|
|
1144
|
-
|
|
1145
|
-
- **File**: `Sandbox Hibbet SFCC to Fluent Order integration (1)/src/workflows/process-order-detail-request.ts`
|
|
1146
|
-
- **Pattern**: XML request → GraphQL query → XML response
|
|
1147
|
-
- **Features**: Full error handling, extraction mapping, XMLBuilder integration
|
|
1148
|
-
|
|
1149
|
-
## Summary
|
|
1150
|
-
|
|
1151
|
-
**Golden Rule:** For non-JSON content types in Versori webhooks, **ALWAYS** use custom `onSuccess` and `onError` handlers that return `Response` objects with the correct Content-Type header.
|
|
1152
|
-
|
|
1153
|
-
**Why It Matters:**
|
|
1154
|
-
|
|
1155
|
-
- Default handlers JSON-encode all responses (breaks XML/HTML/CSV)
|
|
1156
|
-
- Custom handlers bypass encoding and stream content directly
|
|
1157
|
-
- Proper Content-Type headers ensure correct client behavior
|
|
1158
|
-
- Consistent error handling provides better API experience
|
|
1159
|
-
|
|
1160
|
-
**Impact:** This pattern saves hours of debugging and ensures proper content delivery for XML APIs, HTML pages, CSV exports, and other non-JSON formats. It's critical for SOAP integrations, enterprise systems, and file downloads.
|
|
1
|
+
# Versori Webhook: XML/HTML Response Patterns
|
|
2
|
+
|
|
3
|
+
**FC Connect SDK Use Case Guide**
|
|
4
|
+
|
|
5
|
+
> **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
|
|
6
|
+
> **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
|
|
7
|
+
|
|
8
|
+
**Context**: Return non-JSON content (XML, HTML, CSV) from Versori HTTP webhooks
|
|
9
|
+
|
|
10
|
+
**Complexity**: Low-Medium
|
|
11
|
+
|
|
12
|
+
**Runtime**: Versori Platform
|
|
13
|
+
|
|
14
|
+
**Estimated Lines**: ~300 lines
|
|
15
|
+
|
|
16
|
+
## What You'll Build
|
|
17
|
+
|
|
18
|
+
- Versori webhook with XML response
|
|
19
|
+
- Custom onSuccess/onError handlers
|
|
20
|
+
- Response object with proper Content-Type
|
|
21
|
+
- XMLBuilder for generating XML
|
|
22
|
+
- Error responses in XML format
|
|
23
|
+
|
|
24
|
+
## SDK Methods Used
|
|
25
|
+
|
|
26
|
+
- `webhook('name', { response: { mode, onSuccess, onError } })` - Versori webhook config
|
|
27
|
+
- `XMLBuilder(options)` - Build XML responses
|
|
28
|
+
- `builder.build(data)` - Generate XML string
|
|
29
|
+
- `new Response(body, { status, headers })` - Custom response objects
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Versori Workflows Structure
|
|
34
|
+
|
|
35
|
+
**Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
|
|
36
|
+
|
|
37
|
+
**Trigger Types:**
|
|
38
|
+
- **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
|
|
39
|
+
- **`schedule()`** → Time-based triggers (cron expressions) - NOT exposed as HTTP endpoints
|
|
40
|
+
- **`http()`** → External API calls (chained from webhook/schedule)
|
|
41
|
+
- **`fn()`** → Internal processing (chained from webhook/schedule)
|
|
42
|
+
|
|
43
|
+
### Recommended Project Structure
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
xml-response-patterns/
|
|
47
|
+
├── index.ts # Entry point - exports all workflows
|
|
48
|
+
└── src/
|
|
49
|
+
├── workflows/
|
|
50
|
+
│ └── webhook/
|
|
51
|
+
│ └── xml-response.ts # Webhook: Return XML responses
|
|
52
|
+
│
|
|
53
|
+
├── services/
|
|
54
|
+
│ └── xml-builder.service.ts # Shared XML building logic (reusable)
|
|
55
|
+
│
|
|
56
|
+
└── config/
|
|
57
|
+
└── xml-templates.json # XML templates
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Benefits:**
|
|
61
|
+
- ✅ Clear trigger separation (`webhook/` vs `scheduled/`)
|
|
62
|
+
- ✅ Descriptive file names (easy to browse and understand)
|
|
63
|
+
- ✅ Scalable (add new workflows without cluttering)
|
|
64
|
+
- ✅ Reusable code in `services/` (DRY principle)
|
|
65
|
+
- ✅ Easy to modify individual workflows without affecting others
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## The Critical Problem
|
|
70
|
+
|
|
71
|
+
By default, Versori webhooks **JSON-encode all responses**. This breaks non-JSON content types like XML, HTML, and CSV.
|
|
72
|
+
|
|
73
|
+
### The Wrong Way (Default Behavior)
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { webhook, fn } from '@versori/run';
|
|
77
|
+
|
|
78
|
+
// ❌ PROBLEM: Returns JSON-encoded XML string
|
|
79
|
+
export const badExample = webhook('xml-endpoint', async (ctx) => {
|
|
80
|
+
return '<?xml version="1.0"?><order><id>123</id></order>';
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Response Body: "<?xml version=\"1.0\"?><order><id>123</id></order>"
|
|
84
|
+
// Content-Type: application/json
|
|
85
|
+
// Problem: XML is wrapped in JSON string with escaped quotes!
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**What happens:**
|
|
89
|
+
|
|
90
|
+
1. Your workflow returns an XML string
|
|
91
|
+
2. Versori's default handler runs `JSON.stringify()` on it
|
|
92
|
+
3. Client receives a JSON-encoded string instead of raw XML
|
|
93
|
+
4. Content-Type is set to `application/json` instead of `application/xml`
|
|
94
|
+
|
|
95
|
+
### The Right Way (Custom Response Objects)
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { webhook, fn } from '@versori/run';
|
|
99
|
+
|
|
100
|
+
// ✅ SOLUTION: Returns raw XML
|
|
101
|
+
export const goodExample = webhook('xml-endpoint', {
|
|
102
|
+
response: {
|
|
103
|
+
mode: 'sync',
|
|
104
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
105
|
+
status: 200,
|
|
106
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
107
|
+
}),
|
|
108
|
+
onError: (ctx) => new Response(ctx.data, {
|
|
109
|
+
status: 500,
|
|
110
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.then(fn('generate-xml', () => {
|
|
115
|
+
return '<?xml version="1.0"?><order><id>123</id></order>';
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
// Response Body: <?xml version="1.0"?><order><id>123</id></order>
|
|
119
|
+
// Content-Type: application/xml; charset=utf-8
|
|
120
|
+
// Success: Raw XML streamed directly to client!
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Why this works:**
|
|
124
|
+
|
|
125
|
+
- Custom handlers return `Response` objects
|
|
126
|
+
- Versori's `sendResponse()` function streams Response bodies directly via Node.js `pipeline()`
|
|
127
|
+
- No JSON encoding is applied to Response objects
|
|
128
|
+
- Content-Type header is preserved exactly as specified
|
|
129
|
+
|
|
130
|
+
## Complete Working Examples
|
|
131
|
+
|
|
132
|
+
### Example 1: Basic JSON Response (Default Behavior)
|
|
133
|
+
|
|
134
|
+
For JSON responses, no custom handlers are needed:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { webhook, fn } from '@versori/run';
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Default JSON response - no custom handlers needed
|
|
141
|
+
* Versori automatically JSON-encodes and sets Content-Type: application/json
|
|
142
|
+
*/
|
|
143
|
+
export const jsonEndpoint = webhook('json-data', async (ctx) => {
|
|
144
|
+
return {
|
|
145
|
+
orderId: '12345',
|
|
146
|
+
status: 'COMPLETED',
|
|
147
|
+
items: [
|
|
148
|
+
{ sku: 'ABC123', quantity: 2 },
|
|
149
|
+
{ sku: 'DEF456', quantity: 1 }
|
|
150
|
+
]
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Response Body: {"orderId":"12345","status":"COMPLETED","items":[...]}
|
|
155
|
+
// Content-Type: application/json
|
|
156
|
+
// ✅ Perfect for JSON data!
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Example 2: XML Response with XMLBuilder
|
|
160
|
+
|
|
161
|
+
Full XML response workflow with proper error handling:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { webhook, fn, http } from '@versori/run';
|
|
165
|
+
// FC Connect SDK
|
|
166
|
+
// Install: npm install @fluentcommerce/fc-connect-sdk@latest
|
|
167
|
+
// Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
|
|
168
|
+
// GitHub: https://github.com/fluentcommerce/fc-connect-sdk
|
|
169
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
170
|
+
import { XMLBuilder } from 'fast-xml-parser';
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* XML Response Webhook
|
|
174
|
+
*
|
|
175
|
+
* CRITICAL PATTERNS:
|
|
176
|
+
* 1. Configure custom onSuccess/onError handlers
|
|
177
|
+
* 2. Both handlers return Response objects
|
|
178
|
+
* 3. Set Content-Type: application/xml
|
|
179
|
+
* 4. Workflow steps return raw strings (not Response objects)
|
|
180
|
+
* 5. Error handler also returns XML
|
|
181
|
+
*/
|
|
182
|
+
export const orderXmlEndpoint = webhook('order-xml', {
|
|
183
|
+
response: {
|
|
184
|
+
mode: 'sync',
|
|
185
|
+
// Success handler - wraps XML string in Response object
|
|
186
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
187
|
+
status: 200,
|
|
188
|
+
headers: {
|
|
189
|
+
'Content-Type': 'application/xml; charset=utf-8',
|
|
190
|
+
'X-Execution-Id': ctx.executionId,
|
|
191
|
+
'Cache-Control': 'no-cache'
|
|
192
|
+
}
|
|
193
|
+
}),
|
|
194
|
+
// Error handler - also returns XML format
|
|
195
|
+
onError: (ctx) => new Response(ctx.data, {
|
|
196
|
+
status: 500,
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/xml; charset=utf-8',
|
|
199
|
+
'X-Execution-Id': ctx.executionId
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
},
|
|
203
|
+
cors: true
|
|
204
|
+
})
|
|
205
|
+
// Step 1: Extract order ID from request
|
|
206
|
+
.then(fn('parse-request', ({ data }) => {
|
|
207
|
+
// Versori auto-parses XML when Content-Type is application/xml
|
|
208
|
+
const orderId = data?.OrderRequest?.OrderId;
|
|
209
|
+
if (!orderId) {
|
|
210
|
+
throw new Error('OrderId missing from request');
|
|
211
|
+
}
|
|
212
|
+
return { orderId };
|
|
213
|
+
}))
|
|
214
|
+
// Step 2: Fetch order from Fluent Commerce
|
|
215
|
+
.then(http('fetch-order', {
|
|
216
|
+
connection: 'fluent_commerce'
|
|
217
|
+
}, async (ctx) => {
|
|
218
|
+
const client = await createClient(ctx);
|
|
219
|
+
const result = await client.graphql({
|
|
220
|
+
query: `query GetOrder($ref: String) {
|
|
221
|
+
order(ref: $ref) {
|
|
222
|
+
id
|
|
223
|
+
ref
|
|
224
|
+
status
|
|
225
|
+
totalPrice
|
|
226
|
+
customer {
|
|
227
|
+
firstName
|
|
228
|
+
lastName
|
|
229
|
+
username
|
|
230
|
+
}
|
|
231
|
+
items(first: 50) {
|
|
232
|
+
edges {
|
|
233
|
+
node {
|
|
234
|
+
ref
|
|
235
|
+
quantity
|
|
236
|
+
price
|
|
237
|
+
product {
|
|
238
|
+
ref
|
|
239
|
+
name
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}`,
|
|
246
|
+
variables: { ref: ctx.data.orderId }
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (!result.data?.order) {
|
|
250
|
+
throw new Error(`Order not found: ${ctx.data.orderId}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { order: result.data.order };
|
|
254
|
+
}))
|
|
255
|
+
// Step 3: Transform to XML format
|
|
256
|
+
.then(fn('build-xml', ({ data }) => {
|
|
257
|
+
const { order } = data;
|
|
258
|
+
|
|
259
|
+
// Initialize XML Builder
|
|
260
|
+
const builder = new XMLBuilder({
|
|
261
|
+
ignoreAttributes: false,
|
|
262
|
+
attributeNamePrefix: '@',
|
|
263
|
+
format: true,
|
|
264
|
+
indentBy: ' ',
|
|
265
|
+
suppressEmptyNode: true
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Build XML structure
|
|
269
|
+
const xmlObject = {
|
|
270
|
+
'?xml': {
|
|
271
|
+
'@version': '1.0',
|
|
272
|
+
'@encoding': 'UTF-8'
|
|
273
|
+
},
|
|
274
|
+
OrderResponse: {
|
|
275
|
+
'@xmlns': 'http://api.example.com/schema/order/1.0',
|
|
276
|
+
Order: {
|
|
277
|
+
OrderId: order.ref,
|
|
278
|
+
Status: order.status,
|
|
279
|
+
TotalPrice: {
|
|
280
|
+
'@currency': 'USD',
|
|
281
|
+
'#text': order.totalPrice
|
|
282
|
+
},
|
|
283
|
+
Customer: {
|
|
284
|
+
FirstName: order.customer.firstName,
|
|
285
|
+
LastName: order.customer.lastName,
|
|
286
|
+
Email: order.customer.username
|
|
287
|
+
},
|
|
288
|
+
Items: {
|
|
289
|
+
Item: order.items.edges.map(edge => ({
|
|
290
|
+
SKU: edge.node.product.ref,
|
|
291
|
+
ProductName: edge.node.product.name,
|
|
292
|
+
Quantity: edge.node.quantity,
|
|
293
|
+
Price: edge.node.price
|
|
294
|
+
}))
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Generate XML string
|
|
301
|
+
const xmlString = builder.build(xmlObject);
|
|
302
|
+
|
|
303
|
+
// Return XML string - onSuccess handler will wrap it
|
|
304
|
+
return xmlString;
|
|
305
|
+
}))
|
|
306
|
+
// Error handling - return XML error format
|
|
307
|
+
.catch(({ data }) => {
|
|
308
|
+
const errorMessage = data instanceof Error ? data.message : String(data);
|
|
309
|
+
|
|
310
|
+
// Return error XML string - onError handler will wrap it
|
|
311
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
312
|
+
<OrderResponse xmlns="http://api.example.com/schema/order/1.0">
|
|
313
|
+
<Error>
|
|
314
|
+
<Code>PROCESSING_ERROR</Code>
|
|
315
|
+
<Message>${errorMessage}</Message>
|
|
316
|
+
<Timestamp>${new Date().toISOString()}</Timestamp>
|
|
317
|
+
</Error>
|
|
318
|
+
</OrderResponse>`;
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Testing this endpoint:**
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# Send XML request
|
|
326
|
+
curl -X POST https://your-workspace.versori.run/order-xml \
|
|
327
|
+
-H "Content-Type: application/xml" \
|
|
328
|
+
-d '<?xml version="1.0"?>
|
|
329
|
+
<OrderRequest>
|
|
330
|
+
<OrderId>ORD-12345</OrderId>
|
|
331
|
+
</OrderRequest>'
|
|
332
|
+
|
|
333
|
+
# Success Response (200 OK):
|
|
334
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
335
|
+
<OrderResponse xmlns="http://api.example.com/schema/order/1.0">
|
|
336
|
+
<Order>
|
|
337
|
+
<OrderId>ORD-12345</OrderId>
|
|
338
|
+
<Status>COMPLETED</Status>
|
|
339
|
+
<TotalPrice currency="USD">199.99</TotalPrice>
|
|
340
|
+
...
|
|
341
|
+
</Order>
|
|
342
|
+
</OrderResponse>
|
|
343
|
+
|
|
344
|
+
# Error Response (500 Internal Server Error):
|
|
345
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
346
|
+
<OrderResponse xmlns="http://api.example.com/schema/order/1.0">
|
|
347
|
+
<Error>
|
|
348
|
+
<Code>PROCESSING_ERROR</Code>
|
|
349
|
+
<Message>Order not found: ORD-12345</Message>
|
|
350
|
+
<Timestamp>2025-01-15T10:30:00Z</Timestamp>
|
|
351
|
+
</Error>
|
|
352
|
+
</OrderResponse>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Example 3: HTML Response
|
|
356
|
+
|
|
357
|
+
Return HTML pages from webhooks (useful for status pages, documentation):
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
import { webhook, fn } from '@versori/run';
|
|
361
|
+
|
|
362
|
+
export const statusPage = webhook('status', {
|
|
363
|
+
response: {
|
|
364
|
+
mode: 'sync',
|
|
365
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
366
|
+
status: 200,
|
|
367
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
.then(fn('generate-html', () => {
|
|
372
|
+
return `<!DOCTYPE html>
|
|
373
|
+
<html lang="en">
|
|
374
|
+
<head>
|
|
375
|
+
<meta charset="UTF-8">
|
|
376
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
377
|
+
<title>Service Status</title>
|
|
378
|
+
<style>
|
|
379
|
+
body {
|
|
380
|
+
font-family: Arial, sans-serif;
|
|
381
|
+
max-width: 800px;
|
|
382
|
+
margin: 50px auto;
|
|
383
|
+
padding: 20px;
|
|
384
|
+
}
|
|
385
|
+
.status {
|
|
386
|
+
padding: 20px;
|
|
387
|
+
background-color: #4CAF50;
|
|
388
|
+
color: white;
|
|
389
|
+
border-radius: 5px;
|
|
390
|
+
}
|
|
391
|
+
.service {
|
|
392
|
+
margin: 10px 0;
|
|
393
|
+
padding: 10px;
|
|
394
|
+
border: 1px solid #ddd;
|
|
395
|
+
border-radius: 3px;
|
|
396
|
+
}
|
|
397
|
+
</style>
|
|
398
|
+
</head>
|
|
399
|
+
<body>
|
|
400
|
+
<h1>Service Status Dashboard</h1>
|
|
401
|
+
<div class="status">
|
|
402
|
+
<h2>✓ All Systems Operational</h2>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="service">
|
|
405
|
+
<h3>Inventory Ingestion</h3>
|
|
406
|
+
<p>Status: <strong>Operational</strong></p>
|
|
407
|
+
<p>Last Run: ${new Date().toISOString()}</p>
|
|
408
|
+
</div>
|
|
409
|
+
<div class="service">
|
|
410
|
+
<h3>Order Processing</h3>
|
|
411
|
+
<p>Status: <strong>Operational</strong></p>
|
|
412
|
+
</div>
|
|
413
|
+
</body>
|
|
414
|
+
</html>`;
|
|
415
|
+
}));
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Example 4: CSV Response
|
|
419
|
+
|
|
420
|
+
Download data as CSV files:
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import { webhook, fn, http } from '@versori/run';
|
|
424
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
425
|
+
|
|
426
|
+
export const exportInventory = webhook('export-inventory-csv', {
|
|
427
|
+
response: {
|
|
428
|
+
mode: 'sync',
|
|
429
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
430
|
+
status: 200,
|
|
431
|
+
headers: {
|
|
432
|
+
'Content-Type': 'text/csv; charset=utf-8',
|
|
433
|
+
'Content-Disposition': 'attachment; filename="inventory-export.csv"'
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
.then(http('fetch-inventory', {
|
|
439
|
+
connection: 'fluent_commerce'
|
|
440
|
+
}, async (ctx) => {
|
|
441
|
+
const client = await createClient(ctx);
|
|
442
|
+
const result = await client.graphql({
|
|
443
|
+
query: `query {
|
|
444
|
+
inventoryQuantities(first: 100) {
|
|
445
|
+
edges {
|
|
446
|
+
node {
|
|
447
|
+
ref
|
|
448
|
+
quantity
|
|
449
|
+
location { ref }
|
|
450
|
+
product { ref name }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}`
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
return { items: result.data.inventoryQuantities.edges };
|
|
458
|
+
}))
|
|
459
|
+
.then(fn('build-csv', ({ data }) => {
|
|
460
|
+
// CSV Header
|
|
461
|
+
let csv = 'SKU,Product Name,Location,Quantity\n';
|
|
462
|
+
|
|
463
|
+
// CSV Rows
|
|
464
|
+
data.items.forEach(edge => {
|
|
465
|
+
const item = edge.node;
|
|
466
|
+
const sku = item.product?.ref || '';
|
|
467
|
+
const name = (item.product?.name || '').replace(/"/g, '""'); // Escape quotes
|
|
468
|
+
const location = item.location?.ref || '';
|
|
469
|
+
const qty = item.quantity || 0;
|
|
470
|
+
|
|
471
|
+
csv += `"${sku}","${name}","${location}",${qty}\n`;
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
return csv;
|
|
475
|
+
}));
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Example 5: Plain Text Response
|
|
479
|
+
|
|
480
|
+
Health check or simple text endpoints:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import { webhook, fn } from '@versori/run';
|
|
484
|
+
|
|
485
|
+
export const healthCheck = webhook('health', {
|
|
486
|
+
response: {
|
|
487
|
+
mode: 'sync',
|
|
488
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
489
|
+
status: 200,
|
|
490
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
.then(fn('check-health', () => {
|
|
495
|
+
const uptime = process.uptime();
|
|
496
|
+
const timestamp = new Date().toISOString();
|
|
497
|
+
|
|
498
|
+
return `Service Status: Operational
|
|
499
|
+
Uptime: ${uptime.toFixed(0)} seconds
|
|
500
|
+
Timestamp: ${timestamp}
|
|
501
|
+
}));
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Key Patterns Explained
|
|
505
|
+
|
|
506
|
+
### Pattern 1: Why Custom Response Objects Are Needed
|
|
507
|
+
|
|
508
|
+
**The Technical Details:**
|
|
509
|
+
|
|
510
|
+
Versori's `sendResponse()` function (from @versori/run v0.4.4):
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
function sendResponse(res, response) {
|
|
514
|
+
res.status(response.status);
|
|
515
|
+
response.headers.forEach((value, key) => {
|
|
516
|
+
res.setHeader(key, value);
|
|
517
|
+
});
|
|
518
|
+
if (response.body) {
|
|
519
|
+
pipeline(response.body, res); // Streams directly without encoding
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Key insight:** When you return a `Response` object, Versori uses Node.js `pipeline()` to stream the body directly to the HTTP response **without any JSON encoding**.
|
|
525
|
+
|
|
526
|
+
**Default handlers** (when you don't provide custom ones):
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// Simplified version of default behavior
|
|
530
|
+
const defaultOnSuccess = (ctx) => {
|
|
531
|
+
return JSON.stringify(ctx.data); // ❌ Encodes everything as JSON
|
|
532
|
+
};
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Pattern 2: onSuccess Handler Pattern
|
|
536
|
+
|
|
537
|
+
The `onSuccess` handler receives context and must return a `Response` object:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
onSuccess: (ctx) => {
|
|
541
|
+
// ctx.data = output from final .then() step
|
|
542
|
+
// ctx.executionId = unique execution ID
|
|
543
|
+
// ctx.metadata = any metadata from workflow
|
|
544
|
+
|
|
545
|
+
return new Response(ctx.data, {
|
|
546
|
+
status: 200, // HTTP status code
|
|
547
|
+
headers: {
|
|
548
|
+
'Content-Type': 'application/xml; charset=utf-8',
|
|
549
|
+
'X-Execution-Id': ctx.executionId,
|
|
550
|
+
'Cache-Control': 'no-cache',
|
|
551
|
+
// Any custom headers...
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**Important:** Your workflow steps should return **raw strings**, not Response objects. The handler wraps them.
|
|
558
|
+
|
|
559
|
+
### Pattern 3: onError Handler Pattern
|
|
560
|
+
|
|
561
|
+
The `onError` handler handles exceptions and should match the content type:
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
onError: (ctx) => {
|
|
565
|
+
// ctx.data = error from .catch() or thrown exception
|
|
566
|
+
// ctx.executionId = unique execution ID
|
|
567
|
+
|
|
568
|
+
return new Response(ctx.data, {
|
|
569
|
+
status: 500, // or 400, 404, etc.
|
|
570
|
+
headers: {
|
|
571
|
+
'Content-Type': 'application/xml; charset=utf-8', // Match success type!
|
|
572
|
+
'X-Execution-Id': ctx.executionId
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Best practice:** Use the same Content-Type for errors as for success responses. If your API returns XML on success, return XML errors too.
|
|
579
|
+
|
|
580
|
+
### Pattern 4: Content-Type Headers
|
|
581
|
+
|
|
582
|
+
Always include charset for text-based formats:
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
// ✅ CORRECT - Includes charset
|
|
586
|
+
'Content-Type': 'application/xml; charset=utf-8'
|
|
587
|
+
'Content-Type': 'text/html; charset=utf-8'
|
|
588
|
+
'Content-Type': 'text/csv; charset=utf-8'
|
|
589
|
+
'Content-Type': 'text/plain; charset=utf-8'
|
|
590
|
+
|
|
591
|
+
// ❌ INCOMPLETE - Missing charset (may work but not recommended)
|
|
592
|
+
'Content-Type': 'application/xml'
|
|
593
|
+
|
|
594
|
+
// ✅ CORRECT - Binary formats don't need charset
|
|
595
|
+
'Content-Type': 'application/pdf'
|
|
596
|
+
'Content-Type': 'image/png'
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Pattern 5: XMLBuilder Integration
|
|
600
|
+
|
|
601
|
+
Using `fast-xml-parser` to generate clean XML:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
import { XMLBuilder } from 'fast-xml-parser';
|
|
605
|
+
|
|
606
|
+
// Initialize builder with options
|
|
607
|
+
const builder = new XMLBuilder({
|
|
608
|
+
ignoreAttributes: false, // Include XML attributes
|
|
609
|
+
attributeNamePrefix: '@', // Prefix for attributes
|
|
610
|
+
textNodeName: '#text', // Key for text content
|
|
611
|
+
format: true, // Pretty-print
|
|
612
|
+
indentBy: ' ', // Indentation
|
|
613
|
+
suppressEmptyNode: true // Don't output <tag></tag> for nulls
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// Define XML structure
|
|
617
|
+
const xmlObject = {
|
|
618
|
+
'?xml': { // XML declaration
|
|
619
|
+
'@version': '1.0',
|
|
620
|
+
'@encoding': 'UTF-8'
|
|
621
|
+
},
|
|
622
|
+
Root: {
|
|
623
|
+
'@xmlns': 'http://...', // Root attributes start with @
|
|
624
|
+
Child: 'value',
|
|
625
|
+
Another: {
|
|
626
|
+
'@id': '123', // Element with attribute and text
|
|
627
|
+
'#text': 'content'
|
|
628
|
+
},
|
|
629
|
+
List: {
|
|
630
|
+
Item: ['item1', 'item2'] // Arrays become multiple elements
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// Generate XML string
|
|
636
|
+
const xmlString = builder.build(xmlObject);
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Generated Output:**
|
|
640
|
+
|
|
641
|
+
```xml
|
|
642
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
643
|
+
<Root xmlns="http://...">
|
|
644
|
+
<Child>value</Child>
|
|
645
|
+
<Another id="123">content</Another>
|
|
646
|
+
<List>
|
|
647
|
+
<Item>item1</Item>
|
|
648
|
+
<Item>item2</Item>
|
|
649
|
+
</List>
|
|
650
|
+
</Root>
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## The JSON Encoding Problem
|
|
654
|
+
|
|
655
|
+
### Side-by-Side Comparison
|
|
656
|
+
|
|
657
|
+
#### ❌ WRONG: Default Behavior (JSON-Encoded)
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
export const badXmlEndpoint = webhook('bad-xml', async (ctx) => {
|
|
661
|
+
return '<?xml version="1.0"?><order><id>123</id></order>';
|
|
662
|
+
});
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
**What the client receives:**
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
$ curl https://workspace.versori.run/bad-xml
|
|
669
|
+
|
|
670
|
+
# Response:
|
|
671
|
+
"<?xml version=\"1.0\"?><order><id>123</id></order>"
|
|
672
|
+
|
|
673
|
+
# Response Headers:
|
|
674
|
+
Content-Type: application/json
|
|
675
|
+
Content-Length: 52
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Problems:**
|
|
679
|
+
|
|
680
|
+
1. XML is wrapped in quotes
|
|
681
|
+
2. Special characters are escaped (`\n`, `\"`)
|
|
682
|
+
3. Content-Type is JSON, not XML
|
|
683
|
+
4. Cannot be parsed as XML by clients
|
|
684
|
+
5. Requires client-side JSON parsing then XML parsing
|
|
685
|
+
|
|
686
|
+
#### ✅ RIGHT: Custom Response Objects
|
|
687
|
+
|
|
688
|
+
```typescript
|
|
689
|
+
export const goodXmlEndpoint = webhook('good-xml', {
|
|
690
|
+
response: {
|
|
691
|
+
mode: 'sync',
|
|
692
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
693
|
+
status: 200,
|
|
694
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
695
|
+
})
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
.then(fn('gen', () => '<?xml version="1.0"?><order><id>123</id></order>'));
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
**What the client receives:**
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
$ curl https://workspace.versori.run/good-xml
|
|
705
|
+
|
|
706
|
+
# Response:
|
|
707
|
+
<?xml version="1.0"?><order><id>123</id></order>
|
|
708
|
+
|
|
709
|
+
# Response Headers:
|
|
710
|
+
Content-Type: application/xml; charset=utf-8
|
|
711
|
+
Content-Length: 47
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Benefits:**
|
|
715
|
+
|
|
716
|
+
1. Raw XML delivered to client
|
|
717
|
+
2. No escaping or encoding
|
|
718
|
+
3. Correct Content-Type header
|
|
719
|
+
4. Client can parse directly as XML
|
|
720
|
+
5. Standard HTTP semantics
|
|
721
|
+
|
|
722
|
+
### Visual Example: What Gets Sent
|
|
723
|
+
|
|
724
|
+
**Bad (JSON-encoded):**
|
|
725
|
+
|
|
726
|
+
```
|
|
727
|
+
HTTP/1.1 200 OK
|
|
728
|
+
Content-Type: application/json
|
|
729
|
+
|
|
730
|
+
"<?xml version=\"1.0\"?><order><id>123</id></order>"
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
**Good (Raw XML):**
|
|
734
|
+
|
|
735
|
+
```
|
|
736
|
+
HTTP/1.1 200 OK
|
|
737
|
+
Content-Type: application/xml; charset=utf-8
|
|
738
|
+
|
|
739
|
+
<?xml version="1.0"?><order><id>123</id></order>
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
## Common Issues and Solutions
|
|
743
|
+
|
|
744
|
+
### Issue 1: XML Still Being JSON-Encoded
|
|
745
|
+
|
|
746
|
+
**Symptom:** Response is wrapped in quotes with escaped characters.
|
|
747
|
+
|
|
748
|
+
**Cause:** Missing custom response handlers.
|
|
749
|
+
|
|
750
|
+
**Solution:**
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
// ❌ Missing handlers
|
|
754
|
+
export const endpoint = webhook('test')
|
|
755
|
+
.then(fn('gen', () => '<xml>data</xml>'));
|
|
756
|
+
|
|
757
|
+
// ✅ Add custom handlers
|
|
758
|
+
export const endpoint = webhook('test', {
|
|
759
|
+
response: {
|
|
760
|
+
mode: 'sync',
|
|
761
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
762
|
+
status: 200,
|
|
763
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
764
|
+
})
|
|
765
|
+
}
|
|
766
|
+
})
|
|
767
|
+
.then(fn('gen', () => '<xml>data</xml>'));
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Issue 2: Wrong Content-Type in Response
|
|
771
|
+
|
|
772
|
+
**Symptom:** Browser tries to download file or displays incorrectly.
|
|
773
|
+
|
|
774
|
+
**Cause:** Content-Type doesn't match actual content.
|
|
775
|
+
|
|
776
|
+
**Solution:**
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
// ❌ WRONG - XML with JSON content type
|
|
780
|
+
headers: { 'Content-Type': 'application/json' }
|
|
781
|
+
|
|
782
|
+
// ✅ RIGHT - Match content type to content
|
|
783
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Content-Type Reference:**
|
|
787
|
+
|
|
788
|
+
- XML: `application/xml; charset=utf-8`
|
|
789
|
+
- HTML: `text/html; charset=utf-8`
|
|
790
|
+
- CSV: `text/csv; charset=utf-8`
|
|
791
|
+
- Plain text: `text/plain; charset=utf-8`
|
|
792
|
+
- JSON: `application/json` (default, no custom handler needed)
|
|
793
|
+
|
|
794
|
+
### Issue 3: Error Responses Return JSON Instead of XML
|
|
795
|
+
|
|
796
|
+
**Symptom:** Success returns XML, but errors return JSON.
|
|
797
|
+
|
|
798
|
+
**Cause:** Missing `onError` handler.
|
|
799
|
+
|
|
800
|
+
**Solution:**
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
export const endpoint = webhook('test', {
|
|
804
|
+
response: {
|
|
805
|
+
mode: 'sync',
|
|
806
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
807
|
+
status: 200,
|
|
808
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
809
|
+
}),
|
|
810
|
+
// ✅ Add error handler with same content type
|
|
811
|
+
onError: (ctx) => new Response(ctx.data, {
|
|
812
|
+
status: 500,
|
|
813
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
814
|
+
})
|
|
815
|
+
}
|
|
816
|
+
})
|
|
817
|
+
.then(fn('gen', () => '<success/>'))
|
|
818
|
+
.catch(() => '<error/>'); // Make sure .catch() returns XML string
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Issue 4: Accidental Double-Encoding
|
|
822
|
+
|
|
823
|
+
**Symptom:** Response has extra quotes or escaped characters.
|
|
824
|
+
|
|
825
|
+
**Cause:** Calling `JSON.stringify()` on data before returning.
|
|
826
|
+
|
|
827
|
+
**Solution:**
|
|
828
|
+
|
|
829
|
+
```typescript
|
|
830
|
+
// ❌ WRONG - Don't stringify in handler
|
|
831
|
+
onSuccess: (ctx) => new Response(JSON.stringify(ctx.data), {
|
|
832
|
+
status: 200,
|
|
833
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
// ✅ RIGHT - Pass raw data
|
|
837
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
838
|
+
status: 200,
|
|
839
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
840
|
+
})
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
## Testing Your Implementation
|
|
844
|
+
|
|
845
|
+
### Test 1: Verify Content-Type Header
|
|
846
|
+
|
|
847
|
+
```bash
|
|
848
|
+
# Check response headers
|
|
849
|
+
curl -v https://your-workspace.versori.run/your-endpoint
|
|
850
|
+
|
|
851
|
+
# Look for:
|
|
852
|
+
# ✅ Content-Type: application/xml; charset=utf-8
|
|
853
|
+
# ❌ Content-Type: application/json
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### Test 2: Verify Raw Content
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
# Get response body
|
|
860
|
+
curl https://your-workspace.versori.run/your-endpoint
|
|
861
|
+
|
|
862
|
+
# Expected (raw XML):
|
|
863
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
864
|
+
# <order><id>123</id></order>
|
|
865
|
+
|
|
866
|
+
# NOT expected (JSON-encoded):
|
|
867
|
+
# "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<order><id>123</id></order>"
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
### Test 3: Verify Error Handling
|
|
871
|
+
|
|
872
|
+
```bash
|
|
873
|
+
# Trigger an error (send invalid request)
|
|
874
|
+
curl -X POST https://your-workspace.versori.run/your-endpoint \
|
|
875
|
+
-H "Content-Type: application/xml" \
|
|
876
|
+
-d '<?xml version="1.0"?><Invalid/>'
|
|
877
|
+
|
|
878
|
+
# Should return:
|
|
879
|
+
# - Status: 500 (or appropriate error code)
|
|
880
|
+
# - Content-Type: application/xml (same as success)
|
|
881
|
+
# - Body: XML error message
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
### Test 4: XML Parser Validation
|
|
885
|
+
|
|
886
|
+
```bash
|
|
887
|
+
# Pipe response through xmllint to validate XML
|
|
888
|
+
curl https://your-workspace.versori.run/your-endpoint | xmllint --format -
|
|
889
|
+
|
|
890
|
+
# Should format successfully without errors
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
## Implementation Checklist
|
|
894
|
+
|
|
895
|
+
When implementing non-JSON webhooks, verify:
|
|
896
|
+
|
|
897
|
+
- [ ] Webhook configured with `response.mode: 'sync'`
|
|
898
|
+
- [ ] `onSuccess` handler defined and returns `Response` object
|
|
899
|
+
- [ ] `onError` handler defined and returns `Response` object
|
|
900
|
+
- [ ] Both handlers set correct `Content-Type` header
|
|
901
|
+
- [ ] Content-Type includes `charset=utf-8` for text formats
|
|
902
|
+
- [ ] Workflow `.then()` steps return raw strings (not Response objects)
|
|
903
|
+
- [ ] Workflow `.catch()` returns error string in correct format
|
|
904
|
+
- [ ] Status codes are appropriate (200, 400, 500, etc.)
|
|
905
|
+
- [ ] Error handler uses same Content-Type as success handler
|
|
906
|
+
- [ ] Tested with curl to verify raw output (no JSON encoding)
|
|
907
|
+
- [ ] Tested error scenarios return proper format
|
|
908
|
+
- [ ] No `JSON.stringify()` calls in custom handlers
|
|
909
|
+
- [ ] XML validated with parser (if XML response)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
## Versori Syntax Validation
|
|
913
|
+
|
|
914
|
+
### Critical: Buffer Import Required
|
|
915
|
+
|
|
916
|
+
**For Deno/Versori runtime, ALWAYS import Buffer explicitly:**
|
|
917
|
+
|
|
918
|
+
```typescript
|
|
919
|
+
// ✅ CORRECT - Explicit import required
|
|
920
|
+
import { Buffer } from 'node:buffer';
|
|
921
|
+
|
|
922
|
+
// Use Buffer for encoding operations
|
|
923
|
+
await sftp.uploadFile('utf8', Buffer.from(xmlContent), '/path/file.xml');
|
|
924
|
+
const decoded = Buffer.from(base64String, 'base64').toString('utf-8');
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
**Why:** Deno runtime doesn't have `Buffer` as a global (unlike Node.js). Missing this import will cause runtime errors.
|
|
928
|
+
|
|
929
|
+
### Validated Syntax Patterns for SDK ^0.1.27
|
|
930
|
+
|
|
931
|
+
#### 1. Webhook Response Configuration
|
|
932
|
+
|
|
933
|
+
```typescript
|
|
934
|
+
// ✅ CORRECT - Webhook with custom response handlers
|
|
935
|
+
export const endpoint = webhook('name', {
|
|
936
|
+
response: {
|
|
937
|
+
mode: 'sync', // Required for custom handlers
|
|
938
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
939
|
+
status: 200,
|
|
940
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
941
|
+
}),
|
|
942
|
+
onError: (ctx) => new Response(ctx.data, {
|
|
943
|
+
status: 500,
|
|
944
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
945
|
+
})
|
|
946
|
+
},
|
|
947
|
+
cors: true // Optional CORS configuration
|
|
948
|
+
})
|
|
949
|
+
.then(fn('step', () => '<xml/>'));
|
|
950
|
+
|
|
951
|
+
// ❌ WRONG - Missing response configuration
|
|
952
|
+
export const endpoint = webhook('name', async (ctx) => {
|
|
953
|
+
return '<xml/>'; // Will be JSON-encoded!
|
|
954
|
+
});
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
#### 2. Client Factory Usage
|
|
958
|
+
|
|
959
|
+
```typescript
|
|
960
|
+
// ✅ CORRECT - Use createClient() factory
|
|
961
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
962
|
+
|
|
963
|
+
export const endpoint = webhook('name', {
|
|
964
|
+
connection: 'fluent_commerce'
|
|
965
|
+
}, async (ctx) => {
|
|
966
|
+
const client = await createClient(ctx);
|
|
967
|
+
// Client automatically configured from connection
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// ❌ WRONG - Direct instantiation bypasses context detection
|
|
971
|
+
import { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
972
|
+
const client = new FluentClient(config); // Don't do this in Versori
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
#### 3. Logging Best Practices
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
// ✅ CORRECT - Use native log from context
|
|
979
|
+
export const endpoint = webhook('name', {
|
|
980
|
+
connection: 'fluent_commerce'
|
|
981
|
+
}, async (ctx) => {
|
|
982
|
+
const { log } = ctx;
|
|
983
|
+
log('Processing started', { orderId: '123' });
|
|
984
|
+
|
|
985
|
+
// SDK client receives log automatically
|
|
986
|
+
const client = await createClient(ctx);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
// ❌ WRONG - Don't use LoggingService in Versori
|
|
990
|
+
import { LoggingService } from '@fluentcommerce/fc-connect-sdk';
|
|
991
|
+
const logger = new LoggingService(); // Not needed in Versori
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
#### 4. Auto-Pagination Syntax
|
|
995
|
+
|
|
996
|
+
```typescript
|
|
997
|
+
// ✅ CORRECT - pagination object (NOT config)
|
|
998
|
+
const result = await client.graphql({
|
|
999
|
+
query,
|
|
1000
|
+
variables: { first: 100 },
|
|
1001
|
+
pagination: { // Use 'pagination', not 'config'
|
|
1002
|
+
maxPages: 50,
|
|
1003
|
+
pageSize: 100
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// ❌ WRONG - Old syntax with 'config'
|
|
1008
|
+
const result = await client.graphql({
|
|
1009
|
+
query,
|
|
1010
|
+
variables: { first: 100 },
|
|
1011
|
+
config: { // Use 'pagination' parameter instead
|
|
1012
|
+
maxPages: 50
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
#### 5. SDK Version Specification
|
|
1018
|
+
|
|
1019
|
+
```json
|
|
1020
|
+
{
|
|
1021
|
+
"dependencies": {
|
|
1022
|
+
"@fluentcommerce/fc-connect-sdk": "^0.1.39",
|
|
1023
|
+
"@versori/run": "latest",
|
|
1024
|
+
"fast-xml-parser": "^4.3.0"
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
#### 6. Workflow Step Return Values
|
|
1030
|
+
|
|
1031
|
+
```typescript
|
|
1032
|
+
// ✅ CORRECT - Return raw data, let handlers wrap it
|
|
1033
|
+
export const endpoint = webhook('name', {
|
|
1034
|
+
response: {
|
|
1035
|
+
mode: 'sync',
|
|
1036
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
1037
|
+
status: 200,
|
|
1038
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
1039
|
+
})
|
|
1040
|
+
}
|
|
1041
|
+
})
|
|
1042
|
+
.then(fn('generate', () => {
|
|
1043
|
+
return '<xml>data</xml>'; // Return raw string
|
|
1044
|
+
}));
|
|
1045
|
+
|
|
1046
|
+
// ❌ WRONG - Don't return Response objects from workflow steps
|
|
1047
|
+
export const endpoint = webhook('name', {
|
|
1048
|
+
response: { mode: 'sync', onSuccess: (ctx) => ctx.data }
|
|
1049
|
+
})
|
|
1050
|
+
.then(fn('generate', () => {
|
|
1051
|
+
return new Response('<xml>data</xml>'); // Wrong! Handler should do this
|
|
1052
|
+
}));
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
#### 7. Error Handling Pattern
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
// ✅ CORRECT - .catch() returns error data, onError wraps it
|
|
1059
|
+
export const endpoint = webhook('name', {
|
|
1060
|
+
response: {
|
|
1061
|
+
mode: 'sync',
|
|
1062
|
+
onSuccess: (ctx) => new Response(ctx.data, {
|
|
1063
|
+
status: 200,
|
|
1064
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
1065
|
+
}),
|
|
1066
|
+
onError: (ctx) => new Response(ctx.data, {
|
|
1067
|
+
status: 500,
|
|
1068
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' }
|
|
1069
|
+
})
|
|
1070
|
+
}
|
|
1071
|
+
})
|
|
1072
|
+
.then(fn('process', () => '<success/>'))
|
|
1073
|
+
.catch((error) => {
|
|
1074
|
+
// Return error XML string - onError handler will wrap it
|
|
1075
|
+
return `<error>${error.message}</error>`;
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// ❌ WRONG - Throwing without catch or returning Response
|
|
1079
|
+
export const endpoint = webhook('name')
|
|
1080
|
+
.then(fn('process', () => {
|
|
1081
|
+
throw new Error('Failed'); // Will cause 500 with JSON error
|
|
1082
|
+
}));
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
### Validation Checklist
|
|
1086
|
+
|
|
1087
|
+
Before deploying to Versori, verify:
|
|
1088
|
+
|
|
1089
|
+
**Syntax:**
|
|
1090
|
+
- [ ] Buffer explicitly imported: `import { Buffer } from 'node:buffer';`
|
|
1091
|
+
- [ ] webhook() has response.mode: 'sync' for custom handlers
|
|
1092
|
+
- [ ] onSuccess and onError both defined
|
|
1093
|
+
- [ ] Both handlers return Response objects
|
|
1094
|
+
- [ ] Workflow steps return raw data (not Response objects)
|
|
1095
|
+
|
|
1096
|
+
**SDK Integration:**
|
|
1097
|
+
- [ ] createClient() used (not direct instantiation)
|
|
1098
|
+
- [ ] Native ctx.log used (not LoggingService)
|
|
1099
|
+
- [ ] Auto-pagination uses 'pagination' object (not 'config')
|
|
1100
|
+
- [ ] SDK version is ^0.1.27 or later
|
|
1101
|
+
|
|
1102
|
+
**Response Handling:**
|
|
1103
|
+
- [ ] Content-Type headers match actual content
|
|
1104
|
+
- [ ] charset=utf-8 included for text formats
|
|
1105
|
+
- [ ] onError uses same Content-Type as onSuccess
|
|
1106
|
+
- [ ] Error .catch() returns proper format string
|
|
1107
|
+
|
|
1108
|
+
**Testing:**
|
|
1109
|
+
- [ ] Tested with curl to verify raw output
|
|
1110
|
+
- [ ] Verified Content-Type header is correct
|
|
1111
|
+
- [ ] Tested error scenarios return proper format
|
|
1112
|
+
- [ ] Validated XML with parser (if XML)
|
|
1113
|
+
|
|
1114
|
+
## Version Compatibility
|
|
1115
|
+
|
|
1116
|
+
This pattern works with:
|
|
1117
|
+
|
|
1118
|
+
**Versori Runtime:**
|
|
1119
|
+
- @versori/run v0.4.4 (latest stable)
|
|
1120
|
+
- @versori/run v0.4.3
|
|
1121
|
+
- @versori/run v0.4.2
|
|
1122
|
+
- @versori/run v0.4.1
|
|
1123
|
+
- @versori/run v0.4.0
|
|
1124
|
+
- @versori/run v0.4.0-alpha.2
|
|
1125
|
+
|
|
1126
|
+
All v0.4.x versions use the same `sendResponse()` implementation that streams Response objects directly.
|
|
1127
|
+
|
|
1128
|
+
**FC Connect SDK:**
|
|
1129
|
+
- @fluentcommerce/fc-connect-sdk v0.1.27+
|
|
1130
|
+
- @fluentcommerce/fc-connect-sdk v0.1.26
|
|
1131
|
+
- @fluentcommerce/fc-connect-sdk v0.1.25
|
|
1132
|
+
- All v0.1.x versions support Versori platform integration
|
|
1133
|
+
|
|
1134
|
+
## Related Guides
|
|
1135
|
+
|
|
1136
|
+
- **[Versori Platform Integration](../../../04-REFERENCE/platforms/versori/)** - Complete Versori platform guide
|
|
1137
|
+
- **[GraphQL Mutation Mapping Guide](../../../02-CORE-GUIDES/mapping/graphql-mutation-mapping/)** - XML/JSON to GraphQL mutations
|
|
1138
|
+
- **[Universal Mapping Guide](../../../02-CORE-GUIDES/mapping/)** - Field transformation patterns
|
|
1139
|
+
- **[Webhook Validation Guide](../../../02-CORE-GUIDES/webhook-validation/)** - Webhook signature validation
|
|
1140
|
+
|
|
1141
|
+
## Real-World Example Reference
|
|
1142
|
+
|
|
1143
|
+
See complete production implementation:
|
|
1144
|
+
|
|
1145
|
+
- **File**: `Sandbox Hibbet SFCC to Fluent Order integration (1)/src/workflows/process-order-detail-request.ts`
|
|
1146
|
+
- **Pattern**: XML request → GraphQL query → XML response
|
|
1147
|
+
- **Features**: Full error handling, extraction mapping, XMLBuilder integration
|
|
1148
|
+
|
|
1149
|
+
## Summary
|
|
1150
|
+
|
|
1151
|
+
**Golden Rule:** For non-JSON content types in Versori webhooks, **ALWAYS** use custom `onSuccess` and `onError` handlers that return `Response` objects with the correct Content-Type header.
|
|
1152
|
+
|
|
1153
|
+
**Why It Matters:**
|
|
1154
|
+
|
|
1155
|
+
- Default handlers JSON-encode all responses (breaks XML/HTML/CSV)
|
|
1156
|
+
- Custom handlers bypass encoding and stream content directly
|
|
1157
|
+
- Proper Content-Type headers ensure correct client behavior
|
|
1158
|
+
- Consistent error handling provides better API experience
|
|
1159
|
+
|
|
1160
|
+
**Impact:** This pattern saves hours of debugging and ensures proper content delivery for XML APIs, HTML pages, CSV exports, and other non-JSON formats. It's critical for SOAP integrations, enterprise systems, and file downloads.
|