@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -2
- package/README.md +39 -0
- package/dist/cjs/auth/index.d.ts +3 -0
- package/dist/cjs/auth/index.js +13 -0
- package/dist/cjs/auth/profile-loader.d.ts +18 -0
- package/dist/cjs/auth/profile-loader.js +208 -0
- package/dist/cjs/client-factory.d.ts +4 -0
- package/dist/cjs/client-factory.js +10 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/auth/index.d.ts +3 -0
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/profile-loader.d.ts +18 -0
- package/dist/esm/auth/profile-loader.js +169 -0
- package/dist/esm/client-factory.d.ts +4 -0
- package/dist/esm/client-factory.js +9 -0
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/auth/index.d.ts +3 -0
- package/dist/types/auth/profile-loader.d.ts +18 -0
- package/dist/types/client-factory.d.ts +4 -0
- package/dist/types/index.d.ts +3 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -482
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
|
@@ -1,1111 +1,1111 @@
|
|
|
1
|
-
# Module 8: Best Practices & Production Patterns
|
|
2
|
-
|
|
3
|
-
[← Back to Versori Platform Guide](../platforms-versori-readme.md)
|
|
4
|
-
|
|
5
|
-
**Module 8 of 8** | **Level**: Advanced | **Time**: 30 minutes
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Learning Objectives
|
|
10
|
-
|
|
11
|
-
By the end of this module, you will:
|
|
12
|
-
|
|
13
|
-
- ✅ Implement production-ready error handling patterns
|
|
14
|
-
- ✅ Optimize workflow performance and reduce costs
|
|
15
|
-
- ✅ Apply security hardening best practices
|
|
16
|
-
- ✅ Implement robust retry and fallback strategies
|
|
17
|
-
- ✅ Troubleshoot common production issues
|
|
18
|
-
- ✅ Monitor and alert on critical metrics
|
|
19
|
-
- ✅ Apply cost optimization techniques
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Production-Ready Error Handling
|
|
24
|
-
|
|
25
|
-
### Pattern 1: Comprehensive Try-Catch with Context
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { http } from '@versori/run';
|
|
29
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
30
|
-
|
|
31
|
-
export const robustWorkflow = http(
|
|
32
|
-
'robust-workflow',
|
|
33
|
-
{
|
|
34
|
-
connection: 'fluent_commerce',
|
|
35
|
-
retry: {
|
|
36
|
-
attempts: 3,
|
|
37
|
-
delay: 1000,
|
|
38
|
-
backoff: 2, // Exponential backoff
|
|
39
|
-
},
|
|
40
|
-
timeout: 30000,
|
|
41
|
-
},
|
|
42
|
-
async ctx => {
|
|
43
|
-
const operationId = `${ctx.executionId}-${Date.now()}`;
|
|
44
|
-
|
|
45
|
-
ctx.log('info', 'Workflow started', {
|
|
46
|
-
operationId,
|
|
47
|
-
executionId: ctx.executionId,
|
|
48
|
-
timestamp: new Date().toISOString(),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const client = await createClient(ctx);
|
|
53
|
-
|
|
54
|
-
// Validate input
|
|
55
|
-
if (!ctx.data?.orderId) {
|
|
56
|
-
throw new Error('Missing required field: orderId');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// GraphQL query - schema validated
|
|
60
|
-
const result = await client.graphql({
|
|
61
|
-
query: `
|
|
62
|
-
query GetOrder($ref: String!) {
|
|
63
|
-
order(ref: $ref) {
|
|
64
|
-
id
|
|
65
|
-
ref
|
|
66
|
-
status
|
|
67
|
-
totalPrice
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
`,
|
|
71
|
-
variables: { ref: ctx.data.orderId },
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Check GraphQL errors
|
|
75
|
-
if (result.errors?.length) {
|
|
76
|
-
throw new Error(`GraphQL error: ${result.errors[0].message}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check data exists
|
|
80
|
-
if (!result.data?.order) {
|
|
81
|
-
throw new Error(`Order not found: ${ctx.data.orderId}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
ctx.log('info', 'Workflow completed successfully', {
|
|
85
|
-
operationId,
|
|
86
|
-
orderId: ctx.data.orderId,
|
|
87
|
-
orderStatus: result.data.order.status,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
success: true,
|
|
92
|
-
operationId,
|
|
93
|
-
data: result.data.order,
|
|
94
|
-
};
|
|
95
|
-
} catch (error) {
|
|
96
|
-
// Categorize error
|
|
97
|
-
const errorType =
|
|
98
|
-
error instanceof Error && error.message.includes('GraphQL')
|
|
99
|
-
? 'GRAPHQL_ERROR'
|
|
100
|
-
: error instanceof Error && error.message.includes('not found')
|
|
101
|
-
? 'NOT_FOUND'
|
|
102
|
-
: error instanceof Error && error.message.includes('Missing required')
|
|
103
|
-
? 'VALIDATION_ERROR'
|
|
104
|
-
: 'UNKNOWN_ERROR';
|
|
105
|
-
|
|
106
|
-
ctx.log('error', 'Workflow failed', {
|
|
107
|
-
operationId,
|
|
108
|
-
errorType,
|
|
109
|
-
error: error instanceof Error ? error.message : String(error),
|
|
110
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
111
|
-
input: ctx.data,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Return structured error response
|
|
115
|
-
return {
|
|
116
|
-
success: false,
|
|
117
|
-
operationId,
|
|
118
|
-
errorType,
|
|
119
|
-
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
120
|
-
timestamp: new Date().toISOString(),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
);
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Pattern 2: Retry with Exponential Backoff
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
async function retryWithBackoff<T>(
|
|
131
|
-
operation: () => Promise<T>,
|
|
132
|
-
maxAttempts: number = 3,
|
|
133
|
-
baseDelay: number = 1000,
|
|
134
|
-
logger: any
|
|
135
|
-
): Promise<T> {
|
|
136
|
-
let lastError: Error | unknown;
|
|
137
|
-
|
|
138
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
139
|
-
try {
|
|
140
|
-
return await operation();
|
|
141
|
-
} catch (error) {
|
|
142
|
-
lastError = error;
|
|
143
|
-
|
|
144
|
-
if (attempt < maxAttempts) {
|
|
145
|
-
const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff
|
|
146
|
-
logger.warn(`Attempt ${attempt} failed, retrying in ${delay}ms`, {
|
|
147
|
-
error: error instanceof Error ? error.message : String(error),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
throw new Error(`Operation failed after ${maxAttempts} attempts: ${lastError}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Usage
|
|
159
|
-
export const withRetry = http(
|
|
160
|
-
'with-retry',
|
|
161
|
-
{
|
|
162
|
-
connection: 'fluent_commerce',
|
|
163
|
-
},
|
|
164
|
-
async ctx => {
|
|
165
|
-
const client = await createClient(ctx);
|
|
166
|
-
|
|
167
|
-
const result = await retryWithBackoff(
|
|
168
|
-
async () => {
|
|
169
|
-
return await client.graphql({
|
|
170
|
-
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
171
|
-
});
|
|
172
|
-
},
|
|
173
|
-
3,
|
|
174
|
-
1000,
|
|
175
|
-
ctx
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
return result.data;
|
|
179
|
-
}
|
|
180
|
-
);
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### Pattern 3: Circuit Breaker
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
187
|
-
|
|
188
|
-
class CircuitBreaker {
|
|
189
|
-
private adapter: VersoriKVAdapter;
|
|
190
|
-
private key: string;
|
|
191
|
-
private threshold: number;
|
|
192
|
-
private timeout: number;
|
|
193
|
-
|
|
194
|
-
constructor(adapter: VersoriKVAdapter, key: string, threshold = 5, timeout = 60000) {
|
|
195
|
-
this.adapter = adapter;
|
|
196
|
-
this.key = `circuit-breaker:${key}`;
|
|
197
|
-
this.threshold = threshold;
|
|
198
|
-
this.timeout = timeout;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
202
|
-
const state = (await this.adapter.get<{ failures: number; openedAt?: number }>(this.key)) || {
|
|
203
|
-
failures: 0,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// Check if circuit is open
|
|
207
|
-
if (state.openedAt) {
|
|
208
|
-
const elapsed = Date.now() - state.openedAt;
|
|
209
|
-
if (elapsed < this.timeout) {
|
|
210
|
-
throw new Error('Circuit breaker is OPEN - service unavailable');
|
|
211
|
-
}
|
|
212
|
-
// Timeout elapsed, reset to half-open
|
|
213
|
-
await this.adapter.set(this.key, { failures: 0 });
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const result = await operation();
|
|
218
|
-
// Success - reset failures
|
|
219
|
-
await this.adapter.set(this.key, { failures: 0 });
|
|
220
|
-
return result;
|
|
221
|
-
} catch (error) {
|
|
222
|
-
// Failure - increment counter
|
|
223
|
-
const newFailures = state.failures + 1;
|
|
224
|
-
|
|
225
|
-
if (newFailures >= this.threshold) {
|
|
226
|
-
// Open circuit
|
|
227
|
-
await this.adapter.set(this.key, {
|
|
228
|
-
failures: newFailures,
|
|
229
|
-
openedAt: Date.now(),
|
|
230
|
-
});
|
|
231
|
-
throw new Error(`Circuit breaker OPENED after ${newFailures} failures`);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
await this.adapter.set(this.key, { failures: newFailures });
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Usage
|
|
241
|
-
export const withCircuitBreaker = http(
|
|
242
|
-
'with-circuit-breaker',
|
|
243
|
-
{
|
|
244
|
-
connection: 'fluent_commerce',
|
|
245
|
-
},
|
|
246
|
-
async ctx => {
|
|
247
|
-
const kv = ctx.openKv(':project:');
|
|
248
|
-
const adapter = new VersoriKVAdapter(kv);
|
|
249
|
-
const breaker = new CircuitBreaker(adapter, 'fluent-api', 5, 60000);
|
|
250
|
-
|
|
251
|
-
const client = await createClient(ctx);
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const result = await breaker.execute(async () => {
|
|
255
|
-
return await client.graphql({
|
|
256
|
-
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
return { success: true, data: result.data };
|
|
261
|
-
} catch (error) {
|
|
262
|
-
return {
|
|
263
|
-
success: false,
|
|
264
|
-
error: error instanceof Error ? error.message : 'Circuit breaker error',
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
);
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## Performance Optimization
|
|
274
|
-
|
|
275
|
-
### 1. Batch Processing
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
import { schedule } from '@versori/run';
|
|
279
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Process large datasets in batches for memory efficiency
|
|
283
|
-
*/
|
|
284
|
-
export const batchProcessor = schedule(
|
|
285
|
-
'batch-processor',
|
|
286
|
-
'0 */6 * * *',
|
|
287
|
-
{
|
|
288
|
-
connection: 'fluent_commerce',
|
|
289
|
-
},
|
|
290
|
-
async ctx => {
|
|
291
|
-
const BATCH_SIZE = 100;
|
|
292
|
-
let processed = 0;
|
|
293
|
-
let hasMore = true;
|
|
294
|
-
let cursor: string | undefined = undefined;
|
|
295
|
-
|
|
296
|
-
const client = await createClient(ctx);
|
|
297
|
-
|
|
298
|
-
while (hasMore) {
|
|
299
|
-
// Fetch batch - schema validated
|
|
300
|
-
const result = await client.graphql({
|
|
301
|
-
query: `
|
|
302
|
-
query GetInventoryBatch($first: Int!, $after: String) {
|
|
303
|
-
inventoryPositions(first: $first, after: $after) {
|
|
304
|
-
edges {
|
|
305
|
-
node {
|
|
306
|
-
id
|
|
307
|
-
ref
|
|
308
|
-
productRef
|
|
309
|
-
locationRef
|
|
310
|
-
onHand
|
|
311
|
-
}
|
|
312
|
-
cursor
|
|
313
|
-
}
|
|
314
|
-
pageInfo {
|
|
315
|
-
hasNextPage
|
|
316
|
-
endCursor
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
`,
|
|
321
|
-
variables: { first: BATCH_SIZE, after: cursor },
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const items = result.data.inventoryPositions.edges;
|
|
325
|
-
|
|
326
|
-
// Process batch
|
|
327
|
-
await processBatch(items.map(e => e.node));
|
|
328
|
-
|
|
329
|
-
processed += items.length;
|
|
330
|
-
hasMore = result.data.inventoryPositions.pageInfo.hasNextPage;
|
|
331
|
-
cursor = result.data.inventoryPositions.pageInfo.endCursor;
|
|
332
|
-
|
|
333
|
-
ctx.log('info', `Processed batch`, {
|
|
334
|
-
batchSize: items.length,
|
|
335
|
-
totalProcessed: processed,
|
|
336
|
-
hasMore,
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
// Optional: Add small delay to avoid rate limits
|
|
340
|
-
if (hasMore) {
|
|
341
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return { totalProcessed: processed };
|
|
346
|
-
}
|
|
347
|
-
);
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### 2. Caching Strategy
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
import { http } from '@versori/run';
|
|
354
|
-
import { createClient, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Multi-level caching: memory + KV storage
|
|
358
|
-
*/
|
|
359
|
-
const memoryCache = new Map<string, { data: any; expiresAt: number }>();
|
|
360
|
-
|
|
361
|
-
async function getCached<T>(
|
|
362
|
-
key: string,
|
|
363
|
-
fetcher: () => Promise<T>,
|
|
364
|
-
ttl: number,
|
|
365
|
-
kvAdapter: VersoriKVAdapter,
|
|
366
|
-
logger: any
|
|
367
|
-
): Promise<T> {
|
|
368
|
-
// Level 1: Memory cache (fastest)
|
|
369
|
-
const memCached = memoryCache.get(key);
|
|
370
|
-
if (memCached && memCached.expiresAt > Date.now()) {
|
|
371
|
-
logger.debug('Memory cache hit', { key });
|
|
372
|
-
return memCached.data as T;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Level 2: KV storage (fast)
|
|
376
|
-
const kvCached = await kvAdapter.get<T>(key);
|
|
377
|
-
if (kvCached) {
|
|
378
|
-
logger.debug('KV cache hit', { key });
|
|
379
|
-
// Populate memory cache
|
|
380
|
-
memoryCache.set(key, { data: kvCached, expiresAt: Date.now() + ttl });
|
|
381
|
-
return kvCached;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Level 3: Fetch from source (slow)
|
|
385
|
-
logger.debug('Cache miss - fetching from source', { key });
|
|
386
|
-
const data = await fetcher();
|
|
387
|
-
|
|
388
|
-
// Populate both caches
|
|
389
|
-
memoryCache.set(key, { data, expiresAt: Date.now() + ttl });
|
|
390
|
-
await kvAdapter.set(key, data, ttl);
|
|
391
|
-
|
|
392
|
-
return data;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export const cachedWorkflow = http(
|
|
396
|
-
'cached-workflow',
|
|
397
|
-
{
|
|
398
|
-
connection: 'fluent_commerce',
|
|
399
|
-
},
|
|
400
|
-
async ctx => {
|
|
401
|
-
const kv = ctx.openKv(':project:');
|
|
402
|
-
const adapter = new VersoriKVAdapter(kv);
|
|
403
|
-
const client = await createClient(ctx);
|
|
404
|
-
|
|
405
|
-
const products = await getCached(
|
|
406
|
-
'cache:products:list',
|
|
407
|
-
async () => {
|
|
408
|
-
const result = await client.graphql({
|
|
409
|
-
query: `
|
|
410
|
-
query GetProducts($first: Int!) {
|
|
411
|
-
products(first: $first) {
|
|
412
|
-
edges {
|
|
413
|
-
node {
|
|
414
|
-
id
|
|
415
|
-
ref
|
|
416
|
-
name
|
|
417
|
-
status
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
`,
|
|
423
|
-
variables: { first: 100 },
|
|
424
|
-
});
|
|
425
|
-
return result.data.products.edges.map(e => e.node);
|
|
426
|
-
},
|
|
427
|
-
3600000, // 1 hour
|
|
428
|
-
adapter,
|
|
429
|
-
ctx
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
return { data: products, cached: true };
|
|
433
|
-
}
|
|
434
|
-
);
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### 3. Parallel Processing
|
|
438
|
-
|
|
439
|
-
**⚠️ CRITICAL**: When using Versori's `.unpack().parallel()`, remember it's **NOT fault-tolerant**. One failure stops everything. For production workloads, prefer `Promise.allSettled()`.
|
|
440
|
-
|
|
441
|
-
#### Pattern 1: Promise.all() for Independent Operations
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
export const parallelProcessor = http(
|
|
445
|
-
'parallel-processor',
|
|
446
|
-
{
|
|
447
|
-
connection: 'fluent_commerce',
|
|
448
|
-
},
|
|
449
|
-
async ctx => {
|
|
450
|
-
const client = await createClient(ctx);
|
|
451
|
-
|
|
452
|
-
// Fetch multiple resources in parallel
|
|
453
|
-
const [products, orders, inventory] = await Promise.all([
|
|
454
|
-
client.graphql({
|
|
455
|
-
query: `query { products(first: 50) { edges { node { id ref } } } }`,
|
|
456
|
-
}),
|
|
457
|
-
client.graphql({
|
|
458
|
-
query: `query { orders(first: 50) { edges { node { id ref } } } }`,
|
|
459
|
-
}),
|
|
460
|
-
client.graphql({
|
|
461
|
-
query: `query { inventoryPositions(first: 50) { edges { node { id ref } } } }`,
|
|
462
|
-
}),
|
|
463
|
-
]);
|
|
464
|
-
|
|
465
|
-
return {
|
|
466
|
-
products: products.data.products.edges.length,
|
|
467
|
-
orders: orders.data.orders.edges.length,
|
|
468
|
-
inventory: inventory.data.inventoryPositions.edges.length,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
);
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
#### Pattern 2: Promise.allSettled() for Fault Tolerance (Recommended)
|
|
475
|
-
|
|
476
|
-
```typescript
|
|
477
|
-
export const faultTolerantProcessor = http(
|
|
478
|
-
'fault-tolerant-processor',
|
|
479
|
-
{
|
|
480
|
-
connection: 'fluent_commerce',
|
|
481
|
-
},
|
|
482
|
-
async ctx => {
|
|
483
|
-
const client = await createClient(ctx);
|
|
484
|
-
const files = ['file1.xml', 'file2.xml', 'file3.xml'];
|
|
485
|
-
|
|
486
|
-
// ✅ Continues even if some operations fail
|
|
487
|
-
const results = await Promise.allSettled(
|
|
488
|
-
files.map(file => processFile(file))
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
const successes = results
|
|
492
|
-
.filter(r => r.status === 'fulfilled')
|
|
493
|
-
.map(r => r.value);
|
|
494
|
-
|
|
495
|
-
const failures = results
|
|
496
|
-
.filter(r => r.status === 'rejected')
|
|
497
|
-
.map(r => ({ file: r.reason.file, error: r.reason.message }));
|
|
498
|
-
|
|
499
|
-
ctx.log('info', 'Processing complete', {
|
|
500
|
-
successful: successes.length,
|
|
501
|
-
failed: failures.length,
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// Handle failures
|
|
505
|
-
for (const failure of failures) {
|
|
506
|
-
ctx.log('error', 'File processing failed', failure);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
return { successes, failures };
|
|
510
|
-
}
|
|
511
|
-
);
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
#### Pattern 3: Versori .unpack().parallel() (Use with Caution)
|
|
515
|
-
|
|
516
|
-
**⚠️ Only use if**:
|
|
517
|
-
- All tasks **must** succeed (transactional)
|
|
518
|
-
- You wrap each task with error handling that returns error objects instead of throwing
|
|
519
|
-
- You're okay with losing all results on any failure
|
|
520
|
-
|
|
521
|
-
```typescript
|
|
522
|
-
// ✅ Safe usage: Handle errors inside each task
|
|
523
|
-
schedule('process-files', '0 2 * * *')
|
|
524
|
-
.then(fn('fetch-files', (ctx) => ['file1.xml', 'file2.xml', 'file3.xml']))
|
|
525
|
-
.unpack()
|
|
526
|
-
.parallel(fn('process-file', async (ctx) => {
|
|
527
|
-
try {
|
|
528
|
-
const result = await processFile(ctx.data);
|
|
529
|
-
return { success: true, file: ctx.data, result };
|
|
530
|
-
} catch (error) {
|
|
531
|
-
// ✅ Return error object instead of throwing
|
|
532
|
-
return {
|
|
533
|
-
success: false,
|
|
534
|
-
file: ctx.data,
|
|
535
|
-
error: error.message
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
}))
|
|
539
|
-
.then(fn('process-results', (ctx) => {
|
|
540
|
-
// ✅ Now you have all results (success + failures)
|
|
541
|
-
const results = ctx.data;
|
|
542
|
-
const successes = results.filter(r => r.success);
|
|
543
|
-
const failures = results.filter(r => !r.success);
|
|
544
|
-
|
|
545
|
-
ctx.log.info(`Processed ${successes.length} files, ${failures.length} failed`);
|
|
546
|
-
return { successes, failures };
|
|
547
|
-
}));
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
**See**: [Module 4: Workflows - Parallel Processing](./platforms-versori-04-workflows.md#-critical-parallel-processing-with-unpack-and-parallel) for detailed guidance.
|
|
551
|
-
|
|
552
|
-
---
|
|
553
|
-
|
|
554
|
-
## Security Hardening
|
|
555
|
-
|
|
556
|
-
### 1. Input Validation
|
|
557
|
-
|
|
558
|
-
```typescript
|
|
559
|
-
import { webhook } from '@versori/run';
|
|
560
|
-
|
|
561
|
-
// Validation schemas
|
|
562
|
-
const orderSchema = {
|
|
563
|
-
orderId: { type: 'string', required: true, pattern: /^ORD-\d{6}$/ },
|
|
564
|
-
customerId: { type: 'string', required: true },
|
|
565
|
-
items: { type: 'array', required: true, minLength: 1 },
|
|
566
|
-
totalPrice: { type: 'number', required: true, min: 0 },
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
function validateInput(data: any, schema: any): { valid: boolean; errors: string[] } {
|
|
570
|
-
const errors: string[] = [];
|
|
571
|
-
|
|
572
|
-
for (const [field, rules] of Object.entries(schema)) {
|
|
573
|
-
const value = data[field];
|
|
574
|
-
|
|
575
|
-
// Required check
|
|
576
|
-
if (rules.required && (value === undefined || value === null)) {
|
|
577
|
-
errors.push(`Missing required field: ${field}`);
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Type check
|
|
582
|
-
if (value !== undefined && rules.type) {
|
|
583
|
-
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
584
|
-
if (actualType !== rules.type) {
|
|
585
|
-
errors.push(`Invalid type for ${field}: expected ${rules.type}, got ${actualType}`);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Pattern check
|
|
590
|
-
if (value && rules.pattern && !rules.pattern.test(value)) {
|
|
591
|
-
errors.push(`Invalid format for ${field}`);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Min/max checks
|
|
595
|
-
if (typeof value === 'number') {
|
|
596
|
-
if (rules.min !== undefined && value < rules.min) {
|
|
597
|
-
errors.push(`${field} must be >= ${rules.min}`);
|
|
598
|
-
}
|
|
599
|
-
if (rules.max !== undefined && value > rules.max) {
|
|
600
|
-
errors.push(`${field} must be <= ${rules.max}`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Array length checks
|
|
605
|
-
if (Array.isArray(value)) {
|
|
606
|
-
if (rules.minLength !== undefined && value.length < rules.minLength) {
|
|
607
|
-
errors.push(`${field} must have at least ${rules.minLength} items`);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
return { valid: errors.length === 0, errors };
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
export const validatedWebhook = webhook('validated-webhook', async ctx => {
|
|
616
|
-
// Validate input
|
|
617
|
-
const validation = validateInput(ctx.data, orderSchema);
|
|
618
|
-
|
|
619
|
-
if (!validation.valid) {
|
|
620
|
-
ctx.log('warn', 'Validation failed', { errors: validation.errors });
|
|
621
|
-
return {
|
|
622
|
-
success: false,
|
|
623
|
-
errors: validation.errors,
|
|
624
|
-
status: 400,
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Process validated data
|
|
629
|
-
ctx.log('info', 'Order validated successfully', { orderId: ctx.data.orderId });
|
|
630
|
-
|
|
631
|
-
return { success: true, orderId: ctx.data.orderId };
|
|
632
|
-
});
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
### 2. Rate Limiting
|
|
636
|
-
|
|
637
|
-
```typescript
|
|
638
|
-
import { fn } from '@versori/run';
|
|
639
|
-
import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
640
|
-
|
|
641
|
-
async function checkRateLimit(
|
|
642
|
-
adapter: VersoriKVAdapter,
|
|
643
|
-
identifier: string,
|
|
644
|
-
maxRequests: number,
|
|
645
|
-
windowMs: number
|
|
646
|
-
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
|
|
647
|
-
const key = `ratelimit:${identifier}`;
|
|
648
|
-
const now = Date.now();
|
|
649
|
-
|
|
650
|
-
// Get current count
|
|
651
|
-
const current = await adapter.get<{ count: number; resetAt: number }>(key);
|
|
652
|
-
|
|
653
|
-
if (!current) {
|
|
654
|
-
// First request
|
|
655
|
-
const resetAt = now + windowMs;
|
|
656
|
-
await adapter.set(key, { count: 1, resetAt }, windowMs);
|
|
657
|
-
return { allowed: true, remaining: maxRequests - 1, resetAt };
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
if (now > current.resetAt) {
|
|
661
|
-
// Window expired, reset
|
|
662
|
-
const resetAt = now + windowMs;
|
|
663
|
-
await adapter.set(key, { count: 1, resetAt }, windowMs);
|
|
664
|
-
return { allowed: true, remaining: maxRequests - 1, resetAt };
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (current.count >= maxRequests) {
|
|
668
|
-
// Rate limit exceeded
|
|
669
|
-
return { allowed: false, remaining: 0, resetAt: current.resetAt };
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Increment count
|
|
673
|
-
await adapter.set(key, { count: current.count + 1, resetAt: current.resetAt }, windowMs);
|
|
674
|
-
return { allowed: true, remaining: maxRequests - current.count - 1, resetAt: current.resetAt };
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
export const rateLimitedEndpoint = http(
|
|
678
|
-
'rate-limited',
|
|
679
|
-
{
|
|
680
|
-
connection: 'fluent_commerce',
|
|
681
|
-
},
|
|
682
|
-
async ctx => {
|
|
683
|
-
const kv = ctx.openKv(':project:');
|
|
684
|
-
const adapter = new VersoriKVAdapter(kv);
|
|
685
|
-
|
|
686
|
-
const clientId = ctx.query?.clientId || 'anonymous';
|
|
687
|
-
const rateLimit = await checkRateLimit(adapter, clientId, 100, 3600000); // 100 req/hour
|
|
688
|
-
|
|
689
|
-
if (!rateLimit.allowed) {
|
|
690
|
-
const retryAfter = Math.ceil((rateLimit.resetAt - Date.now()) / 1000);
|
|
691
|
-
ctx.log('warn', 'Rate limit exceeded', { clientId, retryAfter });
|
|
692
|
-
|
|
693
|
-
return {
|
|
694
|
-
error: 'Rate limit exceeded',
|
|
695
|
-
retryAfter,
|
|
696
|
-
resetAt: new Date(rateLimit.resetAt).toISOString(),
|
|
697
|
-
status: 429,
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// Process request
|
|
702
|
-
const client = await createClient(ctx);
|
|
703
|
-
const result = await client.graphql({
|
|
704
|
-
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
return {
|
|
708
|
-
data: result.data,
|
|
709
|
-
rateLimit: {
|
|
710
|
-
remaining: rateLimit.remaining,
|
|
711
|
-
resetAt: new Date(rateLimit.resetAt).toISOString(),
|
|
712
|
-
},
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
);
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
### 3. Secrets Management
|
|
719
|
-
|
|
720
|
-
```typescript
|
|
721
|
-
// ❌ NEVER do this
|
|
722
|
-
const BAD_EXAMPLE = {
|
|
723
|
-
clientId: 'my-client-id',
|
|
724
|
-
clientSecret: 'my-secret', // NEVER hardcode secrets!
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// ✅ Use connector variables
|
|
728
|
-
export const secureWorkflow = http(
|
|
729
|
-
'secure',
|
|
730
|
-
{
|
|
731
|
-
connection: 'fluent_commerce',
|
|
732
|
-
},
|
|
733
|
-
async ctx => {
|
|
734
|
-
// Access secrets from connector variables
|
|
735
|
-
const webhookKey = ctx.vars?.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
736
|
-
const apiKey = ctx.vars?.THIRD_PARTY_API_KEY;
|
|
737
|
-
|
|
738
|
-
// Validate secrets exist
|
|
739
|
-
if (!webhookKey || !apiKey) {
|
|
740
|
-
ctx.log('error', 'Missing required secrets');
|
|
741
|
-
return { error: 'Configuration error', status: 500 };
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// Use secrets securely (don't log them)
|
|
745
|
-
ctx.log('info', 'Secrets loaded', {
|
|
746
|
-
hasWebhookKey: !!webhookKey,
|
|
747
|
-
hasApiKey: !!apiKey,
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
// ... use secrets
|
|
751
|
-
}
|
|
752
|
-
);
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
---
|
|
756
|
-
|
|
757
|
-
## Cost Optimization
|
|
758
|
-
|
|
759
|
-
### 1. Minimize KV Operations
|
|
760
|
-
|
|
761
|
-
```typescript
|
|
762
|
-
// ❌ Expensive - sequential writes without atomicity
|
|
763
|
-
for (const item of items) {
|
|
764
|
-
await kv.set(`item:${item.id}`, item); // N sequential operations
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// ✅ Optimized - use atomic operations for batch writes
|
|
768
|
-
// Note: VersoriKVAdapter doesn't have mset() method - use atomic() instead
|
|
769
|
-
const adapter = new VersoriKVAdapter(kv);
|
|
770
|
-
const atomic = adapter.atomic();
|
|
771
|
-
for (const item of items) {
|
|
772
|
-
atomic.set(['item', item.id], item);
|
|
773
|
-
}
|
|
774
|
-
await atomic.commit(); // 1 atomic transaction - all succeed or all fail
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
### 2. Optimize Cron Schedules
|
|
778
|
-
|
|
779
|
-
```typescript
|
|
780
|
-
// ❌ Too frequent - runs every minute (1440 times/day)
|
|
781
|
-
schedule('too-frequent', '* * * * *', handler);
|
|
782
|
-
|
|
783
|
-
// ✅ Optimized - runs every 6 hours (4 times/day)
|
|
784
|
-
schedule('optimized', '0 */6 * * *', handler);
|
|
785
|
-
|
|
786
|
-
// ❌ Overlapping schedules
|
|
787
|
-
schedule('sync-1', '0 * * * *', syncHandler); // Every hour
|
|
788
|
-
schedule('sync-2', '30 * * * *', syncHandler); // Every hour at :30
|
|
789
|
-
|
|
790
|
-
// ✅ Consolidated schedule
|
|
791
|
-
schedule('unified-sync', '0 */2 * * *', async ctx => {
|
|
792
|
-
await syncHandler(ctx);
|
|
793
|
-
// Do both operations in one run
|
|
794
|
-
});
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
### 3. Cache Expensive Operations
|
|
798
|
-
|
|
799
|
-
```typescript
|
|
800
|
-
export const costOptimized = http(
|
|
801
|
-
'cost-optimized',
|
|
802
|
-
{
|
|
803
|
-
connection: 'fluent_commerce',
|
|
804
|
-
},
|
|
805
|
-
async ctx => {
|
|
806
|
-
const kv = ctx.openKv(':project:');
|
|
807
|
-
const adapter = new VersoriKVAdapter(kv);
|
|
808
|
-
|
|
809
|
-
// Check cache first
|
|
810
|
-
const cacheKey = 'expensive:operation:result';
|
|
811
|
-
const cached = await adapter.get(cacheKey);
|
|
812
|
-
|
|
813
|
-
if (cached) {
|
|
814
|
-
ctx.log('info', 'Returning cached result');
|
|
815
|
-
return { data: cached, source: 'cache' };
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Expensive operation
|
|
819
|
-
const client = await createClient(ctx);
|
|
820
|
-
const result = await client.graphql({
|
|
821
|
-
query: `... complex query ...`,
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
// Cache for 1 hour
|
|
825
|
-
await adapter.set(cacheKey, result.data, 3600000);
|
|
826
|
-
|
|
827
|
-
return { data: result.data, source: 'api' };
|
|
828
|
-
}
|
|
829
|
-
);
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
---
|
|
833
|
-
|
|
834
|
-
## Monitoring & Alerting
|
|
835
|
-
|
|
836
|
-
### Critical Metrics Tracking
|
|
837
|
-
|
|
838
|
-
```typescript
|
|
839
|
-
import { schedule } from '@versori/run';
|
|
840
|
-
import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
841
|
-
|
|
842
|
-
export const metricsCollector = schedule('metrics-collector', '*/5 * * * *', async ctx => {
|
|
843
|
-
const kv = ctx.openKv(':project:');
|
|
844
|
-
const adapter = new VersoriKVAdapter(kv);
|
|
845
|
-
|
|
846
|
-
// Note: VersoriKVAdapter doesn't have keys() method - use native Versori KV list() instead
|
|
847
|
-
const allKeys = await kv.list();
|
|
848
|
-
const processedFileKeys = await kv.list({ prefix: 'files:processed:' });
|
|
849
|
-
|
|
850
|
-
// Collect metrics
|
|
851
|
-
const metrics = {
|
|
852
|
-
timestamp: Date.now(),
|
|
853
|
-
workflows: {
|
|
854
|
-
totalExecutions: (await adapter.get(['metrics', 'total', 'executions'])) || 0,
|
|
855
|
-
failedExecutions: (await adapter.get(['metrics', 'failed', 'executions'])) || 0,
|
|
856
|
-
avgDuration: (await adapter.get(['metrics', 'avg', 'duration'])) || 0,
|
|
857
|
-
},
|
|
858
|
-
storage: {
|
|
859
|
-
kvKeys: allKeys.length,
|
|
860
|
-
processedFiles: processedFileKeys.length,
|
|
861
|
-
},
|
|
862
|
-
health: {
|
|
863
|
-
lastHealthCheck: await adapter.get(['health', 'last', 'check']),
|
|
864
|
-
fluentApiStatus: await adapter.get(['health', 'fluent', 'status']),
|
|
865
|
-
},
|
|
866
|
-
};
|
|
867
|
-
|
|
868
|
-
// Calculate derived metrics
|
|
869
|
-
const successRate =
|
|
870
|
-
metrics.workflows.totalExecutions > 0
|
|
871
|
-
? (1 - metrics.workflows.failedExecutions / metrics.workflows.totalExecutions) * 100
|
|
872
|
-
: 100;
|
|
873
|
-
|
|
874
|
-
// Store metrics history
|
|
875
|
-
const metricsHistory = (await adapter.get(['metrics', 'history'])) || [];
|
|
876
|
-
metricsHistory.push({ ...metrics, successRate });
|
|
877
|
-
|
|
878
|
-
// Keep last 288 entries (24 hours at 5-minute intervals)
|
|
879
|
-
if (metricsHistory.length > 288) {
|
|
880
|
-
metricsHistory.shift();
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
await adapter.set(['metrics', 'history'], metricsHistory);
|
|
884
|
-
|
|
885
|
-
// Alert on critical thresholds
|
|
886
|
-
if (successRate < 95) {
|
|
887
|
-
ctx.log('error', 'ALERT: Success rate below threshold', {
|
|
888
|
-
successRate: successRate.toFixed(2),
|
|
889
|
-
threshold: 95,
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
if (metrics.workflows.avgDuration > 10000) {
|
|
894
|
-
ctx.log('warn', 'ALERT: Average duration above threshold', {
|
|
895
|
-
avgDuration: metrics.workflows.avgDuration,
|
|
896
|
-
threshold: 10000,
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
ctx.log('info', 'Metrics collected', {
|
|
901
|
-
successRate: successRate.toFixed(2),
|
|
902
|
-
avgDuration: metrics.workflows.avgDuration,
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
return metrics;
|
|
906
|
-
});
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
---
|
|
910
|
-
|
|
911
|
-
## Troubleshooting Guide
|
|
912
|
-
|
|
913
|
-
### Common Issues & Solutions
|
|
914
|
-
|
|
915
|
-
#### Issue 1: "No authentication credentials found"
|
|
916
|
-
|
|
917
|
-
**Symptoms**: Error when calling Fluent API
|
|
918
|
-
|
|
919
|
-
**Cause**: Using `fn()` instead of `http()` for external API calls
|
|
920
|
-
|
|
921
|
-
**Solution**:
|
|
922
|
-
|
|
923
|
-
```typescript
|
|
924
|
-
// ❌ Wrong
|
|
925
|
-
export const bad = fn('bad', async ctx => {
|
|
926
|
-
const client = await createClient(ctx); // FAILS - no ctx.fetch
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
// ✅ Correct
|
|
930
|
-
export const good = http(
|
|
931
|
-
'good',
|
|
932
|
-
{
|
|
933
|
-
connection: 'fluent_commerce',
|
|
934
|
-
},
|
|
935
|
-
async ctx => {
|
|
936
|
-
const client = await createClient(ctx); // Works
|
|
937
|
-
}
|
|
938
|
-
);
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
#### Issue 2: Duplicate File Processing
|
|
942
|
-
|
|
943
|
-
**Symptoms**: Same file processed multiple times
|
|
944
|
-
|
|
945
|
-
**Cause**: Missing or incorrect file tracking
|
|
946
|
-
|
|
947
|
-
**Solution**:
|
|
948
|
-
|
|
949
|
-
```typescript
|
|
950
|
-
import { VersoriFileTracker } from '@fluentcommerce/fc-connect-sdk';
|
|
951
|
-
|
|
952
|
-
const kv = ctx.openKv(':project:');
|
|
953
|
-
const tracker = new VersoriFileTracker(kv, 'namespace');
|
|
954
|
-
|
|
955
|
-
// Check before processing
|
|
956
|
-
if (await tracker.wasFileProcessed(fileName)) {
|
|
957
|
-
return { skipped: true };
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// Process file...
|
|
961
|
-
|
|
962
|
-
// Mark as processed
|
|
963
|
-
await tracker.markFileProcessed(fileName, { records: count });
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
#### Issue 3: Memory Errors with Large Datasets
|
|
967
|
-
|
|
968
|
-
**Symptoms**: Out of memory errors
|
|
969
|
-
|
|
970
|
-
**Cause**: Loading entire dataset into memory
|
|
971
|
-
|
|
972
|
-
**Solution**:
|
|
973
|
-
|
|
974
|
-
```typescript
|
|
975
|
-
// ❌ Wrong - loads everything
|
|
976
|
-
const allData = await fetchAllData();
|
|
977
|
-
await processData(allData);
|
|
978
|
-
|
|
979
|
-
// ✅ Correct - batch processing
|
|
980
|
-
const BATCH_SIZE = 100;
|
|
981
|
-
let cursor = undefined;
|
|
982
|
-
let hasMore = true;
|
|
983
|
-
|
|
984
|
-
while (hasMore) {
|
|
985
|
-
const batch = await fetchBatch(BATCH_SIZE, cursor);
|
|
986
|
-
await processBatch(batch);
|
|
987
|
-
hasMore = batch.pageInfo.hasNextPage;
|
|
988
|
-
cursor = batch.pageInfo.endCursor;
|
|
989
|
-
}
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
---
|
|
993
|
-
|
|
994
|
-
## Practice Solutions
|
|
995
|
-
|
|
996
|
-
### Module 2: Quick Start Exercise Solution
|
|
997
|
-
|
|
998
|
-
```typescript
|
|
999
|
-
import { http } from '@versori/run';
|
|
1000
|
-
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
1001
|
-
|
|
1002
|
-
/**
|
|
1003
|
-
* Create inventory position
|
|
1004
|
-
* POST https://{workspace}.versori.run/create-inventory
|
|
1005
|
-
* Body: { productRef, locationRef, qty }
|
|
1006
|
-
*/
|
|
1007
|
-
export const createInventoryPosition = http(
|
|
1008
|
-
'create-inventory',
|
|
1009
|
-
{
|
|
1010
|
-
connection: 'fluent_commerce',
|
|
1011
|
-
},
|
|
1012
|
-
async ctx => {
|
|
1013
|
-
ctx.log('info', 'Creating inventory position');
|
|
1014
|
-
|
|
1015
|
-
const { productRef, locationRef, qty } = ctx.data || {};
|
|
1016
|
-
|
|
1017
|
-
// Validation
|
|
1018
|
-
if (!productRef || !locationRef || qty === undefined) {
|
|
1019
|
-
return {
|
|
1020
|
-
success: false,
|
|
1021
|
-
error: 'Missing required fields: productRef, locationRef, qty',
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
try {
|
|
1026
|
-
const client = await createClient(ctx);
|
|
1027
|
-
|
|
1028
|
-
const result = await client.graphql({
|
|
1029
|
-
query: `
|
|
1030
|
-
mutation CreateInventory($input: CreateInventoryPositionInput!) {
|
|
1031
|
-
createInventoryPosition(input: $input) {
|
|
1032
|
-
id
|
|
1033
|
-
ref
|
|
1034
|
-
productRef
|
|
1035
|
-
locationRef
|
|
1036
|
-
onHand
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
`,
|
|
1040
|
-
variables: {
|
|
1041
|
-
input: { productRef, locationRef, qty },
|
|
1042
|
-
},
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
|
-
if (result.errors?.length) {
|
|
1046
|
-
throw new Error(result.errors[0].message);
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
ctx.log('info', 'Inventory position created', {
|
|
1050
|
-
id: result.data.createInventoryPosition.id,
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
return {
|
|
1054
|
-
success: true,
|
|
1055
|
-
data: result.data.createInventoryPosition,
|
|
1056
|
-
};
|
|
1057
|
-
} catch (error) {
|
|
1058
|
-
ctx.log('error', 'Failed to create inventory position', {
|
|
1059
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
return {
|
|
1063
|
-
success: false,
|
|
1064
|
-
error: error instanceof Error ? error.message : 'Creation failed',
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
);
|
|
1069
|
-
```
|
|
1070
|
-
|
|
1071
|
-
---
|
|
1072
|
-
|
|
1073
|
-
## Key Takeaways
|
|
1074
|
-
|
|
1075
|
-
- 🎯 **Error handling** with comprehensive try-catch, retry, and circuit breaker patterns
|
|
1076
|
-
- 🎯 **Performance** via batching, caching, and parallel processing
|
|
1077
|
-
- 🎯 **Security** through input validation, rate limiting, and secrets management
|
|
1078
|
-
- 🎯 **Cost optimization** by minimizing KV ops, optimizing schedules, and caching
|
|
1079
|
-
- 🎯 **Monitoring** with metrics tracking and alerting on critical thresholds
|
|
1080
|
-
- 🎯 **Troubleshooting** using structured debugging and common issue patterns
|
|
1081
|
-
|
|
1082
|
-
---
|
|
1083
|
-
|
|
1084
|
-
## Conclusion
|
|
1085
|
-
|
|
1086
|
-
You've completed the Versori Platform Integration Guide! You now have:
|
|
1087
|
-
|
|
1088
|
-
- ✅ Understanding of Versori platform architecture
|
|
1089
|
-
- ✅ Ability to create all workflow types (http, webhook, schedule, fn)
|
|
1090
|
-
- ✅ Connection management expertise
|
|
1091
|
-
- ✅ KV storage and state management skills
|
|
1092
|
-
- ✅ Deployment and CI/CD knowledge
|
|
1093
|
-
- ✅ Production-ready patterns and best practices
|
|
1094
|
-
|
|
1095
|
-
### Next Steps
|
|
1096
|
-
|
|
1097
|
-
1. **Build Your Integration**: Apply these patterns to your specific use case
|
|
1098
|
-
2. **Review Examples**: Study the connector examples in `connectors/` directory
|
|
1099
|
-
3. **Consult SDK Guides**: Deep dive into specific SDK features
|
|
1100
|
-
4. **Join Community**: Engage with other developers using Versori
|
|
1101
|
-
|
|
1102
|
-
### Additional Resources
|
|
1103
|
-
|
|
1104
|
-
- [Versori Platform Documentation](https://docs.versori.com)
|
|
1105
|
-
- [FC Connect SDK API Reference](../../../../02-CORE-GUIDES/api-reference/api-reference-readme.md)
|
|
1106
|
-
- [Sample Connectors](../../../../01-TEMPLATES/versori/workflows/readme.md) - Production connector examples
|
|
1107
|
-
- [Webhook Response Patterns](.././modules/platforms-versori-04-workflows.md#critical-non-json-response-handlers-xml-html-csv)
|
|
1108
|
-
|
|
1109
|
-
---
|
|
1110
|
-
|
|
1111
|
-
[← Previous: Module 7](./platforms-versori-07-deployment.md) | [Back to Guide](../platforms-versori-readme.md)
|
|
1
|
+
# Module 8: Best Practices & Production Patterns
|
|
2
|
+
|
|
3
|
+
[← Back to Versori Platform Guide](../platforms-versori-readme.md)
|
|
4
|
+
|
|
5
|
+
**Module 8 of 8** | **Level**: Advanced | **Time**: 30 minutes
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Learning Objectives
|
|
10
|
+
|
|
11
|
+
By the end of this module, you will:
|
|
12
|
+
|
|
13
|
+
- ✅ Implement production-ready error handling patterns
|
|
14
|
+
- ✅ Optimize workflow performance and reduce costs
|
|
15
|
+
- ✅ Apply security hardening best practices
|
|
16
|
+
- ✅ Implement robust retry and fallback strategies
|
|
17
|
+
- ✅ Troubleshoot common production issues
|
|
18
|
+
- ✅ Monitor and alert on critical metrics
|
|
19
|
+
- ✅ Apply cost optimization techniques
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Production-Ready Error Handling
|
|
24
|
+
|
|
25
|
+
### Pattern 1: Comprehensive Try-Catch with Context
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { http } from '@versori/run';
|
|
29
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
30
|
+
|
|
31
|
+
export const robustWorkflow = http(
|
|
32
|
+
'robust-workflow',
|
|
33
|
+
{
|
|
34
|
+
connection: 'fluent_commerce',
|
|
35
|
+
retry: {
|
|
36
|
+
attempts: 3,
|
|
37
|
+
delay: 1000,
|
|
38
|
+
backoff: 2, // Exponential backoff
|
|
39
|
+
},
|
|
40
|
+
timeout: 30000,
|
|
41
|
+
},
|
|
42
|
+
async ctx => {
|
|
43
|
+
const operationId = `${ctx.executionId}-${Date.now()}`;
|
|
44
|
+
|
|
45
|
+
ctx.log('info', 'Workflow started', {
|
|
46
|
+
operationId,
|
|
47
|
+
executionId: ctx.executionId,
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const client = await createClient(ctx);
|
|
53
|
+
|
|
54
|
+
// Validate input
|
|
55
|
+
if (!ctx.data?.orderId) {
|
|
56
|
+
throw new Error('Missing required field: orderId');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// GraphQL query - schema validated
|
|
60
|
+
const result = await client.graphql({
|
|
61
|
+
query: `
|
|
62
|
+
query GetOrder($ref: String!) {
|
|
63
|
+
order(ref: $ref) {
|
|
64
|
+
id
|
|
65
|
+
ref
|
|
66
|
+
status
|
|
67
|
+
totalPrice
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`,
|
|
71
|
+
variables: { ref: ctx.data.orderId },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Check GraphQL errors
|
|
75
|
+
if (result.errors?.length) {
|
|
76
|
+
throw new Error(`GraphQL error: ${result.errors[0].message}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check data exists
|
|
80
|
+
if (!result.data?.order) {
|
|
81
|
+
throw new Error(`Order not found: ${ctx.data.orderId}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ctx.log('info', 'Workflow completed successfully', {
|
|
85
|
+
operationId,
|
|
86
|
+
orderId: ctx.data.orderId,
|
|
87
|
+
orderStatus: result.data.order.status,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
operationId,
|
|
93
|
+
data: result.data.order,
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// Categorize error
|
|
97
|
+
const errorType =
|
|
98
|
+
error instanceof Error && error.message.includes('GraphQL')
|
|
99
|
+
? 'GRAPHQL_ERROR'
|
|
100
|
+
: error instanceof Error && error.message.includes('not found')
|
|
101
|
+
? 'NOT_FOUND'
|
|
102
|
+
: error instanceof Error && error.message.includes('Missing required')
|
|
103
|
+
? 'VALIDATION_ERROR'
|
|
104
|
+
: 'UNKNOWN_ERROR';
|
|
105
|
+
|
|
106
|
+
ctx.log('error', 'Workflow failed', {
|
|
107
|
+
operationId,
|
|
108
|
+
errorType,
|
|
109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
110
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
111
|
+
input: ctx.data,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Return structured error response
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
operationId,
|
|
118
|
+
errorType,
|
|
119
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Pattern 2: Retry with Exponential Backoff
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
async function retryWithBackoff<T>(
|
|
131
|
+
operation: () => Promise<T>,
|
|
132
|
+
maxAttempts: number = 3,
|
|
133
|
+
baseDelay: number = 1000,
|
|
134
|
+
logger: any
|
|
135
|
+
): Promise<T> {
|
|
136
|
+
let lastError: Error | unknown;
|
|
137
|
+
|
|
138
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
139
|
+
try {
|
|
140
|
+
return await operation();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
lastError = error;
|
|
143
|
+
|
|
144
|
+
if (attempt < maxAttempts) {
|
|
145
|
+
const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff
|
|
146
|
+
logger.warn(`Attempt ${attempt} failed, retrying in ${delay}ms`, {
|
|
147
|
+
error: error instanceof Error ? error.message : String(error),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
throw new Error(`Operation failed after ${maxAttempts} attempts: ${lastError}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Usage
|
|
159
|
+
export const withRetry = http(
|
|
160
|
+
'with-retry',
|
|
161
|
+
{
|
|
162
|
+
connection: 'fluent_commerce',
|
|
163
|
+
},
|
|
164
|
+
async ctx => {
|
|
165
|
+
const client = await createClient(ctx);
|
|
166
|
+
|
|
167
|
+
const result = await retryWithBackoff(
|
|
168
|
+
async () => {
|
|
169
|
+
return await client.graphql({
|
|
170
|
+
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
3,
|
|
174
|
+
1000,
|
|
175
|
+
ctx
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return result.data;
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Pattern 3: Circuit Breaker
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
187
|
+
|
|
188
|
+
class CircuitBreaker {
|
|
189
|
+
private adapter: VersoriKVAdapter;
|
|
190
|
+
private key: string;
|
|
191
|
+
private threshold: number;
|
|
192
|
+
private timeout: number;
|
|
193
|
+
|
|
194
|
+
constructor(adapter: VersoriKVAdapter, key: string, threshold = 5, timeout = 60000) {
|
|
195
|
+
this.adapter = adapter;
|
|
196
|
+
this.key = `circuit-breaker:${key}`;
|
|
197
|
+
this.threshold = threshold;
|
|
198
|
+
this.timeout = timeout;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
202
|
+
const state = (await this.adapter.get<{ failures: number; openedAt?: number }>(this.key)) || {
|
|
203
|
+
failures: 0,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Check if circuit is open
|
|
207
|
+
if (state.openedAt) {
|
|
208
|
+
const elapsed = Date.now() - state.openedAt;
|
|
209
|
+
if (elapsed < this.timeout) {
|
|
210
|
+
throw new Error('Circuit breaker is OPEN - service unavailable');
|
|
211
|
+
}
|
|
212
|
+
// Timeout elapsed, reset to half-open
|
|
213
|
+
await this.adapter.set(this.key, { failures: 0 });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await operation();
|
|
218
|
+
// Success - reset failures
|
|
219
|
+
await this.adapter.set(this.key, { failures: 0 });
|
|
220
|
+
return result;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
// Failure - increment counter
|
|
223
|
+
const newFailures = state.failures + 1;
|
|
224
|
+
|
|
225
|
+
if (newFailures >= this.threshold) {
|
|
226
|
+
// Open circuit
|
|
227
|
+
await this.adapter.set(this.key, {
|
|
228
|
+
failures: newFailures,
|
|
229
|
+
openedAt: Date.now(),
|
|
230
|
+
});
|
|
231
|
+
throw new Error(`Circuit breaker OPENED after ${newFailures} failures`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await this.adapter.set(this.key, { failures: newFailures });
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Usage
|
|
241
|
+
export const withCircuitBreaker = http(
|
|
242
|
+
'with-circuit-breaker',
|
|
243
|
+
{
|
|
244
|
+
connection: 'fluent_commerce',
|
|
245
|
+
},
|
|
246
|
+
async ctx => {
|
|
247
|
+
const kv = ctx.openKv(':project:');
|
|
248
|
+
const adapter = new VersoriKVAdapter(kv);
|
|
249
|
+
const breaker = new CircuitBreaker(adapter, 'fluent-api', 5, 60000);
|
|
250
|
+
|
|
251
|
+
const client = await createClient(ctx);
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const result = await breaker.execute(async () => {
|
|
255
|
+
return await client.graphql({
|
|
256
|
+
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return { success: true, data: result.data };
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
error: error instanceof Error ? error.message : 'Circuit breaker error',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Performance Optimization
|
|
274
|
+
|
|
275
|
+
### 1. Batch Processing
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { schedule } from '@versori/run';
|
|
279
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Process large datasets in batches for memory efficiency
|
|
283
|
+
*/
|
|
284
|
+
export const batchProcessor = schedule(
|
|
285
|
+
'batch-processor',
|
|
286
|
+
'0 */6 * * *',
|
|
287
|
+
{
|
|
288
|
+
connection: 'fluent_commerce',
|
|
289
|
+
},
|
|
290
|
+
async ctx => {
|
|
291
|
+
const BATCH_SIZE = 100;
|
|
292
|
+
let processed = 0;
|
|
293
|
+
let hasMore = true;
|
|
294
|
+
let cursor: string | undefined = undefined;
|
|
295
|
+
|
|
296
|
+
const client = await createClient(ctx);
|
|
297
|
+
|
|
298
|
+
while (hasMore) {
|
|
299
|
+
// Fetch batch - schema validated
|
|
300
|
+
const result = await client.graphql({
|
|
301
|
+
query: `
|
|
302
|
+
query GetInventoryBatch($first: Int!, $after: String) {
|
|
303
|
+
inventoryPositions(first: $first, after: $after) {
|
|
304
|
+
edges {
|
|
305
|
+
node {
|
|
306
|
+
id
|
|
307
|
+
ref
|
|
308
|
+
productRef
|
|
309
|
+
locationRef
|
|
310
|
+
onHand
|
|
311
|
+
}
|
|
312
|
+
cursor
|
|
313
|
+
}
|
|
314
|
+
pageInfo {
|
|
315
|
+
hasNextPage
|
|
316
|
+
endCursor
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
`,
|
|
321
|
+
variables: { first: BATCH_SIZE, after: cursor },
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const items = result.data.inventoryPositions.edges;
|
|
325
|
+
|
|
326
|
+
// Process batch
|
|
327
|
+
await processBatch(items.map(e => e.node));
|
|
328
|
+
|
|
329
|
+
processed += items.length;
|
|
330
|
+
hasMore = result.data.inventoryPositions.pageInfo.hasNextPage;
|
|
331
|
+
cursor = result.data.inventoryPositions.pageInfo.endCursor;
|
|
332
|
+
|
|
333
|
+
ctx.log('info', `Processed batch`, {
|
|
334
|
+
batchSize: items.length,
|
|
335
|
+
totalProcessed: processed,
|
|
336
|
+
hasMore,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Optional: Add small delay to avoid rate limits
|
|
340
|
+
if (hasMore) {
|
|
341
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { totalProcessed: processed };
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 2. Caching Strategy
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { http } from '@versori/run';
|
|
354
|
+
import { createClient, VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Multi-level caching: memory + KV storage
|
|
358
|
+
*/
|
|
359
|
+
const memoryCache = new Map<string, { data: any; expiresAt: number }>();
|
|
360
|
+
|
|
361
|
+
async function getCached<T>(
|
|
362
|
+
key: string,
|
|
363
|
+
fetcher: () => Promise<T>,
|
|
364
|
+
ttl: number,
|
|
365
|
+
kvAdapter: VersoriKVAdapter,
|
|
366
|
+
logger: any
|
|
367
|
+
): Promise<T> {
|
|
368
|
+
// Level 1: Memory cache (fastest)
|
|
369
|
+
const memCached = memoryCache.get(key);
|
|
370
|
+
if (memCached && memCached.expiresAt > Date.now()) {
|
|
371
|
+
logger.debug('Memory cache hit', { key });
|
|
372
|
+
return memCached.data as T;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Level 2: KV storage (fast)
|
|
376
|
+
const kvCached = await kvAdapter.get<T>(key);
|
|
377
|
+
if (kvCached) {
|
|
378
|
+
logger.debug('KV cache hit', { key });
|
|
379
|
+
// Populate memory cache
|
|
380
|
+
memoryCache.set(key, { data: kvCached, expiresAt: Date.now() + ttl });
|
|
381
|
+
return kvCached;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Level 3: Fetch from source (slow)
|
|
385
|
+
logger.debug('Cache miss - fetching from source', { key });
|
|
386
|
+
const data = await fetcher();
|
|
387
|
+
|
|
388
|
+
// Populate both caches
|
|
389
|
+
memoryCache.set(key, { data, expiresAt: Date.now() + ttl });
|
|
390
|
+
await kvAdapter.set(key, data, ttl);
|
|
391
|
+
|
|
392
|
+
return data;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export const cachedWorkflow = http(
|
|
396
|
+
'cached-workflow',
|
|
397
|
+
{
|
|
398
|
+
connection: 'fluent_commerce',
|
|
399
|
+
},
|
|
400
|
+
async ctx => {
|
|
401
|
+
const kv = ctx.openKv(':project:');
|
|
402
|
+
const adapter = new VersoriKVAdapter(kv);
|
|
403
|
+
const client = await createClient(ctx);
|
|
404
|
+
|
|
405
|
+
const products = await getCached(
|
|
406
|
+
'cache:products:list',
|
|
407
|
+
async () => {
|
|
408
|
+
const result = await client.graphql({
|
|
409
|
+
query: `
|
|
410
|
+
query GetProducts($first: Int!) {
|
|
411
|
+
products(first: $first) {
|
|
412
|
+
edges {
|
|
413
|
+
node {
|
|
414
|
+
id
|
|
415
|
+
ref
|
|
416
|
+
name
|
|
417
|
+
status
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
`,
|
|
423
|
+
variables: { first: 100 },
|
|
424
|
+
});
|
|
425
|
+
return result.data.products.edges.map(e => e.node);
|
|
426
|
+
},
|
|
427
|
+
3600000, // 1 hour
|
|
428
|
+
adapter,
|
|
429
|
+
ctx
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
return { data: products, cached: true };
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 3. Parallel Processing
|
|
438
|
+
|
|
439
|
+
**⚠️ CRITICAL**: When using Versori's `.unpack().parallel()`, remember it's **NOT fault-tolerant**. One failure stops everything. For production workloads, prefer `Promise.allSettled()`.
|
|
440
|
+
|
|
441
|
+
#### Pattern 1: Promise.all() for Independent Operations
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
export const parallelProcessor = http(
|
|
445
|
+
'parallel-processor',
|
|
446
|
+
{
|
|
447
|
+
connection: 'fluent_commerce',
|
|
448
|
+
},
|
|
449
|
+
async ctx => {
|
|
450
|
+
const client = await createClient(ctx);
|
|
451
|
+
|
|
452
|
+
// Fetch multiple resources in parallel
|
|
453
|
+
const [products, orders, inventory] = await Promise.all([
|
|
454
|
+
client.graphql({
|
|
455
|
+
query: `query { products(first: 50) { edges { node { id ref } } } }`,
|
|
456
|
+
}),
|
|
457
|
+
client.graphql({
|
|
458
|
+
query: `query { orders(first: 50) { edges { node { id ref } } } }`,
|
|
459
|
+
}),
|
|
460
|
+
client.graphql({
|
|
461
|
+
query: `query { inventoryPositions(first: 50) { edges { node { id ref } } } }`,
|
|
462
|
+
}),
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
products: products.data.products.edges.length,
|
|
467
|
+
orders: orders.data.orders.edges.length,
|
|
468
|
+
inventory: inventory.data.inventoryPositions.edges.length,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
#### Pattern 2: Promise.allSettled() for Fault Tolerance (Recommended)
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
export const faultTolerantProcessor = http(
|
|
478
|
+
'fault-tolerant-processor',
|
|
479
|
+
{
|
|
480
|
+
connection: 'fluent_commerce',
|
|
481
|
+
},
|
|
482
|
+
async ctx => {
|
|
483
|
+
const client = await createClient(ctx);
|
|
484
|
+
const files = ['file1.xml', 'file2.xml', 'file3.xml'];
|
|
485
|
+
|
|
486
|
+
// ✅ Continues even if some operations fail
|
|
487
|
+
const results = await Promise.allSettled(
|
|
488
|
+
files.map(file => processFile(file))
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const successes = results
|
|
492
|
+
.filter(r => r.status === 'fulfilled')
|
|
493
|
+
.map(r => r.value);
|
|
494
|
+
|
|
495
|
+
const failures = results
|
|
496
|
+
.filter(r => r.status === 'rejected')
|
|
497
|
+
.map(r => ({ file: r.reason.file, error: r.reason.message }));
|
|
498
|
+
|
|
499
|
+
ctx.log('info', 'Processing complete', {
|
|
500
|
+
successful: successes.length,
|
|
501
|
+
failed: failures.length,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Handle failures
|
|
505
|
+
for (const failure of failures) {
|
|
506
|
+
ctx.log('error', 'File processing failed', failure);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return { successes, failures };
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
#### Pattern 3: Versori .unpack().parallel() (Use with Caution)
|
|
515
|
+
|
|
516
|
+
**⚠️ Only use if**:
|
|
517
|
+
- All tasks **must** succeed (transactional)
|
|
518
|
+
- You wrap each task with error handling that returns error objects instead of throwing
|
|
519
|
+
- You're okay with losing all results on any failure
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// ✅ Safe usage: Handle errors inside each task
|
|
523
|
+
schedule('process-files', '0 2 * * *')
|
|
524
|
+
.then(fn('fetch-files', (ctx) => ['file1.xml', 'file2.xml', 'file3.xml']))
|
|
525
|
+
.unpack()
|
|
526
|
+
.parallel(fn('process-file', async (ctx) => {
|
|
527
|
+
try {
|
|
528
|
+
const result = await processFile(ctx.data);
|
|
529
|
+
return { success: true, file: ctx.data, result };
|
|
530
|
+
} catch (error) {
|
|
531
|
+
// ✅ Return error object instead of throwing
|
|
532
|
+
return {
|
|
533
|
+
success: false,
|
|
534
|
+
file: ctx.data,
|
|
535
|
+
error: error.message
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}))
|
|
539
|
+
.then(fn('process-results', (ctx) => {
|
|
540
|
+
// ✅ Now you have all results (success + failures)
|
|
541
|
+
const results = ctx.data;
|
|
542
|
+
const successes = results.filter(r => r.success);
|
|
543
|
+
const failures = results.filter(r => !r.success);
|
|
544
|
+
|
|
545
|
+
ctx.log.info(`Processed ${successes.length} files, ${failures.length} failed`);
|
|
546
|
+
return { successes, failures };
|
|
547
|
+
}));
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**See**: [Module 4: Workflows - Parallel Processing](./platforms-versori-04-workflows.md#-critical-parallel-processing-with-unpack-and-parallel) for detailed guidance.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## Security Hardening
|
|
555
|
+
|
|
556
|
+
### 1. Input Validation
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
import { webhook } from '@versori/run';
|
|
560
|
+
|
|
561
|
+
// Validation schemas
|
|
562
|
+
const orderSchema = {
|
|
563
|
+
orderId: { type: 'string', required: true, pattern: /^ORD-\d{6}$/ },
|
|
564
|
+
customerId: { type: 'string', required: true },
|
|
565
|
+
items: { type: 'array', required: true, minLength: 1 },
|
|
566
|
+
totalPrice: { type: 'number', required: true, min: 0 },
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
function validateInput(data: any, schema: any): { valid: boolean; errors: string[] } {
|
|
570
|
+
const errors: string[] = [];
|
|
571
|
+
|
|
572
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
573
|
+
const value = data[field];
|
|
574
|
+
|
|
575
|
+
// Required check
|
|
576
|
+
if (rules.required && (value === undefined || value === null)) {
|
|
577
|
+
errors.push(`Missing required field: ${field}`);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Type check
|
|
582
|
+
if (value !== undefined && rules.type) {
|
|
583
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
584
|
+
if (actualType !== rules.type) {
|
|
585
|
+
errors.push(`Invalid type for ${field}: expected ${rules.type}, got ${actualType}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Pattern check
|
|
590
|
+
if (value && rules.pattern && !rules.pattern.test(value)) {
|
|
591
|
+
errors.push(`Invalid format for ${field}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Min/max checks
|
|
595
|
+
if (typeof value === 'number') {
|
|
596
|
+
if (rules.min !== undefined && value < rules.min) {
|
|
597
|
+
errors.push(`${field} must be >= ${rules.min}`);
|
|
598
|
+
}
|
|
599
|
+
if (rules.max !== undefined && value > rules.max) {
|
|
600
|
+
errors.push(`${field} must be <= ${rules.max}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Array length checks
|
|
605
|
+
if (Array.isArray(value)) {
|
|
606
|
+
if (rules.minLength !== undefined && value.length < rules.minLength) {
|
|
607
|
+
errors.push(`${field} must have at least ${rules.minLength} items`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return { valid: errors.length === 0, errors };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export const validatedWebhook = webhook('validated-webhook', async ctx => {
|
|
616
|
+
// Validate input
|
|
617
|
+
const validation = validateInput(ctx.data, orderSchema);
|
|
618
|
+
|
|
619
|
+
if (!validation.valid) {
|
|
620
|
+
ctx.log('warn', 'Validation failed', { errors: validation.errors });
|
|
621
|
+
return {
|
|
622
|
+
success: false,
|
|
623
|
+
errors: validation.errors,
|
|
624
|
+
status: 400,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Process validated data
|
|
629
|
+
ctx.log('info', 'Order validated successfully', { orderId: ctx.data.orderId });
|
|
630
|
+
|
|
631
|
+
return { success: true, orderId: ctx.data.orderId };
|
|
632
|
+
});
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### 2. Rate Limiting
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
import { fn } from '@versori/run';
|
|
639
|
+
import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
640
|
+
|
|
641
|
+
async function checkRateLimit(
|
|
642
|
+
adapter: VersoriKVAdapter,
|
|
643
|
+
identifier: string,
|
|
644
|
+
maxRequests: number,
|
|
645
|
+
windowMs: number
|
|
646
|
+
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
|
|
647
|
+
const key = `ratelimit:${identifier}`;
|
|
648
|
+
const now = Date.now();
|
|
649
|
+
|
|
650
|
+
// Get current count
|
|
651
|
+
const current = await adapter.get<{ count: number; resetAt: number }>(key);
|
|
652
|
+
|
|
653
|
+
if (!current) {
|
|
654
|
+
// First request
|
|
655
|
+
const resetAt = now + windowMs;
|
|
656
|
+
await adapter.set(key, { count: 1, resetAt }, windowMs);
|
|
657
|
+
return { allowed: true, remaining: maxRequests - 1, resetAt };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (now > current.resetAt) {
|
|
661
|
+
// Window expired, reset
|
|
662
|
+
const resetAt = now + windowMs;
|
|
663
|
+
await adapter.set(key, { count: 1, resetAt }, windowMs);
|
|
664
|
+
return { allowed: true, remaining: maxRequests - 1, resetAt };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (current.count >= maxRequests) {
|
|
668
|
+
// Rate limit exceeded
|
|
669
|
+
return { allowed: false, remaining: 0, resetAt: current.resetAt };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Increment count
|
|
673
|
+
await adapter.set(key, { count: current.count + 1, resetAt: current.resetAt }, windowMs);
|
|
674
|
+
return { allowed: true, remaining: maxRequests - current.count - 1, resetAt: current.resetAt };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export const rateLimitedEndpoint = http(
|
|
678
|
+
'rate-limited',
|
|
679
|
+
{
|
|
680
|
+
connection: 'fluent_commerce',
|
|
681
|
+
},
|
|
682
|
+
async ctx => {
|
|
683
|
+
const kv = ctx.openKv(':project:');
|
|
684
|
+
const adapter = new VersoriKVAdapter(kv);
|
|
685
|
+
|
|
686
|
+
const clientId = ctx.query?.clientId || 'anonymous';
|
|
687
|
+
const rateLimit = await checkRateLimit(adapter, clientId, 100, 3600000); // 100 req/hour
|
|
688
|
+
|
|
689
|
+
if (!rateLimit.allowed) {
|
|
690
|
+
const retryAfter = Math.ceil((rateLimit.resetAt - Date.now()) / 1000);
|
|
691
|
+
ctx.log('warn', 'Rate limit exceeded', { clientId, retryAfter });
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
error: 'Rate limit exceeded',
|
|
695
|
+
retryAfter,
|
|
696
|
+
resetAt: new Date(rateLimit.resetAt).toISOString(),
|
|
697
|
+
status: 429,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Process request
|
|
702
|
+
const client = await createClient(ctx);
|
|
703
|
+
const result = await client.graphql({
|
|
704
|
+
query: `query { products(first: 10) { edges { node { id ref } } } }`,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
return {
|
|
708
|
+
data: result.data,
|
|
709
|
+
rateLimit: {
|
|
710
|
+
remaining: rateLimit.remaining,
|
|
711
|
+
resetAt: new Date(rateLimit.resetAt).toISOString(),
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### 3. Secrets Management
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
// ❌ NEVER do this
|
|
722
|
+
const BAD_EXAMPLE = {
|
|
723
|
+
clientId: 'my-client-id',
|
|
724
|
+
clientSecret: 'my-secret', // NEVER hardcode secrets!
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// ✅ Use connector variables
|
|
728
|
+
export const secureWorkflow = http(
|
|
729
|
+
'secure',
|
|
730
|
+
{
|
|
731
|
+
connection: 'fluent_commerce',
|
|
732
|
+
},
|
|
733
|
+
async ctx => {
|
|
734
|
+
// Access secrets from connector variables
|
|
735
|
+
const webhookKey = ctx.vars?.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
736
|
+
const apiKey = ctx.vars?.THIRD_PARTY_API_KEY;
|
|
737
|
+
|
|
738
|
+
// Validate secrets exist
|
|
739
|
+
if (!webhookKey || !apiKey) {
|
|
740
|
+
ctx.log('error', 'Missing required secrets');
|
|
741
|
+
return { error: 'Configuration error', status: 500 };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Use secrets securely (don't log them)
|
|
745
|
+
ctx.log('info', 'Secrets loaded', {
|
|
746
|
+
hasWebhookKey: !!webhookKey,
|
|
747
|
+
hasApiKey: !!apiKey,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// ... use secrets
|
|
751
|
+
}
|
|
752
|
+
);
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Cost Optimization
|
|
758
|
+
|
|
759
|
+
### 1. Minimize KV Operations
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
// ❌ Expensive - sequential writes without atomicity
|
|
763
|
+
for (const item of items) {
|
|
764
|
+
await kv.set(`item:${item.id}`, item); // N sequential operations
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// ✅ Optimized - use atomic operations for batch writes
|
|
768
|
+
// Note: VersoriKVAdapter doesn't have mset() method - use atomic() instead
|
|
769
|
+
const adapter = new VersoriKVAdapter(kv);
|
|
770
|
+
const atomic = adapter.atomic();
|
|
771
|
+
for (const item of items) {
|
|
772
|
+
atomic.set(['item', item.id], item);
|
|
773
|
+
}
|
|
774
|
+
await atomic.commit(); // 1 atomic transaction - all succeed or all fail
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### 2. Optimize Cron Schedules
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
// ❌ Too frequent - runs every minute (1440 times/day)
|
|
781
|
+
schedule('too-frequent', '* * * * *', handler);
|
|
782
|
+
|
|
783
|
+
// ✅ Optimized - runs every 6 hours (4 times/day)
|
|
784
|
+
schedule('optimized', '0 */6 * * *', handler);
|
|
785
|
+
|
|
786
|
+
// ❌ Overlapping schedules
|
|
787
|
+
schedule('sync-1', '0 * * * *', syncHandler); // Every hour
|
|
788
|
+
schedule('sync-2', '30 * * * *', syncHandler); // Every hour at :30
|
|
789
|
+
|
|
790
|
+
// ✅ Consolidated schedule
|
|
791
|
+
schedule('unified-sync', '0 */2 * * *', async ctx => {
|
|
792
|
+
await syncHandler(ctx);
|
|
793
|
+
// Do both operations in one run
|
|
794
|
+
});
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### 3. Cache Expensive Operations
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
export const costOptimized = http(
|
|
801
|
+
'cost-optimized',
|
|
802
|
+
{
|
|
803
|
+
connection: 'fluent_commerce',
|
|
804
|
+
},
|
|
805
|
+
async ctx => {
|
|
806
|
+
const kv = ctx.openKv(':project:');
|
|
807
|
+
const adapter = new VersoriKVAdapter(kv);
|
|
808
|
+
|
|
809
|
+
// Check cache first
|
|
810
|
+
const cacheKey = 'expensive:operation:result';
|
|
811
|
+
const cached = await adapter.get(cacheKey);
|
|
812
|
+
|
|
813
|
+
if (cached) {
|
|
814
|
+
ctx.log('info', 'Returning cached result');
|
|
815
|
+
return { data: cached, source: 'cache' };
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Expensive operation
|
|
819
|
+
const client = await createClient(ctx);
|
|
820
|
+
const result = await client.graphql({
|
|
821
|
+
query: `... complex query ...`,
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// Cache for 1 hour
|
|
825
|
+
await adapter.set(cacheKey, result.data, 3600000);
|
|
826
|
+
|
|
827
|
+
return { data: result.data, source: 'api' };
|
|
828
|
+
}
|
|
829
|
+
);
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## Monitoring & Alerting
|
|
835
|
+
|
|
836
|
+
### Critical Metrics Tracking
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
import { schedule } from '@versori/run';
|
|
840
|
+
import { VersoriKVAdapter } from '@fluentcommerce/fc-connect-sdk';
|
|
841
|
+
|
|
842
|
+
export const metricsCollector = schedule('metrics-collector', '*/5 * * * *', async ctx => {
|
|
843
|
+
const kv = ctx.openKv(':project:');
|
|
844
|
+
const adapter = new VersoriKVAdapter(kv);
|
|
845
|
+
|
|
846
|
+
// Note: VersoriKVAdapter doesn't have keys() method - use native Versori KV list() instead
|
|
847
|
+
const allKeys = await kv.list();
|
|
848
|
+
const processedFileKeys = await kv.list({ prefix: 'files:processed:' });
|
|
849
|
+
|
|
850
|
+
// Collect metrics
|
|
851
|
+
const metrics = {
|
|
852
|
+
timestamp: Date.now(),
|
|
853
|
+
workflows: {
|
|
854
|
+
totalExecutions: (await adapter.get(['metrics', 'total', 'executions'])) || 0,
|
|
855
|
+
failedExecutions: (await adapter.get(['metrics', 'failed', 'executions'])) || 0,
|
|
856
|
+
avgDuration: (await adapter.get(['metrics', 'avg', 'duration'])) || 0,
|
|
857
|
+
},
|
|
858
|
+
storage: {
|
|
859
|
+
kvKeys: allKeys.length,
|
|
860
|
+
processedFiles: processedFileKeys.length,
|
|
861
|
+
},
|
|
862
|
+
health: {
|
|
863
|
+
lastHealthCheck: await adapter.get(['health', 'last', 'check']),
|
|
864
|
+
fluentApiStatus: await adapter.get(['health', 'fluent', 'status']),
|
|
865
|
+
},
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
// Calculate derived metrics
|
|
869
|
+
const successRate =
|
|
870
|
+
metrics.workflows.totalExecutions > 0
|
|
871
|
+
? (1 - metrics.workflows.failedExecutions / metrics.workflows.totalExecutions) * 100
|
|
872
|
+
: 100;
|
|
873
|
+
|
|
874
|
+
// Store metrics history
|
|
875
|
+
const metricsHistory = (await adapter.get(['metrics', 'history'])) || [];
|
|
876
|
+
metricsHistory.push({ ...metrics, successRate });
|
|
877
|
+
|
|
878
|
+
// Keep last 288 entries (24 hours at 5-minute intervals)
|
|
879
|
+
if (metricsHistory.length > 288) {
|
|
880
|
+
metricsHistory.shift();
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
await adapter.set(['metrics', 'history'], metricsHistory);
|
|
884
|
+
|
|
885
|
+
// Alert on critical thresholds
|
|
886
|
+
if (successRate < 95) {
|
|
887
|
+
ctx.log('error', 'ALERT: Success rate below threshold', {
|
|
888
|
+
successRate: successRate.toFixed(2),
|
|
889
|
+
threshold: 95,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (metrics.workflows.avgDuration > 10000) {
|
|
894
|
+
ctx.log('warn', 'ALERT: Average duration above threshold', {
|
|
895
|
+
avgDuration: metrics.workflows.avgDuration,
|
|
896
|
+
threshold: 10000,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
ctx.log('info', 'Metrics collected', {
|
|
901
|
+
successRate: successRate.toFixed(2),
|
|
902
|
+
avgDuration: metrics.workflows.avgDuration,
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
return metrics;
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
---
|
|
910
|
+
|
|
911
|
+
## Troubleshooting Guide
|
|
912
|
+
|
|
913
|
+
### Common Issues & Solutions
|
|
914
|
+
|
|
915
|
+
#### Issue 1: "No authentication credentials found"
|
|
916
|
+
|
|
917
|
+
**Symptoms**: Error when calling Fluent API
|
|
918
|
+
|
|
919
|
+
**Cause**: Using `fn()` instead of `http()` for external API calls
|
|
920
|
+
|
|
921
|
+
**Solution**:
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
// ❌ Wrong
|
|
925
|
+
export const bad = fn('bad', async ctx => {
|
|
926
|
+
const client = await createClient(ctx); // FAILS - no ctx.fetch
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// ✅ Correct
|
|
930
|
+
export const good = http(
|
|
931
|
+
'good',
|
|
932
|
+
{
|
|
933
|
+
connection: 'fluent_commerce',
|
|
934
|
+
},
|
|
935
|
+
async ctx => {
|
|
936
|
+
const client = await createClient(ctx); // Works
|
|
937
|
+
}
|
|
938
|
+
);
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
#### Issue 2: Duplicate File Processing
|
|
942
|
+
|
|
943
|
+
**Symptoms**: Same file processed multiple times
|
|
944
|
+
|
|
945
|
+
**Cause**: Missing or incorrect file tracking
|
|
946
|
+
|
|
947
|
+
**Solution**:
|
|
948
|
+
|
|
949
|
+
```typescript
|
|
950
|
+
import { VersoriFileTracker } from '@fluentcommerce/fc-connect-sdk';
|
|
951
|
+
|
|
952
|
+
const kv = ctx.openKv(':project:');
|
|
953
|
+
const tracker = new VersoriFileTracker(kv, 'namespace');
|
|
954
|
+
|
|
955
|
+
// Check before processing
|
|
956
|
+
if (await tracker.wasFileProcessed(fileName)) {
|
|
957
|
+
return { skipped: true };
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Process file...
|
|
961
|
+
|
|
962
|
+
// Mark as processed
|
|
963
|
+
await tracker.markFileProcessed(fileName, { records: count });
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
#### Issue 3: Memory Errors with Large Datasets
|
|
967
|
+
|
|
968
|
+
**Symptoms**: Out of memory errors
|
|
969
|
+
|
|
970
|
+
**Cause**: Loading entire dataset into memory
|
|
971
|
+
|
|
972
|
+
**Solution**:
|
|
973
|
+
|
|
974
|
+
```typescript
|
|
975
|
+
// ❌ Wrong - loads everything
|
|
976
|
+
const allData = await fetchAllData();
|
|
977
|
+
await processData(allData);
|
|
978
|
+
|
|
979
|
+
// ✅ Correct - batch processing
|
|
980
|
+
const BATCH_SIZE = 100;
|
|
981
|
+
let cursor = undefined;
|
|
982
|
+
let hasMore = true;
|
|
983
|
+
|
|
984
|
+
while (hasMore) {
|
|
985
|
+
const batch = await fetchBatch(BATCH_SIZE, cursor);
|
|
986
|
+
await processBatch(batch);
|
|
987
|
+
hasMore = batch.pageInfo.hasNextPage;
|
|
988
|
+
cursor = batch.pageInfo.endCursor;
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
---
|
|
993
|
+
|
|
994
|
+
## Practice Solutions
|
|
995
|
+
|
|
996
|
+
### Module 2: Quick Start Exercise Solution
|
|
997
|
+
|
|
998
|
+
```typescript
|
|
999
|
+
import { http } from '@versori/run';
|
|
1000
|
+
import { createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Create inventory position
|
|
1004
|
+
* POST https://{workspace}.versori.run/create-inventory
|
|
1005
|
+
* Body: { productRef, locationRef, qty }
|
|
1006
|
+
*/
|
|
1007
|
+
export const createInventoryPosition = http(
|
|
1008
|
+
'create-inventory',
|
|
1009
|
+
{
|
|
1010
|
+
connection: 'fluent_commerce',
|
|
1011
|
+
},
|
|
1012
|
+
async ctx => {
|
|
1013
|
+
ctx.log('info', 'Creating inventory position');
|
|
1014
|
+
|
|
1015
|
+
const { productRef, locationRef, qty } = ctx.data || {};
|
|
1016
|
+
|
|
1017
|
+
// Validation
|
|
1018
|
+
if (!productRef || !locationRef || qty === undefined) {
|
|
1019
|
+
return {
|
|
1020
|
+
success: false,
|
|
1021
|
+
error: 'Missing required fields: productRef, locationRef, qty',
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
try {
|
|
1026
|
+
const client = await createClient(ctx);
|
|
1027
|
+
|
|
1028
|
+
const result = await client.graphql({
|
|
1029
|
+
query: `
|
|
1030
|
+
mutation CreateInventory($input: CreateInventoryPositionInput!) {
|
|
1031
|
+
createInventoryPosition(input: $input) {
|
|
1032
|
+
id
|
|
1033
|
+
ref
|
|
1034
|
+
productRef
|
|
1035
|
+
locationRef
|
|
1036
|
+
onHand
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
`,
|
|
1040
|
+
variables: {
|
|
1041
|
+
input: { productRef, locationRef, qty },
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
if (result.errors?.length) {
|
|
1046
|
+
throw new Error(result.errors[0].message);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
ctx.log('info', 'Inventory position created', {
|
|
1050
|
+
id: result.data.createInventoryPosition.id,
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
return {
|
|
1054
|
+
success: true,
|
|
1055
|
+
data: result.data.createInventoryPosition,
|
|
1056
|
+
};
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
ctx.log('error', 'Failed to create inventory position', {
|
|
1059
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
return {
|
|
1063
|
+
success: false,
|
|
1064
|
+
error: error instanceof Error ? error.message : 'Creation failed',
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
);
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
## Key Takeaways
|
|
1074
|
+
|
|
1075
|
+
- 🎯 **Error handling** with comprehensive try-catch, retry, and circuit breaker patterns
|
|
1076
|
+
- 🎯 **Performance** via batching, caching, and parallel processing
|
|
1077
|
+
- 🎯 **Security** through input validation, rate limiting, and secrets management
|
|
1078
|
+
- 🎯 **Cost optimization** by minimizing KV ops, optimizing schedules, and caching
|
|
1079
|
+
- 🎯 **Monitoring** with metrics tracking and alerting on critical thresholds
|
|
1080
|
+
- 🎯 **Troubleshooting** using structured debugging and common issue patterns
|
|
1081
|
+
|
|
1082
|
+
---
|
|
1083
|
+
|
|
1084
|
+
## Conclusion
|
|
1085
|
+
|
|
1086
|
+
You've completed the Versori Platform Integration Guide! You now have:
|
|
1087
|
+
|
|
1088
|
+
- ✅ Understanding of Versori platform architecture
|
|
1089
|
+
- ✅ Ability to create all workflow types (http, webhook, schedule, fn)
|
|
1090
|
+
- ✅ Connection management expertise
|
|
1091
|
+
- ✅ KV storage and state management skills
|
|
1092
|
+
- ✅ Deployment and CI/CD knowledge
|
|
1093
|
+
- ✅ Production-ready patterns and best practices
|
|
1094
|
+
|
|
1095
|
+
### Next Steps
|
|
1096
|
+
|
|
1097
|
+
1. **Build Your Integration**: Apply these patterns to your specific use case
|
|
1098
|
+
2. **Review Examples**: Study the connector examples in `connectors/` directory
|
|
1099
|
+
3. **Consult SDK Guides**: Deep dive into specific SDK features
|
|
1100
|
+
4. **Join Community**: Engage with other developers using Versori
|
|
1101
|
+
|
|
1102
|
+
### Additional Resources
|
|
1103
|
+
|
|
1104
|
+
- [Versori Platform Documentation](https://docs.versori.com)
|
|
1105
|
+
- [FC Connect SDK API Reference](../../../../02-CORE-GUIDES/api-reference/api-reference-readme.md)
|
|
1106
|
+
- [Sample Connectors](../../../../01-TEMPLATES/versori/workflows/readme.md) - Production connector examples
|
|
1107
|
+
- [Webhook Response Patterns](.././modules/platforms-versori-04-workflows.md#critical-non-json-response-handlers-xml-html-csv)
|
|
1108
|
+
|
|
1109
|
+
---
|
|
1110
|
+
|
|
1111
|
+
[← Previous: Module 7](./platforms-versori-07-deployment.md) | [Back to Guide](../platforms-versori-readme.md)
|