@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,1260 +1,1260 @@
|
|
|
1
|
-
---
|
|
2
|
-
template_id: tpl-ingest-payload-json-to-order-update-graphql
|
|
3
|
-
canonical_filename: template-ingestion-payload-json-order-update-graphql.md
|
|
4
|
-
sdk_version: latest
|
|
5
|
-
runtime: versori
|
|
6
|
-
direction: ingestion
|
|
7
|
-
source: payload-json
|
|
8
|
-
destination: fluent-graphql
|
|
9
|
-
entity: order
|
|
10
|
-
format: json
|
|
11
|
-
logging: versori
|
|
12
|
-
status: stable
|
|
13
|
-
features:
|
|
14
|
-
- direct-payload-processing
|
|
15
|
-
- json-parsing
|
|
16
|
-
- graphql-mutations
|
|
17
|
-
- order-query-before-update
|
|
18
|
-
- attribute-updates
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
# Template: Ingestion - Payload JSON to Order Update GraphQL
|
|
22
|
-
|
|
23
|
-
**SDK Version:** @fluentcommerce/fc-connect-sdk@latest
|
|
24
|
-
**Last Updated:** 2025-01-24
|
|
25
|
-
**Deployment Target:** Versori Platform
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 📋 Implementation Prompt
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
I need a Versori webhook ingestion that:
|
|
33
|
-
|
|
34
|
-
1) Receives JSON payload directly in webhook request body with orderRef and attributes
|
|
35
|
-
2) Parses JSON to extract order reference and attributes
|
|
36
|
-
3) Queries Fluent Commerce to get order ID by order reference
|
|
37
|
-
4) Updates order attributes using updateOrder GraphQL mutation
|
|
38
|
-
5) Returns success/error response
|
|
39
|
-
6) Uses native Versori log from context
|
|
40
|
-
|
|
41
|
-
Use the loaded docs to fill in SDK specifics and best practices.
|
|
42
|
-
Keep the structure identical to the template; only adapt where needed.
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## 📋 Template Overview
|
|
48
|
-
|
|
49
|
-
This connector runs on the Versori platform. It receives JSON payloads directly via webhook with order reference and attributes, queries Fluent Commerce to get the order ID, and updates order attributes using GraphQL mutations. Most operational settings (Fluent account/connection) are configured via activation variables.
|
|
50
|
-
|
|
51
|
-
### What This Template Does
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
┌────────────────────────────────────────────────────────────────┐
|
|
55
|
-
│ INGESTION WORKFLOW │
|
|
56
|
-
└────────────────────────────────────────────────────────────────┘
|
|
57
|
-
|
|
58
|
-
1. TRIGGER
|
|
59
|
-
└─ Webhook: HTTP POST endpoint receives JSON payload
|
|
60
|
-
|
|
61
|
-
2. RECEIVE PAYLOAD
|
|
62
|
-
├─ Extract JSON from request body (ctx.data or ctx.request)
|
|
63
|
-
├─ Validate payload structure
|
|
64
|
-
└─ Log incoming request
|
|
65
|
-
|
|
66
|
-
3. PARSE JSON
|
|
67
|
-
├─ Parse JSON payload (if raw string)
|
|
68
|
-
├─ Extract order reference
|
|
69
|
-
├─ Extract attributes array
|
|
70
|
-
└─ Validate required fields
|
|
71
|
-
|
|
72
|
-
4. QUERY ORDER
|
|
73
|
-
├─ Query Fluent Commerce: order(ref: orderRef)
|
|
74
|
-
├─ Get order ID and current state
|
|
75
|
-
├─ Validate order exists
|
|
76
|
-
└─ Log order details
|
|
77
|
-
|
|
78
|
-
5. BUILD UPDATE MUTATION
|
|
79
|
-
├─ Construct UpdateOrderInput with ref
|
|
80
|
-
├─ Map attributes to AttributeInput format
|
|
81
|
-
├─ Preserve existing attributes (optional merge)
|
|
82
|
-
└─ Build GraphQL mutation variables
|
|
83
|
-
|
|
84
|
-
6. EXECUTE MUTATION
|
|
85
|
-
├─ Execute updateOrder GraphQL mutation
|
|
86
|
-
├─ Handle success/error responses
|
|
87
|
-
└─ Log mutation result
|
|
88
|
-
|
|
89
|
-
7. RETURN RESPONSE
|
|
90
|
-
├─ Return success with order details
|
|
91
|
-
└─ Return error with details if failed
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Key Features
|
|
95
|
-
|
|
96
|
-
- **Direct Payload Processing**: Receives JSON payload directly in webhook request body
|
|
97
|
-
- **Order Query First**: Queries order by ref to get ID and validate existence
|
|
98
|
-
- **GraphQL Mutation**: Uses updateOrder mutation to update attributes
|
|
99
|
-
- **Attribute Mapping**: Converts payload attributes to Fluent AttributeInput format
|
|
100
|
-
- **Error Handling**: Comprehensive error handling with detailed logging
|
|
101
|
-
- **Native Versori Logging**: Uses Versori log from context
|
|
102
|
-
|
|
103
|
-
### 📦 Package Information
|
|
104
|
-
|
|
105
|
-
**SDK:** [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk) `latest`
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
npm install @fluentcommerce/fc-connect-sdk@latest
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
**Templates are designed for direct deployment; customize via activation variables.**
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## 📦 SDK Imports (Verified - Versori Optimized)
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// ✅ VERIFIED IMPORTS - These match actual SDK exports
|
|
121
|
-
import { Buffer } from 'node:buffer'; // Required for Versori/Deno runtime
|
|
122
|
-
import {
|
|
123
|
-
createClient, // Universal client factory
|
|
124
|
-
JSONParserService, // JSON parsing utility
|
|
125
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
126
|
-
|
|
127
|
-
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
128
|
-
|
|
129
|
-
// Versori platform imports
|
|
130
|
-
import { webhook, http } from '@versori/run';
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**Note:** All imports are from actual SDK exports - this code compiles and runs as-is.
|
|
134
|
-
|
|
135
|
-
**⚠️ CRITICAL - Buffer Import Required:**
|
|
136
|
-
|
|
137
|
-
The `Buffer` import from `node:buffer` is **required** for Versori/Deno runtime compatibility:
|
|
138
|
-
- Versori and Deno do NOT have `Buffer` as a global (unlike Node.js)
|
|
139
|
-
- Without this import, code will crash with `ReferenceError: Buffer is not defined`
|
|
140
|
-
- Always include this import in Versori templates, even if not directly used in the template code
|
|
141
|
-
- Required for SDK internal operations and any Buffer usage in your code
|
|
142
|
-
|
|
143
|
-
**✅ VERSORI PLATFORM - Use Native Logs:**
|
|
144
|
-
|
|
145
|
-
- Use `log` from context: `const { log } = ctx;`
|
|
146
|
-
- Native Versori logs are simpler and automatically integrated with platform monitoring
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## 🔐 Fluent Commerce Connection Setup
|
|
151
|
-
|
|
152
|
-
**✅ BEST PRACTICE:** Store Fluent Commerce credentials in a Versori connection object:
|
|
153
|
-
|
|
154
|
-
**Connection Configuration:**
|
|
155
|
-
|
|
156
|
-
1. **Create Connection in Versori:**
|
|
157
|
-
- Name: `fluent_commerce`
|
|
158
|
-
- Type: HTTP Basic Auth or OAuth2
|
|
159
|
-
- Credentials: Fluent Commerce API credentials
|
|
160
|
-
|
|
161
|
-
2. **Connection Variables:**
|
|
162
|
-
- `FLUENT_BASE_URL` - Fluent Commerce API base URL (e.g., `https://api.fluentcommerce.com`)
|
|
163
|
-
- `FLUENT_CLIENT_ID` - OAuth client ID (if using OAuth2)
|
|
164
|
-
- `FLUENT_CLIENT_SECRET` - OAuth client secret (if using OAuth2)
|
|
165
|
-
- `FLUENT_USERNAME` - Username (if using Basic Auth)
|
|
166
|
-
- `FLUENT_PASSWORD` - Password (if using Basic Auth)
|
|
167
|
-
|
|
168
|
-
**Benefits:**
|
|
169
|
-
- ✅ Credentials stored securely in Versori vault
|
|
170
|
-
- ✅ Connection can be reused across workflows
|
|
171
|
-
- ✅ No sensitive data in activation variables
|
|
172
|
-
- ✅ Easier credential rotation
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## 📄 Expected JSON Payload Format
|
|
177
|
-
|
|
178
|
-
The webhook accepts **flat key-value JSON payloads**. All fields except `orderRef` are automatically treated as order attributes.
|
|
179
|
-
|
|
180
|
-
### ✅ PRIMARY FORMAT: Flat Key-Value Pairs (Recommended)
|
|
181
|
-
|
|
182
|
-
**Simple format - just key-value pairs, no nested structure:**
|
|
183
|
-
|
|
184
|
-
```json
|
|
185
|
-
{
|
|
186
|
-
"orderRef": "ORD-12345",
|
|
187
|
-
"externalOrderId": "EXT-123456789",
|
|
188
|
-
"customerNotes": "Please leave package at front door",
|
|
189
|
-
"priority": "high",
|
|
190
|
-
"tags": "rush,gift"
|
|
191
|
-
}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
**Type Auto-Detection:**
|
|
195
|
-
- Numbers → `INTEGER` or `FLOAT` (auto-detected)
|
|
196
|
-
- Booleans → `BOOLEAN` (auto-detected)
|
|
197
|
-
- Objects → `JSON` (stringified)
|
|
198
|
-
- Strings → `STRING` (default)
|
|
199
|
-
|
|
200
|
-
**cURL Example:**
|
|
201
|
-
```bash
|
|
202
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
203
|
-
-H "Content-Type: application/json" \
|
|
204
|
-
-H "X-API-Key: your-api-key" \
|
|
205
|
-
-d '{
|
|
206
|
-
"orderRef": "ORD-12345",
|
|
207
|
-
"externalOrderId": "EXT-123456789",
|
|
208
|
-
"customerNotes": "Please leave package at front door",
|
|
209
|
-
"priority": "high"
|
|
210
|
-
}'
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Complete Sample Payloads
|
|
214
|
-
|
|
215
|
-
**Example 1: External System Reference Update**
|
|
216
|
-
```json
|
|
217
|
-
{
|
|
218
|
-
"orderRef": "ORD-0017326966182",
|
|
219
|
-
"externalOrderId": "EXT-123456789",
|
|
220
|
-
"sourceSystem": "ERP-SAP",
|
|
221
|
-
"integrationId": "INT-987654321"
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Example 2: Customer Notes and Business Flags**
|
|
226
|
-
```json
|
|
227
|
-
{
|
|
228
|
-
"orderRef": "ORD-0017326966182",
|
|
229
|
-
"customerNotes": "Please leave package at front door",
|
|
230
|
-
"priority": "high",
|
|
231
|
-
"tags": "rush,gift",
|
|
232
|
-
"specialHandling": true
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
**Note:** Metadata fields (`updateReason`, `updatedBy`, `timestamp`) are optional and excluded from attributes. All other fields are treated as order attributes.
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
## GraphQL Mutations
|
|
241
|
-
|
|
242
|
-
### Query Order by Reference
|
|
243
|
-
|
|
244
|
-
```graphql
|
|
245
|
-
query GetOrder($ref: String!) {
|
|
246
|
-
order(ref: $ref) {
|
|
247
|
-
id
|
|
248
|
-
ref
|
|
249
|
-
status
|
|
250
|
-
attributes {
|
|
251
|
-
name
|
|
252
|
-
type
|
|
253
|
-
value
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### Update Order Mutation
|
|
260
|
-
|
|
261
|
-
```graphql
|
|
262
|
-
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
263
|
-
updateOrder(input: $input) {
|
|
264
|
-
id
|
|
265
|
-
ref
|
|
266
|
-
status
|
|
267
|
-
attributes {
|
|
268
|
-
name
|
|
269
|
-
type
|
|
270
|
-
value
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
**UpdateOrderInput Structure:**
|
|
277
|
-
- `id` (ID!) - Order ID (required) - Must use Fluent internal ID, not reference
|
|
278
|
-
- `attributes` (Array<AttributeInput>) - Attributes to update
|
|
279
|
-
- `status` (String) - Optional status update
|
|
280
|
-
- Other optional fields as per schema
|
|
281
|
-
|
|
282
|
-
**Note:** UpdateOrderInput requires `id: ID!` (the Fluent internal ID), not `ref`. This is why the template queries the order first to get the ID.
|
|
283
|
-
|
|
284
|
-
**AttributeInput Structure:**
|
|
285
|
-
- `name` (String!) - Attribute name
|
|
286
|
-
- `type` (String!) - Attribute type (STRING, INTEGER, FLOAT, BOOLEAN, JSON, DATE)
|
|
287
|
-
- `value` (String!) - Attribute value (stringified for non-STRING types)
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## 🔧 Complete Production Code
|
|
292
|
-
|
|
293
|
-
### Versori Workflows Structure
|
|
294
|
-
|
|
295
|
-
**Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
|
|
296
|
-
|
|
297
|
-
**Trigger Types:**
|
|
298
|
-
- **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
|
|
299
|
-
|
|
300
|
-
**Execution Steps (chained to triggers):**
|
|
301
|
-
- **`http()`** → External API calls (chained from webhook)
|
|
302
|
-
|
|
303
|
-
### Recommended Project Structure
|
|
304
|
-
|
|
305
|
-
```
|
|
306
|
-
order-update-webhook/
|
|
307
|
-
├── index.ts # Entry point - exports all workflows
|
|
308
|
-
└── src/
|
|
309
|
-
├── workflows/
|
|
310
|
-
│ └── webhook/
|
|
311
|
-
│ └── order-update.ts # Webhook: Order update handler
|
|
312
|
-
│
|
|
313
|
-
├── services/
|
|
314
|
-
│ └── order-update.service.ts # Shared orchestration logic
|
|
315
|
-
│
|
|
316
|
-
├── utils/
|
|
317
|
-
│ └── response-status.utils.ts # FC status code mapping utilities
|
|
318
|
-
│
|
|
319
|
-
└── types/
|
|
320
|
-
└── order-update.types.ts # Type definitions
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
**Benefits:**
|
|
324
|
-
- ✅ Clear structure (webhook handlers in `webhook/`)
|
|
325
|
-
- ✅ Descriptive file names (easy to browse and understand)
|
|
326
|
-
- ✅ Reusable code in `services/` (DRY principle)
|
|
327
|
-
- ✅ Centralized type definitions
|
|
328
|
-
- ✅ Utility functions for consistent error handling (FC status codes)
|
|
329
|
-
|
|
330
|
-
---
|
|
331
|
-
|
|
332
|
-
## Workflow Files
|
|
333
|
-
|
|
334
|
-
### 1. Webhook Workflow (`src/workflows/webhook/order-update.ts`)
|
|
335
|
-
|
|
336
|
-
**Purpose**: Handle order update requests via webhook
|
|
337
|
-
**Trigger**: HTTP POST
|
|
338
|
-
**Endpoint**: `POST https://{workspace}.versori.run/order-update`
|
|
339
|
-
**Use Cases**: Order attribute updates from external systems, status changes
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
import { webhook, http } from '@versori/run';
|
|
343
|
-
import { executeOrderUpdate } from '../../services/order-update.service';
|
|
344
|
-
import { mapErrorToFCStatus } from '../../utils/response-status.utils';
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Webhook: Order Update Handler
|
|
348
|
-
*
|
|
349
|
-
* Endpoint: POST https://{workspace}.versori.run/order-update
|
|
350
|
-
* Request body: { "orderRef": "ORD-12345", "externalOrderId": "EXT-123", "customerNotes": "...", ... }
|
|
351
|
-
*
|
|
352
|
-
* Pattern: webhook().then(http()) - needs Fluent API access
|
|
353
|
-
* Uses shared service: order-update.service.ts
|
|
354
|
-
*
|
|
355
|
-
* SECURITY: Authentication handled via connection parameter
|
|
356
|
-
* No manual API key validation needed - Versori manages this via connection auth
|
|
357
|
-
*/
|
|
358
|
-
export const orderUpdateWebhook = webhook('order-update', {
|
|
359
|
-
response: { mode: 'sync' }, // ✅ Sync mode: response sent when handler returns
|
|
360
|
-
connection: 'order-update-webhook', // Versori validates API key
|
|
361
|
-
}).then(
|
|
362
|
-
http('process-order-update', { connection: 'fluent_commerce' }, async ctx => {
|
|
363
|
-
const { log, data } = ctx;
|
|
364
|
-
|
|
365
|
-
log.info('🚀 [WEBHOOK] Order update request received', {
|
|
366
|
-
timestamp: new Date().toISOString(),
|
|
367
|
-
hasPayload: !!data,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
// Reuse shared orchestration logic
|
|
372
|
-
const result = await executeOrderUpdate(ctx);
|
|
373
|
-
|
|
374
|
-
log.info('✅ [WEBHOOK] Order update completed successfully', {
|
|
375
|
-
orderRef: result.orderRef,
|
|
376
|
-
orderId: result.orderId,
|
|
377
|
-
attributesUpdated: result.attributesUpdated,
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Return response with FC200 status code
|
|
381
|
-
return {
|
|
382
|
-
success: true,
|
|
383
|
-
statusCode: result.statusCode,
|
|
384
|
-
orderRef: result.orderRef,
|
|
385
|
-
orderId: result.orderId,
|
|
386
|
-
attributesUpdated: result.attributesUpdated,
|
|
387
|
-
message: result.message || 'Order attributes updated successfully',
|
|
388
|
-
};
|
|
389
|
-
} catch (e: any) {
|
|
390
|
-
log.error('❌ [WEBHOOK] Order update failed', {
|
|
391
|
-
message: e?.message,
|
|
392
|
-
stack: e?.stack,
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Map error to FC status code
|
|
396
|
-
const fcStatus = mapErrorToFCStatus(e);
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
success: false,
|
|
400
|
-
statusCode: fcStatus.statusCode,
|
|
401
|
-
retryable: fcStatus.retryable,
|
|
402
|
-
error: fcStatus.message,
|
|
403
|
-
details: e?.message || 'Unknown error occurred',
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
})
|
|
407
|
-
);
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
### 2. Entry Point (`index.ts`)
|
|
413
|
-
|
|
414
|
-
**Purpose**: Register all workflows with Versori platform
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
/**
|
|
418
|
-
* Entry Point - Registers all workflows with Versori platform
|
|
419
|
-
*
|
|
420
|
-
* Versori automatically discovers and registers exported workflows
|
|
421
|
-
*
|
|
422
|
-
* File Structure:
|
|
423
|
-
* - src/workflows/webhook/ → HTTP-based triggers (webhooks)
|
|
424
|
-
* - src/services/ → Shared service logic (reusable across workflows)
|
|
425
|
-
*/
|
|
426
|
-
|
|
427
|
-
// Import webhook workflows
|
|
428
|
-
import { orderUpdateWebhook } from './src/workflows/webhook/order-update';
|
|
429
|
-
|
|
430
|
-
// Register all workflows with Versori platform
|
|
431
|
-
export {
|
|
432
|
-
// Webhooks (HTTP-based triggers)
|
|
433
|
-
orderUpdateWebhook,
|
|
434
|
-
};
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
**What Gets Exposed:**
|
|
438
|
-
- ✅ `orderUpdateWebhook` → `https://{workspace}.versori.run/order-update`
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## 3. Type Definitions (`src/types/order-update.types.ts`)
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
/**
|
|
446
|
-
* Type Definitions for Order Update Ingestion
|
|
447
|
-
*
|
|
448
|
-
* Centralized type definitions for order update workflow
|
|
449
|
-
*/
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Order update request payload
|
|
453
|
-
*
|
|
454
|
-
* PRIMARY FORMAT: Flat key-value pairs
|
|
455
|
-
* - orderRef: Required order reference
|
|
456
|
-
* - All other fields (except metadata fields) are treated as attributes
|
|
457
|
-
*
|
|
458
|
-
* Example:
|
|
459
|
-
* {
|
|
460
|
-
* "orderRef": "ORD-12345",
|
|
461
|
-
* "externalOrderId": "EXT-123456789",
|
|
462
|
-
* "customerNotes": "Please leave package at front door",
|
|
463
|
-
* "priority": "high"
|
|
464
|
-
* }
|
|
465
|
-
*/
|
|
466
|
-
export interface OrderUpdatePayload {
|
|
467
|
-
orderRef: string;
|
|
468
|
-
// Flat attributes: all fields except orderRef are treated as attributes
|
|
469
|
-
[key: string]: string | number | boolean | unknown;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Fluent AttributeInput format
|
|
474
|
-
*/
|
|
475
|
-
export interface AttributeInput {
|
|
476
|
-
name: string;
|
|
477
|
-
type: 'STRING' | 'INTEGER' | 'FLOAT' | 'BOOLEAN' | 'JSON' | 'DATE';
|
|
478
|
-
value: string;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Order query result
|
|
483
|
-
*/
|
|
484
|
-
export interface OrderQueryResult {
|
|
485
|
-
id: string;
|
|
486
|
-
ref: string;
|
|
487
|
-
status?: string;
|
|
488
|
-
attributes?: Array<{
|
|
489
|
-
name: string;
|
|
490
|
-
type: string;
|
|
491
|
-
value: string;
|
|
492
|
-
}>;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Order update result with FC status code
|
|
497
|
-
*/
|
|
498
|
-
export interface OrderUpdateResult {
|
|
499
|
-
success: boolean;
|
|
500
|
-
statusCode: 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
501
|
-
orderRef: string;
|
|
502
|
-
orderId: string;
|
|
503
|
-
attributesUpdated: number;
|
|
504
|
-
message?: string;
|
|
505
|
-
error?: string;
|
|
506
|
-
retryable?: boolean;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Versori Context Interface
|
|
511
|
-
* Represents the Versori runtime context passed to workflow functions
|
|
512
|
-
*/
|
|
513
|
-
export interface VersoriContext {
|
|
514
|
-
log: {
|
|
515
|
-
info: (message: string, data?: Record<string, unknown>) => void;
|
|
516
|
-
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
517
|
-
error: (message: string, data?: Record<string, unknown>) => void;
|
|
518
|
-
debug?: (message: string, data?: Record<string, unknown>) => void;
|
|
519
|
-
};
|
|
520
|
-
data?: unknown;
|
|
521
|
-
request?: Request;
|
|
522
|
-
activation: {
|
|
523
|
-
getVariable: (name: string) => string | undefined;
|
|
524
|
-
connections?: Record<string, unknown>;
|
|
525
|
-
};
|
|
526
|
-
connections?: Record<string, unknown>;
|
|
527
|
-
}
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## 4. Utility: Response Status Code Mapper (`src/utils/response-status.utils.ts`)
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
/**
|
|
536
|
-
* Response Status Code Utility
|
|
537
|
-
*
|
|
538
|
-
* Maps errors to Fluent Commerce (FC) status codes for consistent API responses
|
|
539
|
-
*/
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* FC Status Codes:
|
|
543
|
-
* - FC200: OK (Successfully - No further action needed)
|
|
544
|
-
* - FC400: Bad Request (Non-retryable)
|
|
545
|
-
* - FC409: Duplicate Request, Entity already exists (Non-retryable)
|
|
546
|
-
* - FC504: Timeout (Retryable)
|
|
547
|
-
*/
|
|
548
|
-
|
|
549
|
-
export type FCStatusCode = 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
550
|
-
|
|
551
|
-
export interface FCStatusMapping {
|
|
552
|
-
statusCode: FCStatusCode;
|
|
553
|
-
retryable: boolean;
|
|
554
|
-
message: string;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Map error to FC status code based on error message and type
|
|
559
|
-
*/
|
|
560
|
-
export function mapErrorToFCStatus(error: unknown): FCStatusMapping {
|
|
561
|
-
const errorMessage =
|
|
562
|
-
error instanceof Error ? error.message : String(error);
|
|
563
|
-
const lowerMessage = errorMessage.toLowerCase();
|
|
564
|
-
|
|
565
|
-
// FC409: Duplicate/Already exists (Non-retryable)
|
|
566
|
-
if (
|
|
567
|
-
lowerMessage.includes('already exists') ||
|
|
568
|
-
lowerMessage.includes('duplicate') ||
|
|
569
|
-
lowerMessage.includes('unique constraint') ||
|
|
570
|
-
lowerMessage.includes('entity already exists')
|
|
571
|
-
) {
|
|
572
|
-
return {
|
|
573
|
-
statusCode: 'FC409',
|
|
574
|
-
retryable: false,
|
|
575
|
-
message: 'Entity already exists. Duplicate request detected.',
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// FC504: Timeout (Retryable)
|
|
580
|
-
if (
|
|
581
|
-
lowerMessage.includes('timeout') ||
|
|
582
|
-
lowerMessage.includes('timed out') ||
|
|
583
|
-
lowerMessage.includes('request timeout') ||
|
|
584
|
-
(error instanceof Error && error.name === 'TimeoutError')
|
|
585
|
-
) {
|
|
586
|
-
return {
|
|
587
|
-
statusCode: 'FC504',
|
|
588
|
-
retryable: true,
|
|
589
|
-
message: 'Request timeout. Please retry.',
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// FC400: Bad Request (Non-retryable)
|
|
594
|
-
// Validation errors, missing fields, invalid format
|
|
595
|
-
if (
|
|
596
|
-
lowerMessage.includes('required') ||
|
|
597
|
-
lowerMessage.includes('missing') ||
|
|
598
|
-
lowerMessage.includes('invalid') ||
|
|
599
|
-
lowerMessage.includes('bad request') ||
|
|
600
|
-
lowerMessage.includes('validation') ||
|
|
601
|
-
lowerMessage.includes('not found') ||
|
|
602
|
-
lowerMessage.includes('does not exist')
|
|
603
|
-
) {
|
|
604
|
-
return {
|
|
605
|
-
statusCode: 'FC400',
|
|
606
|
-
retryable: false,
|
|
607
|
-
message: 'Bad request. Please verify payload and try again.',
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Default: FC400 for unknown errors (Non-retryable)
|
|
612
|
-
return {
|
|
613
|
-
statusCode: 'FC400',
|
|
614
|
-
retryable: false,
|
|
615
|
-
message: errorMessage,
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Create success response with FC200 status
|
|
621
|
-
*/
|
|
622
|
-
export function createSuccessResponse<T extends Record<string, unknown>>(
|
|
623
|
-
data: T
|
|
624
|
-
): T & { statusCode: 'FC200'; retryable: false } {
|
|
625
|
-
return {
|
|
626
|
-
...data,
|
|
627
|
-
statusCode: 'FC200' as const,
|
|
628
|
-
retryable: false,
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
---
|
|
634
|
-
|
|
635
|
-
## 5. Service: Order Update Processor (`src/services/order-update.service.ts`)
|
|
636
|
-
|
|
637
|
-
```typescript
|
|
638
|
-
/**
|
|
639
|
-
* Order Update Service
|
|
640
|
-
*
|
|
641
|
-
* Processes order update requests: extracts order reference and attributes from JSON payload,
|
|
642
|
-
* queries Fluent Commerce to get order ID, and updates order attributes via GraphQL mutation.
|
|
643
|
-
*/
|
|
644
|
-
|
|
645
|
-
import {
|
|
646
|
-
createClient,
|
|
647
|
-
JSONParserService,
|
|
648
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
649
|
-
import { Buffer } from 'node:buffer';
|
|
650
|
-
import { mapErrorToFCStatus, createSuccessResponse } from '../utils/response-status.utils';
|
|
651
|
-
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
652
|
-
import type {
|
|
653
|
-
OrderUpdatePayload,
|
|
654
|
-
OrderUpdateResult,
|
|
655
|
-
VersoriContext,
|
|
656
|
-
AttributeInput,
|
|
657
|
-
OrderQueryResult,
|
|
658
|
-
} from '../types/order-update.types';
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* GraphQL query to get order by reference
|
|
662
|
-
*/
|
|
663
|
-
const GET_ORDER_QUERY = `
|
|
664
|
-
query GetOrder($ref: String!) {
|
|
665
|
-
order(ref: $ref) {
|
|
666
|
-
id
|
|
667
|
-
ref
|
|
668
|
-
status
|
|
669
|
-
attributes {
|
|
670
|
-
name
|
|
671
|
-
type
|
|
672
|
-
value
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
`;
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* GraphQL mutation to update order
|
|
680
|
-
*/
|
|
681
|
-
const UPDATE_ORDER_MUTATION = `
|
|
682
|
-
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
683
|
-
updateOrder(input: $input) {
|
|
684
|
-
id
|
|
685
|
-
ref
|
|
686
|
-
status
|
|
687
|
-
attributes {
|
|
688
|
-
name
|
|
689
|
-
type
|
|
690
|
-
value
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
`;
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Extract attributes from flat payload format
|
|
698
|
-
*
|
|
699
|
-
* PRIMARY FORMAT: All fields except orderRef (and metadata fields) are attributes
|
|
700
|
-
* - orderRef: Required, excluded from attributes
|
|
701
|
-
* - Metadata fields: updateReason, updatedBy, timestamp (optional, excluded)
|
|
702
|
-
* - All other fields: Treated as attributes with auto-detected types
|
|
703
|
-
*
|
|
704
|
-
* Example payload:
|
|
705
|
-
* {
|
|
706
|
-
* "orderRef": "ORD-12345",
|
|
707
|
-
* "externalOrderId": "EXT-123456789", // → attribute
|
|
708
|
-
* "customerNotes": "Front door", // → attribute
|
|
709
|
-
* "priority": "high" // → attribute
|
|
710
|
-
* }
|
|
711
|
-
*/
|
|
712
|
-
function extractAttributesFromPayload(
|
|
713
|
-
payload: OrderUpdatePayload
|
|
714
|
-
): AttributeInput[] {
|
|
715
|
-
const metadataFields = ['orderRef', 'updateReason', 'updatedBy', 'timestamp'];
|
|
716
|
-
const attributes: AttributeInput[] = [];
|
|
717
|
-
|
|
718
|
-
// Extract all fields except orderRef and metadata fields
|
|
719
|
-
for (const [name, value] of Object.entries(payload)) {
|
|
720
|
-
// Skip metadata fields
|
|
721
|
-
if (metadataFields.includes(name)) {
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// Skip null/undefined values
|
|
726
|
-
if (value === null || value === undefined) {
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// Auto-detect type from value
|
|
731
|
-
let type: AttributeInput['type'] = 'STRING';
|
|
732
|
-
let stringValue: string;
|
|
733
|
-
|
|
734
|
-
if (typeof value === 'number') {
|
|
735
|
-
type = Number.isInteger(value) ? 'INTEGER' : 'FLOAT';
|
|
736
|
-
stringValue = String(value);
|
|
737
|
-
} else if (typeof value === 'boolean') {
|
|
738
|
-
type = 'BOOLEAN';
|
|
739
|
-
stringValue = String(value);
|
|
740
|
-
} else if (typeof value === 'object') {
|
|
741
|
-
type = 'JSON';
|
|
742
|
-
stringValue = JSON.stringify(value);
|
|
743
|
-
} else {
|
|
744
|
-
// String or other types → STRING
|
|
745
|
-
stringValue = String(value);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
attributes.push({
|
|
749
|
-
name,
|
|
750
|
-
type,
|
|
751
|
-
value: stringValue,
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return attributes;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* Parse webhook payload from Versori context
|
|
760
|
-
*
|
|
761
|
-
* Handles both pre-parsed JSON (ctx.data) and raw request body (ctx.request)
|
|
762
|
-
*/
|
|
763
|
-
async function parseWebhookPayload(
|
|
764
|
-
ctx: VersoriContext
|
|
765
|
-
): Promise<OrderUpdatePayload | null> {
|
|
766
|
-
const { log, data, request } = ctx;
|
|
767
|
-
|
|
768
|
-
// Scenario 1: Pre-parsed JSON (Versori auto-parsed)
|
|
769
|
-
if (data && typeof data === 'object' && data !== null) {
|
|
770
|
-
log.info('📥 [PARSE] Using pre-parsed JSON payload', {
|
|
771
|
-
hasData: true,
|
|
772
|
-
dataKeys: Object.keys(data),
|
|
773
|
-
});
|
|
774
|
-
return data as OrderUpdatePayload;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// Scenario 2: Raw request body (need to parse)
|
|
778
|
-
if (request && typeof request.text === 'function') {
|
|
779
|
-
log.info('📥 [PARSE] Extracting raw request body');
|
|
780
|
-
try {
|
|
781
|
-
const rawBody = await request.text();
|
|
782
|
-
if (!rawBody || rawBody.trim() === '') {
|
|
783
|
-
log.error('❌ [PARSE] Request body is empty');
|
|
784
|
-
return null;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const parser = new JSONParserService(log);
|
|
788
|
-
const parsed = await parser.parse(rawBody);
|
|
789
|
-
|
|
790
|
-
log.info('📥 [PARSE] Successfully parsed JSON payload', {
|
|
791
|
-
parsedKeys: Object.keys(parsed),
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
return parsed as OrderUpdatePayload;
|
|
795
|
-
} catch (parseError: unknown) {
|
|
796
|
-
const errorMessage =
|
|
797
|
-
parseError instanceof Error ? parseError.message : String(parseError);
|
|
798
|
-
log.error('❌ [PARSE] Failed to parse JSON payload', {
|
|
799
|
-
error: errorMessage,
|
|
800
|
-
});
|
|
801
|
-
return null;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
log.error('❌ [PARSE] No valid payload found in context');
|
|
806
|
-
return null;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Query order by reference to get ID and current state
|
|
811
|
-
*/
|
|
812
|
-
async function queryOrderByRef(
|
|
813
|
-
client: FluentClient,
|
|
814
|
-
orderRef: string,
|
|
815
|
-
log: VersoriContext['log']
|
|
816
|
-
): Promise<OrderQueryResult> {
|
|
817
|
-
log.info('🔍 [QUERY] Querying order by reference', { orderRef });
|
|
818
|
-
|
|
819
|
-
const result = await client.graphql({
|
|
820
|
-
query: GET_ORDER_QUERY,
|
|
821
|
-
variables: { ref: orderRef },
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
if (result.errors && result.errors.length > 0) {
|
|
825
|
-
log.error('❌ [QUERY] GraphQL query returned errors', {
|
|
826
|
-
errors: result.errors,
|
|
827
|
-
orderRef,
|
|
828
|
-
});
|
|
829
|
-
throw new Error(`GraphQL query failed: ${result.errors[0].message}`);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
const order = (result.data as any)?.order;
|
|
833
|
-
|
|
834
|
-
if (!order) {
|
|
835
|
-
log.error('❌ [QUERY] Order not found', { orderRef });
|
|
836
|
-
throw new Error(`Order not found: ${orderRef}`);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
log.info('✅ [QUERY] Order found', {
|
|
840
|
-
orderId: order.id,
|
|
841
|
-
orderRef: order.ref,
|
|
842
|
-
status: order.status,
|
|
843
|
-
currentAttributeCount: order.attributes?.length || 0,
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
return order as OrderQueryResult;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Execute order update workflow
|
|
851
|
-
*
|
|
852
|
-
* Main orchestration function:
|
|
853
|
-
* 1. Parse webhook payload
|
|
854
|
-
* 2. Extract order reference and attributes
|
|
855
|
-
* 3. Create Fluent client
|
|
856
|
-
* 4. Query order by reference to get ID
|
|
857
|
-
* 5. Build update mutation variables
|
|
858
|
-
* 6. Execute updateOrder mutation
|
|
859
|
-
*
|
|
860
|
-
* Note: Authentication handled via Versori connection (fluent_commerce).
|
|
861
|
-
* Retailer ID is not required for GraphQL mutations - connection handles authentication.
|
|
862
|
-
*
|
|
863
|
-
* CRITICAL: UpdateOrderInput requires id: ID! (not ref).
|
|
864
|
-
* The query step retrieves order.id which must be used in the mutation.
|
|
865
|
-
*/
|
|
866
|
-
export async function executeOrderUpdate(
|
|
867
|
-
ctx: VersoriContext
|
|
868
|
-
): Promise<OrderUpdateResult> {
|
|
869
|
-
const { log, activation } = ctx;
|
|
870
|
-
|
|
871
|
-
log.info('🚀 [SERVICE] Starting order update processing');
|
|
872
|
-
|
|
873
|
-
// STEP 1: Parse webhook payload
|
|
874
|
-
const payload = await parseWebhookPayload(ctx);
|
|
875
|
-
if (!payload) {
|
|
876
|
-
log.error('❌ [SERVICE] Failed to parse webhook payload', {
|
|
877
|
-
hasData: !!ctx.data,
|
|
878
|
-
hasRequest: !!ctx.request,
|
|
879
|
-
});
|
|
880
|
-
throw new Error('Failed to parse webhook payload. Ensure Content-Type: application/json is set in request headers.');
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
log.info('📥 [SERVICE] Payload parsed successfully', {
|
|
884
|
-
payloadKeys: Object.keys(payload),
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
// STEP 2: Extract order reference
|
|
888
|
-
if (!payload.orderRef || typeof payload.orderRef !== 'string') {
|
|
889
|
-
log.error('❌ [SERVICE] Order reference not found in payload', {
|
|
890
|
-
payloadKeys: Object.keys(payload),
|
|
891
|
-
expectedField: 'orderRef',
|
|
892
|
-
});
|
|
893
|
-
throw new Error('Order reference (orderRef) is required in payload');
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
const orderRef = payload.orderRef;
|
|
897
|
-
log.info('✅ [SERVICE] Order reference extracted', { orderRef });
|
|
898
|
-
|
|
899
|
-
// STEP 3: Extract attributes from flat payload format
|
|
900
|
-
// ✅ PRIMARY FORMAT: All fields except orderRef are treated as attributes
|
|
901
|
-
const attributes = extractAttributesFromPayload(payload);
|
|
902
|
-
if (attributes.length === 0) {
|
|
903
|
-
log.warn('⚠️ [SERVICE] No attributes provided in payload', {
|
|
904
|
-
orderRef,
|
|
905
|
-
payloadKeys: Object.keys(payload),
|
|
906
|
-
});
|
|
907
|
-
throw new Error('At least one attribute is required for order update. Provide fields like: { "orderRef": "ORD-123", "externalOrderId": "EXT-123", "customerNotes": "Leave at door" }');
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
log.info('✅ [SERVICE] Attributes extracted', {
|
|
911
|
-
orderRef,
|
|
912
|
-
attributeCount: attributes.length,
|
|
913
|
-
attributeNames: attributes.map(a => a.name),
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
// STEP 4: Create Fluent client
|
|
917
|
-
// ✅ Pass ctx directly - createClient auto-detects Versori context
|
|
918
|
-
// Authentication handled via Versori connection (fluent_commerce)
|
|
919
|
-
const client = await createClient(ctx);
|
|
920
|
-
|
|
921
|
-
log.info('✅ [SERVICE] Fluent client created');
|
|
922
|
-
|
|
923
|
-
// STEP 5: Query order by reference to get ID
|
|
924
|
-
// ✅ Query first to:
|
|
925
|
-
// - Validate order exists (fail-fast if order not found)
|
|
926
|
-
// - Get order ID for logging and tracking
|
|
927
|
-
// - Get current order state (optional: can merge with existing attributes)
|
|
928
|
-
const order = await queryOrderByRef(client, orderRef, log);
|
|
929
|
-
|
|
930
|
-
// STEP 6: Build update mutation variables
|
|
931
|
-
// ✅ CRITICAL: UpdateOrderInput requires id: ID! (not ref)
|
|
932
|
-
// Use the ID from queryOrderByRef result - this is why we query first!
|
|
933
|
-
const updateVariables = {
|
|
934
|
-
input: {
|
|
935
|
-
id: order.id, // ✅ REQUIRED: UpdateOrderInput.id is ID! (required field)
|
|
936
|
-
attributes: attributes,
|
|
937
|
-
},
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
log.info('📤 [SERVICE] Executing updateOrder mutation', {
|
|
941
|
-
orderRef,
|
|
942
|
-
orderId: order.id,
|
|
943
|
-
attributeCount: attributes.length,
|
|
944
|
-
});
|
|
945
|
-
|
|
946
|
-
// STEP 7: Execute updateOrder mutation
|
|
947
|
-
const mutationResult = await client.graphql({
|
|
948
|
-
query: UPDATE_ORDER_MUTATION,
|
|
949
|
-
variables: updateVariables,
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
if (mutationResult.errors && mutationResult.errors.length > 0) {
|
|
953
|
-
log.error('❌ [SERVICE] GraphQL mutation returned errors', {
|
|
954
|
-
errors: mutationResult.errors,
|
|
955
|
-
orderRef,
|
|
956
|
-
orderId: order.id,
|
|
957
|
-
});
|
|
958
|
-
throw new Error(`GraphQL mutation failed: ${mutationResult.errors[0].message}`);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const updatedOrder = (mutationResult.data as any)?.updateOrder;
|
|
962
|
-
|
|
963
|
-
if (!updatedOrder) {
|
|
964
|
-
log.error('❌ [SERVICE] No order data returned from mutation', { orderRef });
|
|
965
|
-
throw new Error('No order data returned from updateOrder mutation');
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
log.info('✅ [SERVICE] Order updated successfully', {
|
|
969
|
-
orderRef,
|
|
970
|
-
orderId: updatedOrder.id,
|
|
971
|
-
attributesUpdated: attributes.length,
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
// Return success response with FC200 status code
|
|
975
|
-
return createSuccessResponse({
|
|
976
|
-
success: true,
|
|
977
|
-
orderRef,
|
|
978
|
-
orderId: updatedOrder.id,
|
|
979
|
-
attributesUpdated: attributes.length,
|
|
980
|
-
message: 'Order attributes updated successfully',
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
```
|
|
984
|
-
|
|
985
|
-
---
|
|
986
|
-
|
|
987
|
-
## 5. Configuration
|
|
988
|
-
|
|
989
|
-
### Activation Variables
|
|
990
|
-
|
|
991
|
-
No activation variables required. Authentication is handled via Versori connection (`fluent_commerce`).
|
|
992
|
-
|
|
993
|
-
**Note:** Configure Fluent Commerce credentials in the Versori connection settings. The connection handles authentication automatically.
|
|
994
|
-
|
|
995
|
-
### Webhook Connection
|
|
996
|
-
|
|
997
|
-
Create a Versori connection for webhook authentication:
|
|
998
|
-
|
|
999
|
-
1. **Connection Name:** `order-update-webhook`
|
|
1000
|
-
2. **Type:** API Key or Basic Auth
|
|
1001
|
-
3. **Purpose:** Authenticate incoming webhook requests
|
|
1002
|
-
|
|
1003
|
-
**Example webhook call:**
|
|
1004
|
-
|
|
1005
|
-
```bash
|
|
1006
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1007
|
-
-H "Content-Type: application/json" \
|
|
1008
|
-
-H "X-API-Key: your-api-key" \
|
|
1009
|
-
-d '{
|
|
1010
|
-
"orderRef": "ORD-12345",
|
|
1011
|
-
"externalOrderId": "EXT-123456789",
|
|
1012
|
-
"customerNotes": "Please leave package at front door",
|
|
1013
|
-
"priority": "high"
|
|
1014
|
-
}'
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
---
|
|
1018
|
-
|
|
1019
|
-
## 6. Error Handling
|
|
1020
|
-
|
|
1021
|
-
The template includes comprehensive error handling:
|
|
1022
|
-
|
|
1023
|
-
### Payload Parsing Errors
|
|
1024
|
-
|
|
1025
|
-
- **Empty payload:** Returns error response
|
|
1026
|
-
- **Invalid JSON:** Returns error with parse details
|
|
1027
|
-
- **Missing order reference:** Returns error with expected field name
|
|
1028
|
-
- **Missing attributes:** Returns error if no attributes provided
|
|
1029
|
-
|
|
1030
|
-
### Order Query Errors
|
|
1031
|
-
|
|
1032
|
-
- **Order not found:** Returns error with order reference
|
|
1033
|
-
- **GraphQL query errors:** Returns error with GraphQL error details
|
|
1034
|
-
|
|
1035
|
-
### Mutation Errors
|
|
1036
|
-
|
|
1037
|
-
- **GraphQL mutation errors:** Returns error with mutation error details
|
|
1038
|
-
- **No response data:** Returns error if mutation doesn't return order data
|
|
1039
|
-
|
|
1040
|
-
### Response Format with FC Status Codes
|
|
1041
|
-
|
|
1042
|
-
**Success Response (FC200):**
|
|
1043
|
-
```json
|
|
1044
|
-
{
|
|
1045
|
-
"success": true,
|
|
1046
|
-
"statusCode": "FC200",
|
|
1047
|
-
"orderRef": "ORD-12345",
|
|
1048
|
-
"orderId": "12345",
|
|
1049
|
-
"attributesUpdated": 3,
|
|
1050
|
-
"message": "Order attributes updated successfully"
|
|
1051
|
-
}
|
|
1052
|
-
```
|
|
1053
|
-
|
|
1054
|
-
**Error Responses:**
|
|
1055
|
-
|
|
1056
|
-
**FC400 - Bad Request (Non-retryable):**
|
|
1057
|
-
```json
|
|
1058
|
-
{
|
|
1059
|
-
"success": false,
|
|
1060
|
-
"statusCode": "FC400",
|
|
1061
|
-
"retryable": false,
|
|
1062
|
-
"error": "Bad request. Please verify payload and try again.",
|
|
1063
|
-
"details": "Order reference (orderRef) is required in payload"
|
|
1064
|
-
}
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
**FC409 - Duplicate/Already Exists (Non-retryable):**
|
|
1068
|
-
```json
|
|
1069
|
-
{
|
|
1070
|
-
"success": false,
|
|
1071
|
-
"statusCode": "FC409",
|
|
1072
|
-
"retryable": false,
|
|
1073
|
-
"error": "Entity already exists. Duplicate request detected.",
|
|
1074
|
-
"details": "Order with reference ORD-12345 already exists"
|
|
1075
|
-
}
|
|
1076
|
-
```
|
|
1077
|
-
|
|
1078
|
-
**FC504 - Timeout (Retryable):**
|
|
1079
|
-
```json
|
|
1080
|
-
{
|
|
1081
|
-
"success": false,
|
|
1082
|
-
"statusCode": "FC504",
|
|
1083
|
-
"retryable": true,
|
|
1084
|
-
"error": "Request timeout. Please retry.",
|
|
1085
|
-
"details": "GraphQL mutation timed out after 30 seconds"
|
|
1086
|
-
}
|
|
1087
|
-
```
|
|
1088
|
-
|
|
1089
|
-
---
|
|
1090
|
-
|
|
1091
|
-
## 7. Testing
|
|
1092
|
-
|
|
1093
|
-
### Test with cURL - Flat Key-Value Format
|
|
1094
|
-
|
|
1095
|
-
```bash
|
|
1096
|
-
# Test order update webhook with flat key-value format
|
|
1097
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1098
|
-
-H "Content-Type: application/json" \
|
|
1099
|
-
-H "X-API-Key: your-api-key" \
|
|
1100
|
-
-d '{
|
|
1101
|
-
"orderRef": "ORD-12345",
|
|
1102
|
-
"externalOrderId": "EXT-123456789",
|
|
1103
|
-
"customerNotes": "Please leave package at front door",
|
|
1104
|
-
"priority": "high"
|
|
1105
|
-
}'
|
|
1106
|
-
```
|
|
1107
|
-
|
|
1108
|
-
### Test with JSON File
|
|
1109
|
-
|
|
1110
|
-
**Create `order-update.json`:**
|
|
1111
|
-
```json
|
|
1112
|
-
{
|
|
1113
|
-
"orderRef": "ORD-0017326966182",
|
|
1114
|
-
"externalOrderId": "EXT-123456789",
|
|
1115
|
-
"customerNotes": "Please leave package at front door",
|
|
1116
|
-
"priority": "high"
|
|
1117
|
-
}
|
|
1118
|
-
```
|
|
1119
|
-
|
|
1120
|
-
**Send with cURL:**
|
|
1121
|
-
```bash
|
|
1122
|
-
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1123
|
-
-H "Content-Type: application/json" \
|
|
1124
|
-
-H "X-API-Key: your-api-key" \
|
|
1125
|
-
-d @order-update.json
|
|
1126
|
-
```
|
|
1127
|
-
|
|
1128
|
-
### Expected Success Response
|
|
1129
|
-
|
|
1130
|
-
```json
|
|
1131
|
-
{
|
|
1132
|
-
"success": true,
|
|
1133
|
-
"statusCode": "FC200",
|
|
1134
|
-
"orderRef": "ORD-12345",
|
|
1135
|
-
"orderId": "12345",
|
|
1136
|
-
"attributesUpdated": 1,
|
|
1137
|
-
"message": "Order attributes updated successfully"
|
|
1138
|
-
}
|
|
1139
|
-
```
|
|
1140
|
-
|
|
1141
|
-
**FC Status Code:** `FC200` - OK (Successfully - No further action needed)
|
|
1142
|
-
|
|
1143
|
-
### Expected Error Responses
|
|
1144
|
-
|
|
1145
|
-
**FC400 - Bad Request:**
|
|
1146
|
-
```json
|
|
1147
|
-
{
|
|
1148
|
-
"success": false,
|
|
1149
|
-
"statusCode": "FC400",
|
|
1150
|
-
"retryable": false,
|
|
1151
|
-
"error": "Bad request. Please verify payload and try again.",
|
|
1152
|
-
"details": "Order not found: ORD-12345"
|
|
1153
|
-
}
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
**FC409 - Duplicate/Already Exists:**
|
|
1157
|
-
```json
|
|
1158
|
-
{
|
|
1159
|
-
"success": false,
|
|
1160
|
-
"statusCode": "FC409",
|
|
1161
|
-
"retryable": false,
|
|
1162
|
-
"error": "Entity already exists. Duplicate request detected.",
|
|
1163
|
-
"details": "Order with reference ORD-12345 already exists"
|
|
1164
|
-
}
|
|
1165
|
-
```
|
|
1166
|
-
|
|
1167
|
-
**FC504 - Timeout:**
|
|
1168
|
-
```json
|
|
1169
|
-
{
|
|
1170
|
-
"success": false,
|
|
1171
|
-
"statusCode": "FC504",
|
|
1172
|
-
"retryable": true,
|
|
1173
|
-
"error": "Request timeout. Please retry.",
|
|
1174
|
-
"details": "GraphQL mutation timed out"
|
|
1175
|
-
}
|
|
1176
|
-
```
|
|
1177
|
-
|
|
1178
|
-
---
|
|
1179
|
-
|
|
1180
|
-
## 8. Customization
|
|
1181
|
-
|
|
1182
|
-
### Merge with Existing Attributes
|
|
1183
|
-
|
|
1184
|
-
To merge new attributes with existing ones (instead of replacing):
|
|
1185
|
-
|
|
1186
|
-
```typescript
|
|
1187
|
-
// In executeOrderUpdate function, after queryOrderByRef:
|
|
1188
|
-
const existingAttributes = order.attributes || [];
|
|
1189
|
-
const mergedAttributes = [
|
|
1190
|
-
...existingAttributes,
|
|
1191
|
-
...attributes, // New attributes override existing ones with same name
|
|
1192
|
-
];
|
|
1193
|
-
|
|
1194
|
-
const updateVariables = {
|
|
1195
|
-
input: {
|
|
1196
|
-
id: order.id, // ✅ REQUIRED: Use ID from query result
|
|
1197
|
-
attributes: mergedAttributes,
|
|
1198
|
-
},
|
|
1199
|
-
};
|
|
1200
|
-
```
|
|
1201
|
-
|
|
1202
|
-
### Custom Attribute Type Detection
|
|
1203
|
-
|
|
1204
|
-
To customize how attribute types are detected from object format:
|
|
1205
|
-
|
|
1206
|
-
```typescript
|
|
1207
|
-
function convertToAttributeInput(
|
|
1208
|
-
attributes: OrderUpdatePayload['attributes']
|
|
1209
|
-
): AttributeInput[] {
|
|
1210
|
-
// Add custom type detection logic
|
|
1211
|
-
// e.g., detect dates, URLs, etc.
|
|
1212
|
-
}
|
|
1213
|
-
```
|
|
1214
|
-
|
|
1215
|
-
### Additional Order Fields
|
|
1216
|
-
|
|
1217
|
-
To update additional order fields (status, etc.):
|
|
1218
|
-
|
|
1219
|
-
```typescript
|
|
1220
|
-
const updateVariables = {
|
|
1221
|
-
input: {
|
|
1222
|
-
id: order.id, // ✅ REQUIRED: Use ID from query result
|
|
1223
|
-
status: payload.status, // Update status if provided
|
|
1224
|
-
attributes: attributes,
|
|
1225
|
-
},
|
|
1226
|
-
};
|
|
1227
|
-
```
|
|
1228
|
-
|
|
1229
|
-
---
|
|
1230
|
-
|
|
1231
|
-
## Summary
|
|
1232
|
-
|
|
1233
|
-
✅ **DO:**
|
|
1234
|
-
- Use Versori connection for Fluent Commerce credentials
|
|
1235
|
-
- Query order first to validate existence and get ID
|
|
1236
|
-
- Use `id` from query result in UpdateOrderInput (required field)
|
|
1237
|
-
- Handle both array and object attribute formats
|
|
1238
|
-
- Log all steps for debugging
|
|
1239
|
-
- Return clear error messages
|
|
1240
|
-
|
|
1241
|
-
❌ **DON'T:**
|
|
1242
|
-
- Hardcode credentials in code
|
|
1243
|
-
- Skip order query validation
|
|
1244
|
-
- Use `ref` in UpdateOrderInput (must use `id: ID!`)
|
|
1245
|
-
- Assume payload format without validation
|
|
1246
|
-
- Skip error handling
|
|
1247
|
-
|
|
1248
|
-
---
|
|
1249
|
-
|
|
1250
|
-
## Next Steps
|
|
1251
|
-
|
|
1252
|
-
1. **Deploy:** Copy code to your Versori project
|
|
1253
|
-
2. **Configure:** Set up Fluent Commerce connection and activation variables
|
|
1254
|
-
3. **Test:** Test webhook with sample payloads
|
|
1255
|
-
4. **Monitor:** Check Versori logs for order updates
|
|
1256
|
-
|
|
1257
|
-
For more templates, see:
|
|
1258
|
-
- [XML Payload Template](./template-ingestion-payload-xml-order-update-graphql.md)
|
|
1259
|
-
- [Other GraphQL Mutation Templates](./graphql-mutations-guide.md)
|
|
1260
|
-
|
|
1
|
+
---
|
|
2
|
+
template_id: tpl-ingest-payload-json-to-order-update-graphql
|
|
3
|
+
canonical_filename: template-ingestion-payload-json-order-update-graphql.md
|
|
4
|
+
sdk_version: latest
|
|
5
|
+
runtime: versori
|
|
6
|
+
direction: ingestion
|
|
7
|
+
source: payload-json
|
|
8
|
+
destination: fluent-graphql
|
|
9
|
+
entity: order
|
|
10
|
+
format: json
|
|
11
|
+
logging: versori
|
|
12
|
+
status: stable
|
|
13
|
+
features:
|
|
14
|
+
- direct-payload-processing
|
|
15
|
+
- json-parsing
|
|
16
|
+
- graphql-mutations
|
|
17
|
+
- order-query-before-update
|
|
18
|
+
- attribute-updates
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Template: Ingestion - Payload JSON to Order Update GraphQL
|
|
22
|
+
|
|
23
|
+
**SDK Version:** @fluentcommerce/fc-connect-sdk@latest
|
|
24
|
+
**Last Updated:** 2025-01-24
|
|
25
|
+
**Deployment Target:** Versori Platform
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📋 Implementation Prompt
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
I need a Versori webhook ingestion that:
|
|
33
|
+
|
|
34
|
+
1) Receives JSON payload directly in webhook request body with orderRef and attributes
|
|
35
|
+
2) Parses JSON to extract order reference and attributes
|
|
36
|
+
3) Queries Fluent Commerce to get order ID by order reference
|
|
37
|
+
4) Updates order attributes using updateOrder GraphQL mutation
|
|
38
|
+
5) Returns success/error response
|
|
39
|
+
6) Uses native Versori log from context
|
|
40
|
+
|
|
41
|
+
Use the loaded docs to fill in SDK specifics and best practices.
|
|
42
|
+
Keep the structure identical to the template; only adapt where needed.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 📋 Template Overview
|
|
48
|
+
|
|
49
|
+
This connector runs on the Versori platform. It receives JSON payloads directly via webhook with order reference and attributes, queries Fluent Commerce to get the order ID, and updates order attributes using GraphQL mutations. Most operational settings (Fluent account/connection) are configured via activation variables.
|
|
50
|
+
|
|
51
|
+
### What This Template Does
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ INGESTION WORKFLOW │
|
|
56
|
+
└────────────────────────────────────────────────────────────────┘
|
|
57
|
+
|
|
58
|
+
1. TRIGGER
|
|
59
|
+
└─ Webhook: HTTP POST endpoint receives JSON payload
|
|
60
|
+
|
|
61
|
+
2. RECEIVE PAYLOAD
|
|
62
|
+
├─ Extract JSON from request body (ctx.data or ctx.request)
|
|
63
|
+
├─ Validate payload structure
|
|
64
|
+
└─ Log incoming request
|
|
65
|
+
|
|
66
|
+
3. PARSE JSON
|
|
67
|
+
├─ Parse JSON payload (if raw string)
|
|
68
|
+
├─ Extract order reference
|
|
69
|
+
├─ Extract attributes array
|
|
70
|
+
└─ Validate required fields
|
|
71
|
+
|
|
72
|
+
4. QUERY ORDER
|
|
73
|
+
├─ Query Fluent Commerce: order(ref: orderRef)
|
|
74
|
+
├─ Get order ID and current state
|
|
75
|
+
├─ Validate order exists
|
|
76
|
+
└─ Log order details
|
|
77
|
+
|
|
78
|
+
5. BUILD UPDATE MUTATION
|
|
79
|
+
├─ Construct UpdateOrderInput with ref
|
|
80
|
+
├─ Map attributes to AttributeInput format
|
|
81
|
+
├─ Preserve existing attributes (optional merge)
|
|
82
|
+
└─ Build GraphQL mutation variables
|
|
83
|
+
|
|
84
|
+
6. EXECUTE MUTATION
|
|
85
|
+
├─ Execute updateOrder GraphQL mutation
|
|
86
|
+
├─ Handle success/error responses
|
|
87
|
+
└─ Log mutation result
|
|
88
|
+
|
|
89
|
+
7. RETURN RESPONSE
|
|
90
|
+
├─ Return success with order details
|
|
91
|
+
└─ Return error with details if failed
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Key Features
|
|
95
|
+
|
|
96
|
+
- **Direct Payload Processing**: Receives JSON payload directly in webhook request body
|
|
97
|
+
- **Order Query First**: Queries order by ref to get ID and validate existence
|
|
98
|
+
- **GraphQL Mutation**: Uses updateOrder mutation to update attributes
|
|
99
|
+
- **Attribute Mapping**: Converts payload attributes to Fluent AttributeInput format
|
|
100
|
+
- **Error Handling**: Comprehensive error handling with detailed logging
|
|
101
|
+
- **Native Versori Logging**: Uses Versori log from context
|
|
102
|
+
|
|
103
|
+
### 📦 Package Information
|
|
104
|
+
|
|
105
|
+
**SDK:** [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk) `latest`
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm install @fluentcommerce/fc-connect-sdk@latest
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
**Templates are designed for direct deployment; customize via activation variables.**
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 📦 SDK Imports (Verified - Versori Optimized)
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// ✅ VERIFIED IMPORTS - These match actual SDK exports
|
|
121
|
+
import { Buffer } from 'node:buffer'; // Required for Versori/Deno runtime
|
|
122
|
+
import {
|
|
123
|
+
createClient, // Universal client factory
|
|
124
|
+
JSONParserService, // JSON parsing utility
|
|
125
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
126
|
+
|
|
127
|
+
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
128
|
+
|
|
129
|
+
// Versori platform imports
|
|
130
|
+
import { webhook, http } from '@versori/run';
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Note:** All imports are from actual SDK exports - this code compiles and runs as-is.
|
|
134
|
+
|
|
135
|
+
**⚠️ CRITICAL - Buffer Import Required:**
|
|
136
|
+
|
|
137
|
+
The `Buffer` import from `node:buffer` is **required** for Versori/Deno runtime compatibility:
|
|
138
|
+
- Versori and Deno do NOT have `Buffer` as a global (unlike Node.js)
|
|
139
|
+
- Without this import, code will crash with `ReferenceError: Buffer is not defined`
|
|
140
|
+
- Always include this import in Versori templates, even if not directly used in the template code
|
|
141
|
+
- Required for SDK internal operations and any Buffer usage in your code
|
|
142
|
+
|
|
143
|
+
**✅ VERSORI PLATFORM - Use Native Logs:**
|
|
144
|
+
|
|
145
|
+
- Use `log` from context: `const { log } = ctx;`
|
|
146
|
+
- Native Versori logs are simpler and automatically integrated with platform monitoring
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🔐 Fluent Commerce Connection Setup
|
|
151
|
+
|
|
152
|
+
**✅ BEST PRACTICE:** Store Fluent Commerce credentials in a Versori connection object:
|
|
153
|
+
|
|
154
|
+
**Connection Configuration:**
|
|
155
|
+
|
|
156
|
+
1. **Create Connection in Versori:**
|
|
157
|
+
- Name: `fluent_commerce`
|
|
158
|
+
- Type: HTTP Basic Auth or OAuth2
|
|
159
|
+
- Credentials: Fluent Commerce API credentials
|
|
160
|
+
|
|
161
|
+
2. **Connection Variables:**
|
|
162
|
+
- `FLUENT_BASE_URL` - Fluent Commerce API base URL (e.g., `https://api.fluentcommerce.com`)
|
|
163
|
+
- `FLUENT_CLIENT_ID` - OAuth client ID (if using OAuth2)
|
|
164
|
+
- `FLUENT_CLIENT_SECRET` - OAuth client secret (if using OAuth2)
|
|
165
|
+
- `FLUENT_USERNAME` - Username (if using Basic Auth)
|
|
166
|
+
- `FLUENT_PASSWORD` - Password (if using Basic Auth)
|
|
167
|
+
|
|
168
|
+
**Benefits:**
|
|
169
|
+
- ✅ Credentials stored securely in Versori vault
|
|
170
|
+
- ✅ Connection can be reused across workflows
|
|
171
|
+
- ✅ No sensitive data in activation variables
|
|
172
|
+
- ✅ Easier credential rotation
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 📄 Expected JSON Payload Format
|
|
177
|
+
|
|
178
|
+
The webhook accepts **flat key-value JSON payloads**. All fields except `orderRef` are automatically treated as order attributes.
|
|
179
|
+
|
|
180
|
+
### ✅ PRIMARY FORMAT: Flat Key-Value Pairs (Recommended)
|
|
181
|
+
|
|
182
|
+
**Simple format - just key-value pairs, no nested structure:**
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"orderRef": "ORD-12345",
|
|
187
|
+
"externalOrderId": "EXT-123456789",
|
|
188
|
+
"customerNotes": "Please leave package at front door",
|
|
189
|
+
"priority": "high",
|
|
190
|
+
"tags": "rush,gift"
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Type Auto-Detection:**
|
|
195
|
+
- Numbers → `INTEGER` or `FLOAT` (auto-detected)
|
|
196
|
+
- Booleans → `BOOLEAN` (auto-detected)
|
|
197
|
+
- Objects → `JSON` (stringified)
|
|
198
|
+
- Strings → `STRING` (default)
|
|
199
|
+
|
|
200
|
+
**cURL Example:**
|
|
201
|
+
```bash
|
|
202
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
203
|
+
-H "Content-Type: application/json" \
|
|
204
|
+
-H "X-API-Key: your-api-key" \
|
|
205
|
+
-d '{
|
|
206
|
+
"orderRef": "ORD-12345",
|
|
207
|
+
"externalOrderId": "EXT-123456789",
|
|
208
|
+
"customerNotes": "Please leave package at front door",
|
|
209
|
+
"priority": "high"
|
|
210
|
+
}'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Complete Sample Payloads
|
|
214
|
+
|
|
215
|
+
**Example 1: External System Reference Update**
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"orderRef": "ORD-0017326966182",
|
|
219
|
+
"externalOrderId": "EXT-123456789",
|
|
220
|
+
"sourceSystem": "ERP-SAP",
|
|
221
|
+
"integrationId": "INT-987654321"
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Example 2: Customer Notes and Business Flags**
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"orderRef": "ORD-0017326966182",
|
|
229
|
+
"customerNotes": "Please leave package at front door",
|
|
230
|
+
"priority": "high",
|
|
231
|
+
"tags": "rush,gift",
|
|
232
|
+
"specialHandling": true
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Note:** Metadata fields (`updateReason`, `updatedBy`, `timestamp`) are optional and excluded from attributes. All other fields are treated as order attributes.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## GraphQL Mutations
|
|
241
|
+
|
|
242
|
+
### Query Order by Reference
|
|
243
|
+
|
|
244
|
+
```graphql
|
|
245
|
+
query GetOrder($ref: String!) {
|
|
246
|
+
order(ref: $ref) {
|
|
247
|
+
id
|
|
248
|
+
ref
|
|
249
|
+
status
|
|
250
|
+
attributes {
|
|
251
|
+
name
|
|
252
|
+
type
|
|
253
|
+
value
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Update Order Mutation
|
|
260
|
+
|
|
261
|
+
```graphql
|
|
262
|
+
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
263
|
+
updateOrder(input: $input) {
|
|
264
|
+
id
|
|
265
|
+
ref
|
|
266
|
+
status
|
|
267
|
+
attributes {
|
|
268
|
+
name
|
|
269
|
+
type
|
|
270
|
+
value
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**UpdateOrderInput Structure:**
|
|
277
|
+
- `id` (ID!) - Order ID (required) - Must use Fluent internal ID, not reference
|
|
278
|
+
- `attributes` (Array<AttributeInput>) - Attributes to update
|
|
279
|
+
- `status` (String) - Optional status update
|
|
280
|
+
- Other optional fields as per schema
|
|
281
|
+
|
|
282
|
+
**Note:** UpdateOrderInput requires `id: ID!` (the Fluent internal ID), not `ref`. This is why the template queries the order first to get the ID.
|
|
283
|
+
|
|
284
|
+
**AttributeInput Structure:**
|
|
285
|
+
- `name` (String!) - Attribute name
|
|
286
|
+
- `type` (String!) - Attribute type (STRING, INTEGER, FLOAT, BOOLEAN, JSON, DATE)
|
|
287
|
+
- `value` (String!) - Attribute value (stringified for non-STRING types)
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 🔧 Complete Production Code
|
|
292
|
+
|
|
293
|
+
### Versori Workflows Structure
|
|
294
|
+
|
|
295
|
+
**Key Concept**: Versori workflows are organized by **trigger type** at the first level, then by **specific workflow** with descriptive file names.
|
|
296
|
+
|
|
297
|
+
**Trigger Types:**
|
|
298
|
+
- **`webhook()`** → HTTP-based triggers (event-driven) - Creates HTTP endpoints
|
|
299
|
+
|
|
300
|
+
**Execution Steps (chained to triggers):**
|
|
301
|
+
- **`http()`** → External API calls (chained from webhook)
|
|
302
|
+
|
|
303
|
+
### Recommended Project Structure
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
order-update-webhook/
|
|
307
|
+
├── index.ts # Entry point - exports all workflows
|
|
308
|
+
└── src/
|
|
309
|
+
├── workflows/
|
|
310
|
+
│ └── webhook/
|
|
311
|
+
│ └── order-update.ts # Webhook: Order update handler
|
|
312
|
+
│
|
|
313
|
+
├── services/
|
|
314
|
+
│ └── order-update.service.ts # Shared orchestration logic
|
|
315
|
+
│
|
|
316
|
+
├── utils/
|
|
317
|
+
│ └── response-status.utils.ts # FC status code mapping utilities
|
|
318
|
+
│
|
|
319
|
+
└── types/
|
|
320
|
+
└── order-update.types.ts # Type definitions
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Benefits:**
|
|
324
|
+
- ✅ Clear structure (webhook handlers in `webhook/`)
|
|
325
|
+
- ✅ Descriptive file names (easy to browse and understand)
|
|
326
|
+
- ✅ Reusable code in `services/` (DRY principle)
|
|
327
|
+
- ✅ Centralized type definitions
|
|
328
|
+
- ✅ Utility functions for consistent error handling (FC status codes)
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Workflow Files
|
|
333
|
+
|
|
334
|
+
### 1. Webhook Workflow (`src/workflows/webhook/order-update.ts`)
|
|
335
|
+
|
|
336
|
+
**Purpose**: Handle order update requests via webhook
|
|
337
|
+
**Trigger**: HTTP POST
|
|
338
|
+
**Endpoint**: `POST https://{workspace}.versori.run/order-update`
|
|
339
|
+
**Use Cases**: Order attribute updates from external systems, status changes
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { webhook, http } from '@versori/run';
|
|
343
|
+
import { executeOrderUpdate } from '../../services/order-update.service';
|
|
344
|
+
import { mapErrorToFCStatus } from '../../utils/response-status.utils';
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Webhook: Order Update Handler
|
|
348
|
+
*
|
|
349
|
+
* Endpoint: POST https://{workspace}.versori.run/order-update
|
|
350
|
+
* Request body: { "orderRef": "ORD-12345", "externalOrderId": "EXT-123", "customerNotes": "...", ... }
|
|
351
|
+
*
|
|
352
|
+
* Pattern: webhook().then(http()) - needs Fluent API access
|
|
353
|
+
* Uses shared service: order-update.service.ts
|
|
354
|
+
*
|
|
355
|
+
* SECURITY: Authentication handled via connection parameter
|
|
356
|
+
* No manual API key validation needed - Versori manages this via connection auth
|
|
357
|
+
*/
|
|
358
|
+
export const orderUpdateWebhook = webhook('order-update', {
|
|
359
|
+
response: { mode: 'sync' }, // ✅ Sync mode: response sent when handler returns
|
|
360
|
+
connection: 'order-update-webhook', // Versori validates API key
|
|
361
|
+
}).then(
|
|
362
|
+
http('process-order-update', { connection: 'fluent_commerce' }, async ctx => {
|
|
363
|
+
const { log, data } = ctx;
|
|
364
|
+
|
|
365
|
+
log.info('🚀 [WEBHOOK] Order update request received', {
|
|
366
|
+
timestamp: new Date().toISOString(),
|
|
367
|
+
hasPayload: !!data,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
// Reuse shared orchestration logic
|
|
372
|
+
const result = await executeOrderUpdate(ctx);
|
|
373
|
+
|
|
374
|
+
log.info('✅ [WEBHOOK] Order update completed successfully', {
|
|
375
|
+
orderRef: result.orderRef,
|
|
376
|
+
orderId: result.orderId,
|
|
377
|
+
attributesUpdated: result.attributesUpdated,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Return response with FC200 status code
|
|
381
|
+
return {
|
|
382
|
+
success: true,
|
|
383
|
+
statusCode: result.statusCode,
|
|
384
|
+
orderRef: result.orderRef,
|
|
385
|
+
orderId: result.orderId,
|
|
386
|
+
attributesUpdated: result.attributesUpdated,
|
|
387
|
+
message: result.message || 'Order attributes updated successfully',
|
|
388
|
+
};
|
|
389
|
+
} catch (e: any) {
|
|
390
|
+
log.error('❌ [WEBHOOK] Order update failed', {
|
|
391
|
+
message: e?.message,
|
|
392
|
+
stack: e?.stack,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Map error to FC status code
|
|
396
|
+
const fcStatus = mapErrorToFCStatus(e);
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
statusCode: fcStatus.statusCode,
|
|
401
|
+
retryable: fcStatus.retryable,
|
|
402
|
+
error: fcStatus.message,
|
|
403
|
+
details: e?.message || 'Unknown error occurred',
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### 2. Entry Point (`index.ts`)
|
|
413
|
+
|
|
414
|
+
**Purpose**: Register all workflows with Versori platform
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
/**
|
|
418
|
+
* Entry Point - Registers all workflows with Versori platform
|
|
419
|
+
*
|
|
420
|
+
* Versori automatically discovers and registers exported workflows
|
|
421
|
+
*
|
|
422
|
+
* File Structure:
|
|
423
|
+
* - src/workflows/webhook/ → HTTP-based triggers (webhooks)
|
|
424
|
+
* - src/services/ → Shared service logic (reusable across workflows)
|
|
425
|
+
*/
|
|
426
|
+
|
|
427
|
+
// Import webhook workflows
|
|
428
|
+
import { orderUpdateWebhook } from './src/workflows/webhook/order-update';
|
|
429
|
+
|
|
430
|
+
// Register all workflows with Versori platform
|
|
431
|
+
export {
|
|
432
|
+
// Webhooks (HTTP-based triggers)
|
|
433
|
+
orderUpdateWebhook,
|
|
434
|
+
};
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**What Gets Exposed:**
|
|
438
|
+
- ✅ `orderUpdateWebhook` → `https://{workspace}.versori.run/order-update`
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## 3. Type Definitions (`src/types/order-update.types.ts`)
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
/**
|
|
446
|
+
* Type Definitions for Order Update Ingestion
|
|
447
|
+
*
|
|
448
|
+
* Centralized type definitions for order update workflow
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Order update request payload
|
|
453
|
+
*
|
|
454
|
+
* PRIMARY FORMAT: Flat key-value pairs
|
|
455
|
+
* - orderRef: Required order reference
|
|
456
|
+
* - All other fields (except metadata fields) are treated as attributes
|
|
457
|
+
*
|
|
458
|
+
* Example:
|
|
459
|
+
* {
|
|
460
|
+
* "orderRef": "ORD-12345",
|
|
461
|
+
* "externalOrderId": "EXT-123456789",
|
|
462
|
+
* "customerNotes": "Please leave package at front door",
|
|
463
|
+
* "priority": "high"
|
|
464
|
+
* }
|
|
465
|
+
*/
|
|
466
|
+
export interface OrderUpdatePayload {
|
|
467
|
+
orderRef: string;
|
|
468
|
+
// Flat attributes: all fields except orderRef are treated as attributes
|
|
469
|
+
[key: string]: string | number | boolean | unknown;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Fluent AttributeInput format
|
|
474
|
+
*/
|
|
475
|
+
export interface AttributeInput {
|
|
476
|
+
name: string;
|
|
477
|
+
type: 'STRING' | 'INTEGER' | 'FLOAT' | 'BOOLEAN' | 'JSON' | 'DATE';
|
|
478
|
+
value: string;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Order query result
|
|
483
|
+
*/
|
|
484
|
+
export interface OrderQueryResult {
|
|
485
|
+
id: string;
|
|
486
|
+
ref: string;
|
|
487
|
+
status?: string;
|
|
488
|
+
attributes?: Array<{
|
|
489
|
+
name: string;
|
|
490
|
+
type: string;
|
|
491
|
+
value: string;
|
|
492
|
+
}>;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Order update result with FC status code
|
|
497
|
+
*/
|
|
498
|
+
export interface OrderUpdateResult {
|
|
499
|
+
success: boolean;
|
|
500
|
+
statusCode: 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
501
|
+
orderRef: string;
|
|
502
|
+
orderId: string;
|
|
503
|
+
attributesUpdated: number;
|
|
504
|
+
message?: string;
|
|
505
|
+
error?: string;
|
|
506
|
+
retryable?: boolean;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Versori Context Interface
|
|
511
|
+
* Represents the Versori runtime context passed to workflow functions
|
|
512
|
+
*/
|
|
513
|
+
export interface VersoriContext {
|
|
514
|
+
log: {
|
|
515
|
+
info: (message: string, data?: Record<string, unknown>) => void;
|
|
516
|
+
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
517
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
518
|
+
debug?: (message: string, data?: Record<string, unknown>) => void;
|
|
519
|
+
};
|
|
520
|
+
data?: unknown;
|
|
521
|
+
request?: Request;
|
|
522
|
+
activation: {
|
|
523
|
+
getVariable: (name: string) => string | undefined;
|
|
524
|
+
connections?: Record<string, unknown>;
|
|
525
|
+
};
|
|
526
|
+
connections?: Record<string, unknown>;
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## 4. Utility: Response Status Code Mapper (`src/utils/response-status.utils.ts`)
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
/**
|
|
536
|
+
* Response Status Code Utility
|
|
537
|
+
*
|
|
538
|
+
* Maps errors to Fluent Commerce (FC) status codes for consistent API responses
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* FC Status Codes:
|
|
543
|
+
* - FC200: OK (Successfully - No further action needed)
|
|
544
|
+
* - FC400: Bad Request (Non-retryable)
|
|
545
|
+
* - FC409: Duplicate Request, Entity already exists (Non-retryable)
|
|
546
|
+
* - FC504: Timeout (Retryable)
|
|
547
|
+
*/
|
|
548
|
+
|
|
549
|
+
export type FCStatusCode = 'FC200' | 'FC400' | 'FC409' | 'FC504';
|
|
550
|
+
|
|
551
|
+
export interface FCStatusMapping {
|
|
552
|
+
statusCode: FCStatusCode;
|
|
553
|
+
retryable: boolean;
|
|
554
|
+
message: string;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Map error to FC status code based on error message and type
|
|
559
|
+
*/
|
|
560
|
+
export function mapErrorToFCStatus(error: unknown): FCStatusMapping {
|
|
561
|
+
const errorMessage =
|
|
562
|
+
error instanceof Error ? error.message : String(error);
|
|
563
|
+
const lowerMessage = errorMessage.toLowerCase();
|
|
564
|
+
|
|
565
|
+
// FC409: Duplicate/Already exists (Non-retryable)
|
|
566
|
+
if (
|
|
567
|
+
lowerMessage.includes('already exists') ||
|
|
568
|
+
lowerMessage.includes('duplicate') ||
|
|
569
|
+
lowerMessage.includes('unique constraint') ||
|
|
570
|
+
lowerMessage.includes('entity already exists')
|
|
571
|
+
) {
|
|
572
|
+
return {
|
|
573
|
+
statusCode: 'FC409',
|
|
574
|
+
retryable: false,
|
|
575
|
+
message: 'Entity already exists. Duplicate request detected.',
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// FC504: Timeout (Retryable)
|
|
580
|
+
if (
|
|
581
|
+
lowerMessage.includes('timeout') ||
|
|
582
|
+
lowerMessage.includes('timed out') ||
|
|
583
|
+
lowerMessage.includes('request timeout') ||
|
|
584
|
+
(error instanceof Error && error.name === 'TimeoutError')
|
|
585
|
+
) {
|
|
586
|
+
return {
|
|
587
|
+
statusCode: 'FC504',
|
|
588
|
+
retryable: true,
|
|
589
|
+
message: 'Request timeout. Please retry.',
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// FC400: Bad Request (Non-retryable)
|
|
594
|
+
// Validation errors, missing fields, invalid format
|
|
595
|
+
if (
|
|
596
|
+
lowerMessage.includes('required') ||
|
|
597
|
+
lowerMessage.includes('missing') ||
|
|
598
|
+
lowerMessage.includes('invalid') ||
|
|
599
|
+
lowerMessage.includes('bad request') ||
|
|
600
|
+
lowerMessage.includes('validation') ||
|
|
601
|
+
lowerMessage.includes('not found') ||
|
|
602
|
+
lowerMessage.includes('does not exist')
|
|
603
|
+
) {
|
|
604
|
+
return {
|
|
605
|
+
statusCode: 'FC400',
|
|
606
|
+
retryable: false,
|
|
607
|
+
message: 'Bad request. Please verify payload and try again.',
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Default: FC400 for unknown errors (Non-retryable)
|
|
612
|
+
return {
|
|
613
|
+
statusCode: 'FC400',
|
|
614
|
+
retryable: false,
|
|
615
|
+
message: errorMessage,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Create success response with FC200 status
|
|
621
|
+
*/
|
|
622
|
+
export function createSuccessResponse<T extends Record<string, unknown>>(
|
|
623
|
+
data: T
|
|
624
|
+
): T & { statusCode: 'FC200'; retryable: false } {
|
|
625
|
+
return {
|
|
626
|
+
...data,
|
|
627
|
+
statusCode: 'FC200' as const,
|
|
628
|
+
retryable: false,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## 5. Service: Order Update Processor (`src/services/order-update.service.ts`)
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
/**
|
|
639
|
+
* Order Update Service
|
|
640
|
+
*
|
|
641
|
+
* Processes order update requests: extracts order reference and attributes from JSON payload,
|
|
642
|
+
* queries Fluent Commerce to get order ID, and updates order attributes via GraphQL mutation.
|
|
643
|
+
*/
|
|
644
|
+
|
|
645
|
+
import {
|
|
646
|
+
createClient,
|
|
647
|
+
JSONParserService,
|
|
648
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
649
|
+
import { Buffer } from 'node:buffer';
|
|
650
|
+
import { mapErrorToFCStatus, createSuccessResponse } from '../utils/response-status.utils';
|
|
651
|
+
import type { FluentClient } from '@fluentcommerce/fc-connect-sdk';
|
|
652
|
+
import type {
|
|
653
|
+
OrderUpdatePayload,
|
|
654
|
+
OrderUpdateResult,
|
|
655
|
+
VersoriContext,
|
|
656
|
+
AttributeInput,
|
|
657
|
+
OrderQueryResult,
|
|
658
|
+
} from '../types/order-update.types';
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* GraphQL query to get order by reference
|
|
662
|
+
*/
|
|
663
|
+
const GET_ORDER_QUERY = `
|
|
664
|
+
query GetOrder($ref: String!) {
|
|
665
|
+
order(ref: $ref) {
|
|
666
|
+
id
|
|
667
|
+
ref
|
|
668
|
+
status
|
|
669
|
+
attributes {
|
|
670
|
+
name
|
|
671
|
+
type
|
|
672
|
+
value
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
`;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* GraphQL mutation to update order
|
|
680
|
+
*/
|
|
681
|
+
const UPDATE_ORDER_MUTATION = `
|
|
682
|
+
mutation UpdateOrder($input: UpdateOrderInput!) {
|
|
683
|
+
updateOrder(input: $input) {
|
|
684
|
+
id
|
|
685
|
+
ref
|
|
686
|
+
status
|
|
687
|
+
attributes {
|
|
688
|
+
name
|
|
689
|
+
type
|
|
690
|
+
value
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
`;
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Extract attributes from flat payload format
|
|
698
|
+
*
|
|
699
|
+
* PRIMARY FORMAT: All fields except orderRef (and metadata fields) are attributes
|
|
700
|
+
* - orderRef: Required, excluded from attributes
|
|
701
|
+
* - Metadata fields: updateReason, updatedBy, timestamp (optional, excluded)
|
|
702
|
+
* - All other fields: Treated as attributes with auto-detected types
|
|
703
|
+
*
|
|
704
|
+
* Example payload:
|
|
705
|
+
* {
|
|
706
|
+
* "orderRef": "ORD-12345",
|
|
707
|
+
* "externalOrderId": "EXT-123456789", // → attribute
|
|
708
|
+
* "customerNotes": "Front door", // → attribute
|
|
709
|
+
* "priority": "high" // → attribute
|
|
710
|
+
* }
|
|
711
|
+
*/
|
|
712
|
+
function extractAttributesFromPayload(
|
|
713
|
+
payload: OrderUpdatePayload
|
|
714
|
+
): AttributeInput[] {
|
|
715
|
+
const metadataFields = ['orderRef', 'updateReason', 'updatedBy', 'timestamp'];
|
|
716
|
+
const attributes: AttributeInput[] = [];
|
|
717
|
+
|
|
718
|
+
// Extract all fields except orderRef and metadata fields
|
|
719
|
+
for (const [name, value] of Object.entries(payload)) {
|
|
720
|
+
// Skip metadata fields
|
|
721
|
+
if (metadataFields.includes(name)) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Skip null/undefined values
|
|
726
|
+
if (value === null || value === undefined) {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Auto-detect type from value
|
|
731
|
+
let type: AttributeInput['type'] = 'STRING';
|
|
732
|
+
let stringValue: string;
|
|
733
|
+
|
|
734
|
+
if (typeof value === 'number') {
|
|
735
|
+
type = Number.isInteger(value) ? 'INTEGER' : 'FLOAT';
|
|
736
|
+
stringValue = String(value);
|
|
737
|
+
} else if (typeof value === 'boolean') {
|
|
738
|
+
type = 'BOOLEAN';
|
|
739
|
+
stringValue = String(value);
|
|
740
|
+
} else if (typeof value === 'object') {
|
|
741
|
+
type = 'JSON';
|
|
742
|
+
stringValue = JSON.stringify(value);
|
|
743
|
+
} else {
|
|
744
|
+
// String or other types → STRING
|
|
745
|
+
stringValue = String(value);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
attributes.push({
|
|
749
|
+
name,
|
|
750
|
+
type,
|
|
751
|
+
value: stringValue,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return attributes;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Parse webhook payload from Versori context
|
|
760
|
+
*
|
|
761
|
+
* Handles both pre-parsed JSON (ctx.data) and raw request body (ctx.request)
|
|
762
|
+
*/
|
|
763
|
+
async function parseWebhookPayload(
|
|
764
|
+
ctx: VersoriContext
|
|
765
|
+
): Promise<OrderUpdatePayload | null> {
|
|
766
|
+
const { log, data, request } = ctx;
|
|
767
|
+
|
|
768
|
+
// Scenario 1: Pre-parsed JSON (Versori auto-parsed)
|
|
769
|
+
if (data && typeof data === 'object' && data !== null) {
|
|
770
|
+
log.info('📥 [PARSE] Using pre-parsed JSON payload', {
|
|
771
|
+
hasData: true,
|
|
772
|
+
dataKeys: Object.keys(data),
|
|
773
|
+
});
|
|
774
|
+
return data as OrderUpdatePayload;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Scenario 2: Raw request body (need to parse)
|
|
778
|
+
if (request && typeof request.text === 'function') {
|
|
779
|
+
log.info('📥 [PARSE] Extracting raw request body');
|
|
780
|
+
try {
|
|
781
|
+
const rawBody = await request.text();
|
|
782
|
+
if (!rawBody || rawBody.trim() === '') {
|
|
783
|
+
log.error('❌ [PARSE] Request body is empty');
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const parser = new JSONParserService(log);
|
|
788
|
+
const parsed = await parser.parse(rawBody);
|
|
789
|
+
|
|
790
|
+
log.info('📥 [PARSE] Successfully parsed JSON payload', {
|
|
791
|
+
parsedKeys: Object.keys(parsed),
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
return parsed as OrderUpdatePayload;
|
|
795
|
+
} catch (parseError: unknown) {
|
|
796
|
+
const errorMessage =
|
|
797
|
+
parseError instanceof Error ? parseError.message : String(parseError);
|
|
798
|
+
log.error('❌ [PARSE] Failed to parse JSON payload', {
|
|
799
|
+
error: errorMessage,
|
|
800
|
+
});
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
log.error('❌ [PARSE] No valid payload found in context');
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Query order by reference to get ID and current state
|
|
811
|
+
*/
|
|
812
|
+
async function queryOrderByRef(
|
|
813
|
+
client: FluentClient,
|
|
814
|
+
orderRef: string,
|
|
815
|
+
log: VersoriContext['log']
|
|
816
|
+
): Promise<OrderQueryResult> {
|
|
817
|
+
log.info('🔍 [QUERY] Querying order by reference', { orderRef });
|
|
818
|
+
|
|
819
|
+
const result = await client.graphql({
|
|
820
|
+
query: GET_ORDER_QUERY,
|
|
821
|
+
variables: { ref: orderRef },
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
if (result.errors && result.errors.length > 0) {
|
|
825
|
+
log.error('❌ [QUERY] GraphQL query returned errors', {
|
|
826
|
+
errors: result.errors,
|
|
827
|
+
orderRef,
|
|
828
|
+
});
|
|
829
|
+
throw new Error(`GraphQL query failed: ${result.errors[0].message}`);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const order = (result.data as any)?.order;
|
|
833
|
+
|
|
834
|
+
if (!order) {
|
|
835
|
+
log.error('❌ [QUERY] Order not found', { orderRef });
|
|
836
|
+
throw new Error(`Order not found: ${orderRef}`);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
log.info('✅ [QUERY] Order found', {
|
|
840
|
+
orderId: order.id,
|
|
841
|
+
orderRef: order.ref,
|
|
842
|
+
status: order.status,
|
|
843
|
+
currentAttributeCount: order.attributes?.length || 0,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
return order as OrderQueryResult;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Execute order update workflow
|
|
851
|
+
*
|
|
852
|
+
* Main orchestration function:
|
|
853
|
+
* 1. Parse webhook payload
|
|
854
|
+
* 2. Extract order reference and attributes
|
|
855
|
+
* 3. Create Fluent client
|
|
856
|
+
* 4. Query order by reference to get ID
|
|
857
|
+
* 5. Build update mutation variables
|
|
858
|
+
* 6. Execute updateOrder mutation
|
|
859
|
+
*
|
|
860
|
+
* Note: Authentication handled via Versori connection (fluent_commerce).
|
|
861
|
+
* Retailer ID is not required for GraphQL mutations - connection handles authentication.
|
|
862
|
+
*
|
|
863
|
+
* CRITICAL: UpdateOrderInput requires id: ID! (not ref).
|
|
864
|
+
* The query step retrieves order.id which must be used in the mutation.
|
|
865
|
+
*/
|
|
866
|
+
export async function executeOrderUpdate(
|
|
867
|
+
ctx: VersoriContext
|
|
868
|
+
): Promise<OrderUpdateResult> {
|
|
869
|
+
const { log, activation } = ctx;
|
|
870
|
+
|
|
871
|
+
log.info('🚀 [SERVICE] Starting order update processing');
|
|
872
|
+
|
|
873
|
+
// STEP 1: Parse webhook payload
|
|
874
|
+
const payload = await parseWebhookPayload(ctx);
|
|
875
|
+
if (!payload) {
|
|
876
|
+
log.error('❌ [SERVICE] Failed to parse webhook payload', {
|
|
877
|
+
hasData: !!ctx.data,
|
|
878
|
+
hasRequest: !!ctx.request,
|
|
879
|
+
});
|
|
880
|
+
throw new Error('Failed to parse webhook payload. Ensure Content-Type: application/json is set in request headers.');
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
log.info('📥 [SERVICE] Payload parsed successfully', {
|
|
884
|
+
payloadKeys: Object.keys(payload),
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// STEP 2: Extract order reference
|
|
888
|
+
if (!payload.orderRef || typeof payload.orderRef !== 'string') {
|
|
889
|
+
log.error('❌ [SERVICE] Order reference not found in payload', {
|
|
890
|
+
payloadKeys: Object.keys(payload),
|
|
891
|
+
expectedField: 'orderRef',
|
|
892
|
+
});
|
|
893
|
+
throw new Error('Order reference (orderRef) is required in payload');
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const orderRef = payload.orderRef;
|
|
897
|
+
log.info('✅ [SERVICE] Order reference extracted', { orderRef });
|
|
898
|
+
|
|
899
|
+
// STEP 3: Extract attributes from flat payload format
|
|
900
|
+
// ✅ PRIMARY FORMAT: All fields except orderRef are treated as attributes
|
|
901
|
+
const attributes = extractAttributesFromPayload(payload);
|
|
902
|
+
if (attributes.length === 0) {
|
|
903
|
+
log.warn('⚠️ [SERVICE] No attributes provided in payload', {
|
|
904
|
+
orderRef,
|
|
905
|
+
payloadKeys: Object.keys(payload),
|
|
906
|
+
});
|
|
907
|
+
throw new Error('At least one attribute is required for order update. Provide fields like: { "orderRef": "ORD-123", "externalOrderId": "EXT-123", "customerNotes": "Leave at door" }');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
log.info('✅ [SERVICE] Attributes extracted', {
|
|
911
|
+
orderRef,
|
|
912
|
+
attributeCount: attributes.length,
|
|
913
|
+
attributeNames: attributes.map(a => a.name),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
// STEP 4: Create Fluent client
|
|
917
|
+
// ✅ Pass ctx directly - createClient auto-detects Versori context
|
|
918
|
+
// Authentication handled via Versori connection (fluent_commerce)
|
|
919
|
+
const client = await createClient(ctx);
|
|
920
|
+
|
|
921
|
+
log.info('✅ [SERVICE] Fluent client created');
|
|
922
|
+
|
|
923
|
+
// STEP 5: Query order by reference to get ID
|
|
924
|
+
// ✅ Query first to:
|
|
925
|
+
// - Validate order exists (fail-fast if order not found)
|
|
926
|
+
// - Get order ID for logging and tracking
|
|
927
|
+
// - Get current order state (optional: can merge with existing attributes)
|
|
928
|
+
const order = await queryOrderByRef(client, orderRef, log);
|
|
929
|
+
|
|
930
|
+
// STEP 6: Build update mutation variables
|
|
931
|
+
// ✅ CRITICAL: UpdateOrderInput requires id: ID! (not ref)
|
|
932
|
+
// Use the ID from queryOrderByRef result - this is why we query first!
|
|
933
|
+
const updateVariables = {
|
|
934
|
+
input: {
|
|
935
|
+
id: order.id, // ✅ REQUIRED: UpdateOrderInput.id is ID! (required field)
|
|
936
|
+
attributes: attributes,
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
log.info('📤 [SERVICE] Executing updateOrder mutation', {
|
|
941
|
+
orderRef,
|
|
942
|
+
orderId: order.id,
|
|
943
|
+
attributeCount: attributes.length,
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// STEP 7: Execute updateOrder mutation
|
|
947
|
+
const mutationResult = await client.graphql({
|
|
948
|
+
query: UPDATE_ORDER_MUTATION,
|
|
949
|
+
variables: updateVariables,
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
if (mutationResult.errors && mutationResult.errors.length > 0) {
|
|
953
|
+
log.error('❌ [SERVICE] GraphQL mutation returned errors', {
|
|
954
|
+
errors: mutationResult.errors,
|
|
955
|
+
orderRef,
|
|
956
|
+
orderId: order.id,
|
|
957
|
+
});
|
|
958
|
+
throw new Error(`GraphQL mutation failed: ${mutationResult.errors[0].message}`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const updatedOrder = (mutationResult.data as any)?.updateOrder;
|
|
962
|
+
|
|
963
|
+
if (!updatedOrder) {
|
|
964
|
+
log.error('❌ [SERVICE] No order data returned from mutation', { orderRef });
|
|
965
|
+
throw new Error('No order data returned from updateOrder mutation');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
log.info('✅ [SERVICE] Order updated successfully', {
|
|
969
|
+
orderRef,
|
|
970
|
+
orderId: updatedOrder.id,
|
|
971
|
+
attributesUpdated: attributes.length,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// Return success response with FC200 status code
|
|
975
|
+
return createSuccessResponse({
|
|
976
|
+
success: true,
|
|
977
|
+
orderRef,
|
|
978
|
+
orderId: updatedOrder.id,
|
|
979
|
+
attributesUpdated: attributes.length,
|
|
980
|
+
message: 'Order attributes updated successfully',
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
---
|
|
986
|
+
|
|
987
|
+
## 5. Configuration
|
|
988
|
+
|
|
989
|
+
### Activation Variables
|
|
990
|
+
|
|
991
|
+
No activation variables required. Authentication is handled via Versori connection (`fluent_commerce`).
|
|
992
|
+
|
|
993
|
+
**Note:** Configure Fluent Commerce credentials in the Versori connection settings. The connection handles authentication automatically.
|
|
994
|
+
|
|
995
|
+
### Webhook Connection
|
|
996
|
+
|
|
997
|
+
Create a Versori connection for webhook authentication:
|
|
998
|
+
|
|
999
|
+
1. **Connection Name:** `order-update-webhook`
|
|
1000
|
+
2. **Type:** API Key or Basic Auth
|
|
1001
|
+
3. **Purpose:** Authenticate incoming webhook requests
|
|
1002
|
+
|
|
1003
|
+
**Example webhook call:**
|
|
1004
|
+
|
|
1005
|
+
```bash
|
|
1006
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1007
|
+
-H "Content-Type: application/json" \
|
|
1008
|
+
-H "X-API-Key: your-api-key" \
|
|
1009
|
+
-d '{
|
|
1010
|
+
"orderRef": "ORD-12345",
|
|
1011
|
+
"externalOrderId": "EXT-123456789",
|
|
1012
|
+
"customerNotes": "Please leave package at front door",
|
|
1013
|
+
"priority": "high"
|
|
1014
|
+
}'
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
---
|
|
1018
|
+
|
|
1019
|
+
## 6. Error Handling
|
|
1020
|
+
|
|
1021
|
+
The template includes comprehensive error handling:
|
|
1022
|
+
|
|
1023
|
+
### Payload Parsing Errors
|
|
1024
|
+
|
|
1025
|
+
- **Empty payload:** Returns error response
|
|
1026
|
+
- **Invalid JSON:** Returns error with parse details
|
|
1027
|
+
- **Missing order reference:** Returns error with expected field name
|
|
1028
|
+
- **Missing attributes:** Returns error if no attributes provided
|
|
1029
|
+
|
|
1030
|
+
### Order Query Errors
|
|
1031
|
+
|
|
1032
|
+
- **Order not found:** Returns error with order reference
|
|
1033
|
+
- **GraphQL query errors:** Returns error with GraphQL error details
|
|
1034
|
+
|
|
1035
|
+
### Mutation Errors
|
|
1036
|
+
|
|
1037
|
+
- **GraphQL mutation errors:** Returns error with mutation error details
|
|
1038
|
+
- **No response data:** Returns error if mutation doesn't return order data
|
|
1039
|
+
|
|
1040
|
+
### Response Format with FC Status Codes
|
|
1041
|
+
|
|
1042
|
+
**Success Response (FC200):**
|
|
1043
|
+
```json
|
|
1044
|
+
{
|
|
1045
|
+
"success": true,
|
|
1046
|
+
"statusCode": "FC200",
|
|
1047
|
+
"orderRef": "ORD-12345",
|
|
1048
|
+
"orderId": "12345",
|
|
1049
|
+
"attributesUpdated": 3,
|
|
1050
|
+
"message": "Order attributes updated successfully"
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**Error Responses:**
|
|
1055
|
+
|
|
1056
|
+
**FC400 - Bad Request (Non-retryable):**
|
|
1057
|
+
```json
|
|
1058
|
+
{
|
|
1059
|
+
"success": false,
|
|
1060
|
+
"statusCode": "FC400",
|
|
1061
|
+
"retryable": false,
|
|
1062
|
+
"error": "Bad request. Please verify payload and try again.",
|
|
1063
|
+
"details": "Order reference (orderRef) is required in payload"
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
**FC409 - Duplicate/Already Exists (Non-retryable):**
|
|
1068
|
+
```json
|
|
1069
|
+
{
|
|
1070
|
+
"success": false,
|
|
1071
|
+
"statusCode": "FC409",
|
|
1072
|
+
"retryable": false,
|
|
1073
|
+
"error": "Entity already exists. Duplicate request detected.",
|
|
1074
|
+
"details": "Order with reference ORD-12345 already exists"
|
|
1075
|
+
}
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
**FC504 - Timeout (Retryable):**
|
|
1079
|
+
```json
|
|
1080
|
+
{
|
|
1081
|
+
"success": false,
|
|
1082
|
+
"statusCode": "FC504",
|
|
1083
|
+
"retryable": true,
|
|
1084
|
+
"error": "Request timeout. Please retry.",
|
|
1085
|
+
"details": "GraphQL mutation timed out after 30 seconds"
|
|
1086
|
+
}
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
## 7. Testing
|
|
1092
|
+
|
|
1093
|
+
### Test with cURL - Flat Key-Value Format
|
|
1094
|
+
|
|
1095
|
+
```bash
|
|
1096
|
+
# Test order update webhook with flat key-value format
|
|
1097
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1098
|
+
-H "Content-Type: application/json" \
|
|
1099
|
+
-H "X-API-Key: your-api-key" \
|
|
1100
|
+
-d '{
|
|
1101
|
+
"orderRef": "ORD-12345",
|
|
1102
|
+
"externalOrderId": "EXT-123456789",
|
|
1103
|
+
"customerNotes": "Please leave package at front door",
|
|
1104
|
+
"priority": "high"
|
|
1105
|
+
}'
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### Test with JSON File
|
|
1109
|
+
|
|
1110
|
+
**Create `order-update.json`:**
|
|
1111
|
+
```json
|
|
1112
|
+
{
|
|
1113
|
+
"orderRef": "ORD-0017326966182",
|
|
1114
|
+
"externalOrderId": "EXT-123456789",
|
|
1115
|
+
"customerNotes": "Please leave package at front door",
|
|
1116
|
+
"priority": "high"
|
|
1117
|
+
}
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
**Send with cURL:**
|
|
1121
|
+
```bash
|
|
1122
|
+
curl -X POST https://{workspace}.versori.run/order-update \
|
|
1123
|
+
-H "Content-Type: application/json" \
|
|
1124
|
+
-H "X-API-Key: your-api-key" \
|
|
1125
|
+
-d @order-update.json
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Expected Success Response
|
|
1129
|
+
|
|
1130
|
+
```json
|
|
1131
|
+
{
|
|
1132
|
+
"success": true,
|
|
1133
|
+
"statusCode": "FC200",
|
|
1134
|
+
"orderRef": "ORD-12345",
|
|
1135
|
+
"orderId": "12345",
|
|
1136
|
+
"attributesUpdated": 1,
|
|
1137
|
+
"message": "Order attributes updated successfully"
|
|
1138
|
+
}
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
**FC Status Code:** `FC200` - OK (Successfully - No further action needed)
|
|
1142
|
+
|
|
1143
|
+
### Expected Error Responses
|
|
1144
|
+
|
|
1145
|
+
**FC400 - Bad Request:**
|
|
1146
|
+
```json
|
|
1147
|
+
{
|
|
1148
|
+
"success": false,
|
|
1149
|
+
"statusCode": "FC400",
|
|
1150
|
+
"retryable": false,
|
|
1151
|
+
"error": "Bad request. Please verify payload and try again.",
|
|
1152
|
+
"details": "Order not found: ORD-12345"
|
|
1153
|
+
}
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
**FC409 - Duplicate/Already Exists:**
|
|
1157
|
+
```json
|
|
1158
|
+
{
|
|
1159
|
+
"success": false,
|
|
1160
|
+
"statusCode": "FC409",
|
|
1161
|
+
"retryable": false,
|
|
1162
|
+
"error": "Entity already exists. Duplicate request detected.",
|
|
1163
|
+
"details": "Order with reference ORD-12345 already exists"
|
|
1164
|
+
}
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
**FC504 - Timeout:**
|
|
1168
|
+
```json
|
|
1169
|
+
{
|
|
1170
|
+
"success": false,
|
|
1171
|
+
"statusCode": "FC504",
|
|
1172
|
+
"retryable": true,
|
|
1173
|
+
"error": "Request timeout. Please retry.",
|
|
1174
|
+
"details": "GraphQL mutation timed out"
|
|
1175
|
+
}
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
---
|
|
1179
|
+
|
|
1180
|
+
## 8. Customization
|
|
1181
|
+
|
|
1182
|
+
### Merge with Existing Attributes
|
|
1183
|
+
|
|
1184
|
+
To merge new attributes with existing ones (instead of replacing):
|
|
1185
|
+
|
|
1186
|
+
```typescript
|
|
1187
|
+
// In executeOrderUpdate function, after queryOrderByRef:
|
|
1188
|
+
const existingAttributes = order.attributes || [];
|
|
1189
|
+
const mergedAttributes = [
|
|
1190
|
+
...existingAttributes,
|
|
1191
|
+
...attributes, // New attributes override existing ones with same name
|
|
1192
|
+
];
|
|
1193
|
+
|
|
1194
|
+
const updateVariables = {
|
|
1195
|
+
input: {
|
|
1196
|
+
id: order.id, // ✅ REQUIRED: Use ID from query result
|
|
1197
|
+
attributes: mergedAttributes,
|
|
1198
|
+
},
|
|
1199
|
+
};
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
### Custom Attribute Type Detection
|
|
1203
|
+
|
|
1204
|
+
To customize how attribute types are detected from object format:
|
|
1205
|
+
|
|
1206
|
+
```typescript
|
|
1207
|
+
function convertToAttributeInput(
|
|
1208
|
+
attributes: OrderUpdatePayload['attributes']
|
|
1209
|
+
): AttributeInput[] {
|
|
1210
|
+
// Add custom type detection logic
|
|
1211
|
+
// e.g., detect dates, URLs, etc.
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
### Additional Order Fields
|
|
1216
|
+
|
|
1217
|
+
To update additional order fields (status, etc.):
|
|
1218
|
+
|
|
1219
|
+
```typescript
|
|
1220
|
+
const updateVariables = {
|
|
1221
|
+
input: {
|
|
1222
|
+
id: order.id, // ✅ REQUIRED: Use ID from query result
|
|
1223
|
+
status: payload.status, // Update status if provided
|
|
1224
|
+
attributes: attributes,
|
|
1225
|
+
},
|
|
1226
|
+
};
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
---
|
|
1230
|
+
|
|
1231
|
+
## Summary
|
|
1232
|
+
|
|
1233
|
+
✅ **DO:**
|
|
1234
|
+
- Use Versori connection for Fluent Commerce credentials
|
|
1235
|
+
- Query order first to validate existence and get ID
|
|
1236
|
+
- Use `id` from query result in UpdateOrderInput (required field)
|
|
1237
|
+
- Handle both array and object attribute formats
|
|
1238
|
+
- Log all steps for debugging
|
|
1239
|
+
- Return clear error messages
|
|
1240
|
+
|
|
1241
|
+
❌ **DON'T:**
|
|
1242
|
+
- Hardcode credentials in code
|
|
1243
|
+
- Skip order query validation
|
|
1244
|
+
- Use `ref` in UpdateOrderInput (must use `id: ID!`)
|
|
1245
|
+
- Assume payload format without validation
|
|
1246
|
+
- Skip error handling
|
|
1247
|
+
|
|
1248
|
+
---
|
|
1249
|
+
|
|
1250
|
+
## Next Steps
|
|
1251
|
+
|
|
1252
|
+
1. **Deploy:** Copy code to your Versori project
|
|
1253
|
+
2. **Configure:** Set up Fluent Commerce connection and activation variables
|
|
1254
|
+
3. **Test:** Test webhook with sample payloads
|
|
1255
|
+
4. **Monitor:** Check Versori logs for order updates
|
|
1256
|
+
|
|
1257
|
+
For more templates, see:
|
|
1258
|
+
- [XML Payload Template](./template-ingestion-payload-xml-order-update-graphql.md)
|
|
1259
|
+
- [Other GraphQL Mutation Templates](./graphql-mutations-guide.md)
|
|
1260
|
+
|