@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +11 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,728 +1,728 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error Recovery and Resilience Example
|
|
3
|
-
*
|
|
4
|
-
* Comprehensive error handling strategies for production-ready ingestion:
|
|
5
|
-
* - Exponential backoff retry logic
|
|
6
|
-
* - Circuit breaker pattern
|
|
7
|
-
* - Partial failure handling
|
|
8
|
-
* - Dead letter queue (DLQ)
|
|
9
|
-
* - Graceful degradation
|
|
10
|
-
* - Error categorization and logging
|
|
11
|
-
*
|
|
12
|
-
* Prerequisites:
|
|
13
|
-
* - npm install @fluentcommerce/fc-connect-sdk
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* npx tsx examples/error-recovery.ts
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
createClient,
|
|
21
|
-
S3DataSource,
|
|
22
|
-
CSVParserService,
|
|
23
|
-
UniversalMapper,
|
|
24
|
-
StateService,
|
|
25
|
-
SimpleKVStore,
|
|
26
|
-
FluentClient,
|
|
27
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
28
|
-
|
|
29
|
-
// ============================================================================
|
|
30
|
-
// Configuration
|
|
31
|
-
// ============================================================================
|
|
32
|
-
|
|
33
|
-
const config = {
|
|
34
|
-
fluent: {
|
|
35
|
-
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
36
|
-
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
37
|
-
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
38
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
39
|
-
},
|
|
40
|
-
s3: {
|
|
41
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
42
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
43
|
-
region: process.env.AWS_REGION || 'us-east-1',
|
|
44
|
-
},
|
|
45
|
-
retry: {
|
|
46
|
-
maxRetries: 3,
|
|
47
|
-
initialDelayMs: 1000,
|
|
48
|
-
maxDelayMs: 30000,
|
|
49
|
-
backoffMultiplier: 2,
|
|
50
|
-
},
|
|
51
|
-
circuitBreaker: {
|
|
52
|
-
failureThreshold: 5,
|
|
53
|
-
resetTimeoutMs: 60000,
|
|
54
|
-
},
|
|
55
|
-
deadLetterQueue: {
|
|
56
|
-
bucket: 'dlq-bucket',
|
|
57
|
-
prefix: 'failed-ingestion/',
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const mappingConfig = {
|
|
62
|
-
fields: {
|
|
63
|
-
ref: { source: 'sku', required: true },
|
|
64
|
-
productRef: { source: 'sku', required: true },
|
|
65
|
-
locationRef: { source: 'location', required: true },
|
|
66
|
-
qty: { source: 'quantity', resolver: 'sdk.parseInt', required: true },
|
|
67
|
-
status: { source: 'status', resolver: 'sdk.uppercase', defaultValue: 'AVAILABLE' },
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// Error Types & Classification
|
|
73
|
-
// ============================================================================
|
|
74
|
-
|
|
75
|
-
enum ErrorCategory {
|
|
76
|
-
TRANSIENT = 'TRANSIENT', // Retry possible (network, rate limit)
|
|
77
|
-
VALIDATION = 'VALIDATION', // Data quality issue (skip/dlq)
|
|
78
|
-
PERMANENT = 'PERMANENT', // Cannot be retried (auth, config)
|
|
79
|
-
UNKNOWN = 'UNKNOWN', // Unclear, safe to retry
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface ClassifiedError {
|
|
83
|
-
category: ErrorCategory;
|
|
84
|
-
message: string;
|
|
85
|
-
retryable: boolean;
|
|
86
|
-
originalError: Error;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Classify error to determine retry strategy
|
|
91
|
-
*/
|
|
92
|
-
function classifyError(error: any): ClassifiedError {
|
|
93
|
-
const message = error.message || String(error);
|
|
94
|
-
|
|
95
|
-
// Transient errors - safe to retry
|
|
96
|
-
if (
|
|
97
|
-
message.includes('ECONNRESET') ||
|
|
98
|
-
message.includes('ETIMEDOUT') ||
|
|
99
|
-
message.includes('ENOTFOUND') ||
|
|
100
|
-
message.includes('Network error') ||
|
|
101
|
-
message.includes('Rate limit') ||
|
|
102
|
-
message.includes('429')
|
|
103
|
-
) {
|
|
104
|
-
return {
|
|
105
|
-
category: ErrorCategory.TRANSIENT,
|
|
106
|
-
message: 'Network or rate limit error',
|
|
107
|
-
retryable: true,
|
|
108
|
-
originalError: error,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Validation errors - data quality issues
|
|
113
|
-
if (
|
|
114
|
-
message.includes('Validation failed') ||
|
|
115
|
-
message.includes('Invalid field') ||
|
|
116
|
-
message.includes('Required field missing') ||
|
|
117
|
-
message.includes('Schema mismatch')
|
|
118
|
-
) {
|
|
119
|
-
return {
|
|
120
|
-
category: ErrorCategory.VALIDATION,
|
|
121
|
-
message: 'Data validation error',
|
|
122
|
-
retryable: false,
|
|
123
|
-
originalError: error,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Permanent errors - configuration/auth issues
|
|
128
|
-
if (
|
|
129
|
-
message.includes('Unauthorized') ||
|
|
130
|
-
message.includes('Authentication failed') ||
|
|
131
|
-
message.includes('Access denied') ||
|
|
132
|
-
message.includes('Invalid credentials') ||
|
|
133
|
-
message.includes('401') ||
|
|
134
|
-
message.includes('403')
|
|
135
|
-
) {
|
|
136
|
-
return {
|
|
137
|
-
category: ErrorCategory.PERMANENT,
|
|
138
|
-
message: 'Authentication or authorization error',
|
|
139
|
-
retryable: false,
|
|
140
|
-
originalError: error,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Unknown errors - default to retryable
|
|
145
|
-
return {
|
|
146
|
-
category: ErrorCategory.UNKNOWN,
|
|
147
|
-
message: 'Unknown error',
|
|
148
|
-
retryable: true,
|
|
149
|
-
originalError: error,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// Retry Logic with Exponential Backoff
|
|
155
|
-
// ============================================================================
|
|
156
|
-
|
|
157
|
-
interface RetryOptions {
|
|
158
|
-
maxRetries?: number;
|
|
159
|
-
initialDelayMs?: number;
|
|
160
|
-
maxDelayMs?: number;
|
|
161
|
-
backoffMultiplier?: number;
|
|
162
|
-
onRetry?: (attempt: number, delay: number, error: Error) => void;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Retry operation with exponential backoff
|
|
167
|
-
*/
|
|
168
|
-
async function retryWithBackoff<T>(
|
|
169
|
-
operation: () => Promise<T>,
|
|
170
|
-
options: RetryOptions = {}
|
|
171
|
-
): Promise<T> {
|
|
172
|
-
const {
|
|
173
|
-
maxRetries = config.retry.maxRetries,
|
|
174
|
-
initialDelayMs = config.retry.initialDelayMs,
|
|
175
|
-
maxDelayMs = config.retry.maxDelayMs,
|
|
176
|
-
backoffMultiplier = config.retry.backoffMultiplier,
|
|
177
|
-
onRetry,
|
|
178
|
-
} = options;
|
|
179
|
-
|
|
180
|
-
let lastError: Error;
|
|
181
|
-
let delay = initialDelayMs;
|
|
182
|
-
|
|
183
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
184
|
-
try {
|
|
185
|
-
return await operation();
|
|
186
|
-
} catch (error: any) {
|
|
187
|
-
lastError = error;
|
|
188
|
-
|
|
189
|
-
// Classify error to determine if retry is appropriate
|
|
190
|
-
const classified = classifyError(error);
|
|
191
|
-
|
|
192
|
-
if (!classified.retryable) {
|
|
193
|
-
console.error(`❌ Non-retryable error (${classified.category}):`, error.message);
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (attempt === maxRetries) {
|
|
198
|
-
console.error(`❌ Max retries (${maxRetries}) exceeded`);
|
|
199
|
-
throw error;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Calculate next delay with exponential backoff
|
|
203
|
-
const nextDelay = Math.min(delay, maxDelayMs);
|
|
204
|
-
|
|
205
|
-
console.log(`⚠️ Attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
206
|
-
console.log(` Retrying in ${nextDelay}ms...`);
|
|
207
|
-
|
|
208
|
-
if (onRetry) {
|
|
209
|
-
onRetry(attempt, nextDelay, error);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
await sleep(nextDelay);
|
|
213
|
-
delay *= backoffMultiplier;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
throw lastError!;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Sleep utility
|
|
222
|
-
*/
|
|
223
|
-
function sleep(ms: number): Promise<void> {
|
|
224
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ============================================================================
|
|
228
|
-
// Circuit Breaker Pattern
|
|
229
|
-
// ============================================================================
|
|
230
|
-
|
|
231
|
-
enum CircuitState {
|
|
232
|
-
CLOSED = 'CLOSED', // Normal operation
|
|
233
|
-
OPEN = 'OPEN', // Failing, reject immediately
|
|
234
|
-
HALF_OPEN = 'HALF_OPEN', // Testing if service recovered
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
class CircuitBreaker {
|
|
238
|
-
private state: CircuitState = CircuitState.CLOSED;
|
|
239
|
-
private failureCount = 0;
|
|
240
|
-
private lastFailureTime: number = 0;
|
|
241
|
-
private successCount = 0;
|
|
242
|
-
|
|
243
|
-
constructor(
|
|
244
|
-
private failureThreshold: number = config.circuitBreaker.failureThreshold,
|
|
245
|
-
private resetTimeoutMs: number = config.circuitBreaker.resetTimeoutMs
|
|
246
|
-
) {}
|
|
247
|
-
|
|
248
|
-
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
249
|
-
// Check if circuit should transition from OPEN to HALF_OPEN
|
|
250
|
-
if (this.state === CircuitState.OPEN) {
|
|
251
|
-
const timeSinceLastFailure = Date.now() - this.lastFailureTime;
|
|
252
|
-
|
|
253
|
-
if (timeSinceLastFailure >= this.resetTimeoutMs) {
|
|
254
|
-
console.log('🔄 Circuit breaker transitioning to HALF_OPEN (testing)');
|
|
255
|
-
this.state = CircuitState.HALF_OPEN;
|
|
256
|
-
this.successCount = 0;
|
|
257
|
-
} else {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`Circuit breaker is OPEN (wait ${Math.ceil((this.resetTimeoutMs - timeSinceLastFailure) / 1000)}s)`
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
const result = await operation();
|
|
266
|
-
|
|
267
|
-
// Success - record it
|
|
268
|
-
this.onSuccess();
|
|
269
|
-
|
|
270
|
-
return result;
|
|
271
|
-
} catch (error) {
|
|
272
|
-
// Failure - record it
|
|
273
|
-
this.onFailure();
|
|
274
|
-
|
|
275
|
-
throw error;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private onSuccess(): void {
|
|
280
|
-
this.failureCount = 0;
|
|
281
|
-
|
|
282
|
-
if (this.state === CircuitState.HALF_OPEN) {
|
|
283
|
-
this.successCount++;
|
|
284
|
-
|
|
285
|
-
// After 3 successes in HALF_OPEN, transition to CLOSED
|
|
286
|
-
if (this.successCount >= 3) {
|
|
287
|
-
console.log('✅ Circuit breaker closing (service recovered)');
|
|
288
|
-
this.state = CircuitState.CLOSED;
|
|
289
|
-
this.successCount = 0;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private onFailure(): void {
|
|
295
|
-
this.failureCount++;
|
|
296
|
-
this.lastFailureTime = Date.now();
|
|
297
|
-
|
|
298
|
-
if (this.failureCount >= this.failureThreshold) {
|
|
299
|
-
console.log(`🚫 Circuit breaker opening (${this.failureCount} failures)`);
|
|
300
|
-
this.state = CircuitState.OPEN;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
getState(): CircuitState {
|
|
305
|
-
return this.state;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// ============================================================================
|
|
310
|
-
// Dead Letter Queue (DLQ)
|
|
311
|
-
// ============================================================================
|
|
312
|
-
|
|
313
|
-
class DeadLetterQueue {
|
|
314
|
-
constructor(
|
|
315
|
-
private s3: S3DataSource,
|
|
316
|
-
private bucket: string = config.deadLetterQueue.bucket,
|
|
317
|
-
private prefix: string = config.deadLetterQueue.prefix
|
|
318
|
-
) {}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Send failed record to DLQ for manual review
|
|
322
|
-
*/
|
|
323
|
-
async sendToDLQ(
|
|
324
|
-
record: any,
|
|
325
|
-
error: ClassifiedError,
|
|
326
|
-
metadata: {
|
|
327
|
-
originalFile?: string;
|
|
328
|
-
attempt?: number;
|
|
329
|
-
timestamp?: string;
|
|
330
|
-
} = {}
|
|
331
|
-
): Promise<void> {
|
|
332
|
-
const dlqEntry = {
|
|
333
|
-
record,
|
|
334
|
-
error: {
|
|
335
|
-
category: error.category,
|
|
336
|
-
message: error.message,
|
|
337
|
-
stack: error.originalError.stack,
|
|
338
|
-
},
|
|
339
|
-
metadata: {
|
|
340
|
-
...metadata,
|
|
341
|
-
timestamp: metadata.timestamp || new Date().toISOString(),
|
|
342
|
-
dlqTimestamp: new Date().toISOString(),
|
|
343
|
-
},
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const key = `${this.prefix}${Date.now()}-${Math.random().toString(36).substr(2, 9)}.json`;
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
await this.s3.putObject(this.bucket, key, Buffer.from(JSON.stringify(dlqEntry, null, 2)));
|
|
350
|
-
|
|
351
|
-
console.log(`📮 Sent to DLQ: s3://${this.bucket}/${key}`);
|
|
352
|
-
} catch (dlqError) {
|
|
353
|
-
console.error('❌ Failed to send to DLQ:', dlqError);
|
|
354
|
-
// Log to local file as fallback
|
|
355
|
-
console.error('DLQ Entry:', JSON.stringify(dlqEntry));
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Retrieve failed records from DLQ for reprocessing
|
|
361
|
-
*/
|
|
362
|
-
async retrieveFromDLQ(limit: number = 100): Promise<any[]> {
|
|
363
|
-
const files = await this.s3.listFiles({ prefix: this.prefix });
|
|
364
|
-
|
|
365
|
-
const entries = [];
|
|
366
|
-
|
|
367
|
-
for (const file of files.slice(0, limit)) {
|
|
368
|
-
try {
|
|
369
|
-
const data = await this.s3.downloadFile(file.path);
|
|
370
|
-
const entry = JSON.parse(data.toString());
|
|
371
|
-
entries.push(entry);
|
|
372
|
-
} catch (error) {
|
|
373
|
-
console.error(`Failed to read DLQ entry ${file.path}:`, error);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return entries;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// ============================================================================
|
|
382
|
-
// Resilient Ingestion Workflow
|
|
383
|
-
// ============================================================================
|
|
384
|
-
|
|
385
|
-
interface IngestionResult {
|
|
386
|
-
totalFiles: number;
|
|
387
|
-
processedFiles: number;
|
|
388
|
-
failedFiles: number;
|
|
389
|
-
totalRecords: number;
|
|
390
|
-
successfulRecords: number;
|
|
391
|
-
failedRecords: number;
|
|
392
|
-
sentToDLQ: number;
|
|
393
|
-
errors: Array<{ file: string; error: string }>;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function resilientIngestion(): Promise<IngestionResult> {
|
|
397
|
-
console.log('🚀 Starting resilient ingestion workflow\n');
|
|
398
|
-
|
|
399
|
-
// Initialize services
|
|
400
|
-
const client = await createClient({ config: config.fluent });
|
|
401
|
-
const s3 = new S3DataSource(
|
|
402
|
-
{
|
|
403
|
-
type: 'S3_CSV',
|
|
404
|
-
connectionId: 's3-error-recovery',
|
|
405
|
-
name: 'S3 Error Recovery',
|
|
406
|
-
s3Config: {
|
|
407
|
-
...config.s3,
|
|
408
|
-
bucket: 'inventory-bucket',
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
console
|
|
412
|
-
);
|
|
413
|
-
const logger = console; // Use console as logger in examples
|
|
414
|
-
const parser = new CSVParserService(logger);
|
|
415
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
416
|
-
const state = new StateService(logger);
|
|
417
|
-
const circuitBreaker = new CircuitBreaker();
|
|
418
|
-
const dlq = new DeadLetterQueue(s3);
|
|
419
|
-
|
|
420
|
-
const result: IngestionResult = {
|
|
421
|
-
totalFiles: 0,
|
|
422
|
-
processedFiles: 0,
|
|
423
|
-
failedFiles: 0,
|
|
424
|
-
totalRecords: 0,
|
|
425
|
-
successfulRecords: 0,
|
|
426
|
-
failedRecords: 0,
|
|
427
|
-
sentToDLQ: 0,
|
|
428
|
-
errors: [],
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// List files
|
|
432
|
-
const files = await retryWithBackoff(() => s3.listFiles({ prefix: 'data/' }), {
|
|
433
|
-
maxRetries: 5,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
const csvFiles = files.filter(f => f.path.endsWith('.csv'));
|
|
437
|
-
result.totalFiles = csvFiles.length;
|
|
438
|
-
|
|
439
|
-
console.log(`📁 Processing ${csvFiles.length} files\n`);
|
|
440
|
-
|
|
441
|
-
// Process each file with error recovery
|
|
442
|
-
for (const file of csvFiles) {
|
|
443
|
-
console.log(`📄 File: ${file.path}`);
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
// Check if already processed
|
|
447
|
-
if (await state.isFileProcessed(file.path)) {
|
|
448
|
-
console.log(' ✓ Already processed, skipping\n');
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Acquire lock with retry
|
|
453
|
-
const lock = await retryWithBackoff(() => state.acquireLock(file.path, 60000), {
|
|
454
|
-
maxRetries: 3,
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
if (!lock.acquired) {
|
|
458
|
-
console.log(' ⏸ Locked by another process, skipping\n');
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
try {
|
|
463
|
-
// Read file with retry and circuit breaker
|
|
464
|
-
const data = await circuitBreaker.execute(() =>
|
|
465
|
-
retryWithBackoff(() => s3.downloadFile(file.path), {
|
|
466
|
-
maxRetries: 3,
|
|
467
|
-
onRetry: (attempt, delay) => {
|
|
468
|
-
console.log(` ⏳ Retrying S3 read (attempt ${attempt})...`);
|
|
469
|
-
},
|
|
470
|
-
})
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
console.log(` 📖 Read ${data.length} bytes`);
|
|
474
|
-
|
|
475
|
-
// Parse CSV
|
|
476
|
-
const records = await parser.parse(data);
|
|
477
|
-
result.totalRecords += records.length;
|
|
478
|
-
|
|
479
|
-
console.log(` 🔍 Parsed ${records.length} records`);
|
|
480
|
-
|
|
481
|
-
// Process records with partial failure handling
|
|
482
|
-
const successfulRecords = [];
|
|
483
|
-
const failedRecordsForDLQ = [];
|
|
484
|
-
|
|
485
|
-
for (const [index, record] of records.entries()) {
|
|
486
|
-
try {
|
|
487
|
-
// Map individual record
|
|
488
|
-
const mapResult = await mapper.map([record]);
|
|
489
|
-
|
|
490
|
-
if (mapResult.success && mapResult.data.length > 0) {
|
|
491
|
-
successfulRecords.push(mapResult.data[0]);
|
|
492
|
-
} else {
|
|
493
|
-
// Validation error - send to DLQ
|
|
494
|
-
const error: ClassifiedError = {
|
|
495
|
-
category: ErrorCategory.VALIDATION,
|
|
496
|
-
message: mapResult.errors.join('; '),
|
|
497
|
-
retryable: false,
|
|
498
|
-
originalError: new Error(mapResult.errors.join('; ')),
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
failedRecordsForDLQ.push({ record, error });
|
|
502
|
-
result.failedRecords++;
|
|
503
|
-
}
|
|
504
|
-
} catch (error: any) {
|
|
505
|
-
const classified = classifyError(error);
|
|
506
|
-
|
|
507
|
-
console.error(` ⚠️ Record ${index + 1} failed:`, error.message);
|
|
508
|
-
|
|
509
|
-
failedRecordsForDLQ.push({ record, error: classified });
|
|
510
|
-
result.failedRecords++;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Send successful records to Fluent
|
|
515
|
-
if (successfulRecords.length > 0) {
|
|
516
|
-
const job = await circuitBreaker.execute(() =>
|
|
517
|
-
retryWithBackoff(
|
|
518
|
-
() =>
|
|
519
|
-
client.createJob({
|
|
520
|
-
name: `Import - ${file.path}`,
|
|
521
|
-
retailerId: config.fluent.retailerId,
|
|
522
|
-
}),
|
|
523
|
-
{ maxRetries: 3 }
|
|
524
|
-
)
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
await circuitBreaker.execute(() =>
|
|
528
|
-
retryWithBackoff(
|
|
529
|
-
() =>
|
|
530
|
-
client.sendBatch(job.id, {
|
|
531
|
-
action: 'UPSERT',
|
|
532
|
-
entityType: 'INVENTORY',
|
|
533
|
-
source: 'ERROR_RECOVERY_RESILIENT',
|
|
534
|
-
event: 'INVENTORY_UPDATE',
|
|
535
|
-
entities: successfulRecords,
|
|
536
|
-
}),
|
|
537
|
-
{ maxRetries: 3 }
|
|
538
|
-
)
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
result.successfulRecords += successfulRecords.length;
|
|
542
|
-
|
|
543
|
-
console.log(` ✅ Ingested ${successfulRecords.length} records (job: ${job.id})`);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Send failed records to DLQ
|
|
547
|
-
if (failedRecordsForDLQ.length > 0) {
|
|
548
|
-
console.log(` 📮 Sending ${failedRecordsForDLQ.length} failed records to DLQ`);
|
|
549
|
-
|
|
550
|
-
for (const { record, error } of failedRecordsForDLQ) {
|
|
551
|
-
await dlq.sendToDLQ(record, error, {
|
|
552
|
-
originalFile: file.path,
|
|
553
|
-
timestamp: new Date().toISOString(),
|
|
554
|
-
});
|
|
555
|
-
result.sentToDLQ++;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Mark file as processed
|
|
560
|
-
await state.markFileProcessed(file.path, {
|
|
561
|
-
processedAt: new Date().toISOString(),
|
|
562
|
-
totalRecords: records.length,
|
|
563
|
-
successfulRecords: successfulRecords.length,
|
|
564
|
-
failedRecords: failedRecordsForDLQ.length,
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
result.processedFiles++;
|
|
568
|
-
|
|
569
|
-
console.log(` ✓ File processing complete\n`);
|
|
570
|
-
} finally {
|
|
571
|
-
await state.releaseLock(file.path);
|
|
572
|
-
}
|
|
573
|
-
} catch (error: any) {
|
|
574
|
-
const classified = classifyError(error);
|
|
575
|
-
|
|
576
|
-
console.error(` ❌ File failed (${classified.category}): ${error.message}\n`);
|
|
577
|
-
|
|
578
|
-
result.failedFiles++;
|
|
579
|
-
result.errors.push({
|
|
580
|
-
file: file.path,
|
|
581
|
-
error: `${classified.category}: ${error.message}`,
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
// For permanent errors, don't retry file
|
|
585
|
-
if (classified.category === ErrorCategory.PERMANENT) {
|
|
586
|
-
console.log(' 🚫 Permanent error - marking file as failed');
|
|
587
|
-
|
|
588
|
-
await state.markFileProcessed(file.path, {
|
|
589
|
-
processedAt: new Date().toISOString(),
|
|
590
|
-
failed: true,
|
|
591
|
-
error: error.message,
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
return result;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// ============================================================================
|
|
601
|
-
// DLQ Reprocessing
|
|
602
|
-
// ============================================================================
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Reprocess failed records from DLQ after fixing issues
|
|
606
|
-
*/
|
|
607
|
-
async function reprocessDLQ(): Promise<void> {
|
|
608
|
-
console.log('🔄 Reprocessing records from DLQ\n');
|
|
609
|
-
|
|
610
|
-
const s3 = new S3DataSource(
|
|
611
|
-
{
|
|
612
|
-
type: 'S3_CSV',
|
|
613
|
-
connectionId: 's3-dlq-reprocess',
|
|
614
|
-
name: 'S3 DLQ Reprocess',
|
|
615
|
-
s3Config: {
|
|
616
|
-
...config.s3,
|
|
617
|
-
bucket: 'dlq-bucket',
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
console
|
|
621
|
-
);
|
|
622
|
-
const client = await createClient({ config: config.fluent });
|
|
623
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
624
|
-
const dlq = new DeadLetterQueue(s3);
|
|
625
|
-
|
|
626
|
-
// Retrieve failed records
|
|
627
|
-
const dlqEntries = await dlq.retrieveFromDLQ(100);
|
|
628
|
-
|
|
629
|
-
console.log(`📮 Found ${dlqEntries.length} DLQ entries\n`);
|
|
630
|
-
|
|
631
|
-
const successfulRetries = [];
|
|
632
|
-
|
|
633
|
-
for (const entry of dlqEntries) {
|
|
634
|
-
try {
|
|
635
|
-
// Remap record (fixes may have been applied)
|
|
636
|
-
const result = await mapper.map([entry.record]);
|
|
637
|
-
|
|
638
|
-
if (result.success && result.data.length > 0) {
|
|
639
|
-
successfulRetries.push(result.data[0]);
|
|
640
|
-
console.log(' ✓ Successfully remapped record');
|
|
641
|
-
} else {
|
|
642
|
-
console.log(' ⚠️ Record still invalid:', result.errors.join('; '));
|
|
643
|
-
}
|
|
644
|
-
} catch (error: any) {
|
|
645
|
-
console.error(' ❌ Reprocessing failed:', error.message);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// Send successful retries to Fluent
|
|
650
|
-
if (successfulRetries.length > 0) {
|
|
651
|
-
const job = await client.createJob({
|
|
652
|
-
name: 'DLQ Reprocessing',
|
|
653
|
-
retailerId: config.fluent.retailerId,
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
await client.sendBatch(job.id, {
|
|
657
|
-
action: 'UPSERT',
|
|
658
|
-
entityType: 'INVENTORY',
|
|
659
|
-
source: 'DLQ_REPROCESSING',
|
|
660
|
-
event: 'INVENTORY_RECOVERY',
|
|
661
|
-
entities: successfulRetries,
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
console.log(`\n✅ Reprocessed ${successfulRetries.length} records from DLQ`);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// ============================================================================
|
|
669
|
-
// Main Entry Point
|
|
670
|
-
// ============================================================================
|
|
671
|
-
|
|
672
|
-
async function main() {
|
|
673
|
-
const mode = process.argv[2] || 'ingest';
|
|
674
|
-
|
|
675
|
-
try {
|
|
676
|
-
switch (mode) {
|
|
677
|
-
case 'ingest':
|
|
678
|
-
const result = await resilientIngestion();
|
|
679
|
-
|
|
680
|
-
console.log('\n' + '='.repeat(60));
|
|
681
|
-
console.log('📊 Ingestion Summary');
|
|
682
|
-
console.log('='.repeat(60));
|
|
683
|
-
console.log(`Total files: ${result.totalFiles}`);
|
|
684
|
-
console.log(`Processed files: ${result.processedFiles}`);
|
|
685
|
-
console.log(`Failed files: ${result.failedFiles}`);
|
|
686
|
-
console.log(`Total records: ${result.totalRecords}`);
|
|
687
|
-
console.log(`Successful records: ${result.successfulRecords}`);
|
|
688
|
-
console.log(`Failed records: ${result.failedRecords}`);
|
|
689
|
-
console.log(`Sent to DLQ: ${result.sentToDLQ}`);
|
|
690
|
-
|
|
691
|
-
if (result.errors.length > 0) {
|
|
692
|
-
console.log('\n❌ Errors:');
|
|
693
|
-
result.errors.forEach(e => console.log(` - ${e.file}: ${e.error}`));
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
console.log('='.repeat(60));
|
|
697
|
-
break;
|
|
698
|
-
|
|
699
|
-
case 'reprocess-dlq':
|
|
700
|
-
await reprocessDLQ();
|
|
701
|
-
break;
|
|
702
|
-
|
|
703
|
-
default:
|
|
704
|
-
console.log('Usage:');
|
|
705
|
-
console.log(' npx tsx examples/error-recovery.ts ingest # Run ingestion');
|
|
706
|
-
console.log(' npx tsx examples/error-recovery.ts reprocess-dlq # Reprocess DLQ');
|
|
707
|
-
}
|
|
708
|
-
} catch (error: any) {
|
|
709
|
-
console.error('\n💥 Fatal error:', error.message);
|
|
710
|
-
process.exit(1);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Run if executed directly
|
|
715
|
-
if (require.main === module) {
|
|
716
|
-
main();
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Export for testing
|
|
720
|
-
export {
|
|
721
|
-
retryWithBackoff,
|
|
722
|
-
classifyError,
|
|
723
|
-
CircuitBreaker,
|
|
724
|
-
DeadLetterQueue,
|
|
725
|
-
resilientIngestion,
|
|
726
|
-
ErrorCategory,
|
|
727
|
-
ClassifiedError,
|
|
728
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Error Recovery and Resilience Example
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive error handling strategies for production-ready ingestion:
|
|
5
|
+
* - Exponential backoff retry logic
|
|
6
|
+
* - Circuit breaker pattern
|
|
7
|
+
* - Partial failure handling
|
|
8
|
+
* - Dead letter queue (DLQ)
|
|
9
|
+
* - Graceful degradation
|
|
10
|
+
* - Error categorization and logging
|
|
11
|
+
*
|
|
12
|
+
* Prerequisites:
|
|
13
|
+
* - npm install @fluentcommerce/fc-connect-sdk
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* npx tsx examples/error-recovery.ts
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
createClient,
|
|
21
|
+
S3DataSource,
|
|
22
|
+
CSVParserService,
|
|
23
|
+
UniversalMapper,
|
|
24
|
+
StateService,
|
|
25
|
+
SimpleKVStore,
|
|
26
|
+
FluentClient,
|
|
27
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Configuration
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
const config = {
|
|
34
|
+
fluent: {
|
|
35
|
+
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
36
|
+
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
37
|
+
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
38
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
39
|
+
},
|
|
40
|
+
s3: {
|
|
41
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
42
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
43
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
44
|
+
},
|
|
45
|
+
retry: {
|
|
46
|
+
maxRetries: 3,
|
|
47
|
+
initialDelayMs: 1000,
|
|
48
|
+
maxDelayMs: 30000,
|
|
49
|
+
backoffMultiplier: 2,
|
|
50
|
+
},
|
|
51
|
+
circuitBreaker: {
|
|
52
|
+
failureThreshold: 5,
|
|
53
|
+
resetTimeoutMs: 60000,
|
|
54
|
+
},
|
|
55
|
+
deadLetterQueue: {
|
|
56
|
+
bucket: 'dlq-bucket',
|
|
57
|
+
prefix: 'failed-ingestion/',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const mappingConfig = {
|
|
62
|
+
fields: {
|
|
63
|
+
ref: { source: 'sku', required: true },
|
|
64
|
+
productRef: { source: 'sku', required: true },
|
|
65
|
+
locationRef: { source: 'location', required: true },
|
|
66
|
+
qty: { source: 'quantity', resolver: 'sdk.parseInt', required: true },
|
|
67
|
+
status: { source: 'status', resolver: 'sdk.uppercase', defaultValue: 'AVAILABLE' },
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Error Types & Classification
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
enum ErrorCategory {
|
|
76
|
+
TRANSIENT = 'TRANSIENT', // Retry possible (network, rate limit)
|
|
77
|
+
VALIDATION = 'VALIDATION', // Data quality issue (skip/dlq)
|
|
78
|
+
PERMANENT = 'PERMANENT', // Cannot be retried (auth, config)
|
|
79
|
+
UNKNOWN = 'UNKNOWN', // Unclear, safe to retry
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ClassifiedError {
|
|
83
|
+
category: ErrorCategory;
|
|
84
|
+
message: string;
|
|
85
|
+
retryable: boolean;
|
|
86
|
+
originalError: Error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Classify error to determine retry strategy
|
|
91
|
+
*/
|
|
92
|
+
function classifyError(error: any): ClassifiedError {
|
|
93
|
+
const message = error.message || String(error);
|
|
94
|
+
|
|
95
|
+
// Transient errors - safe to retry
|
|
96
|
+
if (
|
|
97
|
+
message.includes('ECONNRESET') ||
|
|
98
|
+
message.includes('ETIMEDOUT') ||
|
|
99
|
+
message.includes('ENOTFOUND') ||
|
|
100
|
+
message.includes('Network error') ||
|
|
101
|
+
message.includes('Rate limit') ||
|
|
102
|
+
message.includes('429')
|
|
103
|
+
) {
|
|
104
|
+
return {
|
|
105
|
+
category: ErrorCategory.TRANSIENT,
|
|
106
|
+
message: 'Network or rate limit error',
|
|
107
|
+
retryable: true,
|
|
108
|
+
originalError: error,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validation errors - data quality issues
|
|
113
|
+
if (
|
|
114
|
+
message.includes('Validation failed') ||
|
|
115
|
+
message.includes('Invalid field') ||
|
|
116
|
+
message.includes('Required field missing') ||
|
|
117
|
+
message.includes('Schema mismatch')
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
category: ErrorCategory.VALIDATION,
|
|
121
|
+
message: 'Data validation error',
|
|
122
|
+
retryable: false,
|
|
123
|
+
originalError: error,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Permanent errors - configuration/auth issues
|
|
128
|
+
if (
|
|
129
|
+
message.includes('Unauthorized') ||
|
|
130
|
+
message.includes('Authentication failed') ||
|
|
131
|
+
message.includes('Access denied') ||
|
|
132
|
+
message.includes('Invalid credentials') ||
|
|
133
|
+
message.includes('401') ||
|
|
134
|
+
message.includes('403')
|
|
135
|
+
) {
|
|
136
|
+
return {
|
|
137
|
+
category: ErrorCategory.PERMANENT,
|
|
138
|
+
message: 'Authentication or authorization error',
|
|
139
|
+
retryable: false,
|
|
140
|
+
originalError: error,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Unknown errors - default to retryable
|
|
145
|
+
return {
|
|
146
|
+
category: ErrorCategory.UNKNOWN,
|
|
147
|
+
message: 'Unknown error',
|
|
148
|
+
retryable: true,
|
|
149
|
+
originalError: error,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Retry Logic with Exponential Backoff
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
interface RetryOptions {
|
|
158
|
+
maxRetries?: number;
|
|
159
|
+
initialDelayMs?: number;
|
|
160
|
+
maxDelayMs?: number;
|
|
161
|
+
backoffMultiplier?: number;
|
|
162
|
+
onRetry?: (attempt: number, delay: number, error: Error) => void;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Retry operation with exponential backoff
|
|
167
|
+
*/
|
|
168
|
+
async function retryWithBackoff<T>(
|
|
169
|
+
operation: () => Promise<T>,
|
|
170
|
+
options: RetryOptions = {}
|
|
171
|
+
): Promise<T> {
|
|
172
|
+
const {
|
|
173
|
+
maxRetries = config.retry.maxRetries,
|
|
174
|
+
initialDelayMs = config.retry.initialDelayMs,
|
|
175
|
+
maxDelayMs = config.retry.maxDelayMs,
|
|
176
|
+
backoffMultiplier = config.retry.backoffMultiplier,
|
|
177
|
+
onRetry,
|
|
178
|
+
} = options;
|
|
179
|
+
|
|
180
|
+
let lastError: Error;
|
|
181
|
+
let delay = initialDelayMs;
|
|
182
|
+
|
|
183
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
184
|
+
try {
|
|
185
|
+
return await operation();
|
|
186
|
+
} catch (error: any) {
|
|
187
|
+
lastError = error;
|
|
188
|
+
|
|
189
|
+
// Classify error to determine if retry is appropriate
|
|
190
|
+
const classified = classifyError(error);
|
|
191
|
+
|
|
192
|
+
if (!classified.retryable) {
|
|
193
|
+
console.error(`❌ Non-retryable error (${classified.category}):`, error.message);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (attempt === maxRetries) {
|
|
198
|
+
console.error(`❌ Max retries (${maxRetries}) exceeded`);
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Calculate next delay with exponential backoff
|
|
203
|
+
const nextDelay = Math.min(delay, maxDelayMs);
|
|
204
|
+
|
|
205
|
+
console.log(`⚠️ Attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
206
|
+
console.log(` Retrying in ${nextDelay}ms...`);
|
|
207
|
+
|
|
208
|
+
if (onRetry) {
|
|
209
|
+
onRetry(attempt, nextDelay, error);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await sleep(nextDelay);
|
|
213
|
+
delay *= backoffMultiplier;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
throw lastError!;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Sleep utility
|
|
222
|
+
*/
|
|
223
|
+
function sleep(ms: number): Promise<void> {
|
|
224
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ============================================================================
|
|
228
|
+
// Circuit Breaker Pattern
|
|
229
|
+
// ============================================================================
|
|
230
|
+
|
|
231
|
+
enum CircuitState {
|
|
232
|
+
CLOSED = 'CLOSED', // Normal operation
|
|
233
|
+
OPEN = 'OPEN', // Failing, reject immediately
|
|
234
|
+
HALF_OPEN = 'HALF_OPEN', // Testing if service recovered
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
class CircuitBreaker {
|
|
238
|
+
private state: CircuitState = CircuitState.CLOSED;
|
|
239
|
+
private failureCount = 0;
|
|
240
|
+
private lastFailureTime: number = 0;
|
|
241
|
+
private successCount = 0;
|
|
242
|
+
|
|
243
|
+
constructor(
|
|
244
|
+
private failureThreshold: number = config.circuitBreaker.failureThreshold,
|
|
245
|
+
private resetTimeoutMs: number = config.circuitBreaker.resetTimeoutMs
|
|
246
|
+
) {}
|
|
247
|
+
|
|
248
|
+
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
249
|
+
// Check if circuit should transition from OPEN to HALF_OPEN
|
|
250
|
+
if (this.state === CircuitState.OPEN) {
|
|
251
|
+
const timeSinceLastFailure = Date.now() - this.lastFailureTime;
|
|
252
|
+
|
|
253
|
+
if (timeSinceLastFailure >= this.resetTimeoutMs) {
|
|
254
|
+
console.log('🔄 Circuit breaker transitioning to HALF_OPEN (testing)');
|
|
255
|
+
this.state = CircuitState.HALF_OPEN;
|
|
256
|
+
this.successCount = 0;
|
|
257
|
+
} else {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Circuit breaker is OPEN (wait ${Math.ceil((this.resetTimeoutMs - timeSinceLastFailure) / 1000)}s)`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await operation();
|
|
266
|
+
|
|
267
|
+
// Success - record it
|
|
268
|
+
this.onSuccess();
|
|
269
|
+
|
|
270
|
+
return result;
|
|
271
|
+
} catch (error) {
|
|
272
|
+
// Failure - record it
|
|
273
|
+
this.onFailure();
|
|
274
|
+
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private onSuccess(): void {
|
|
280
|
+
this.failureCount = 0;
|
|
281
|
+
|
|
282
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
283
|
+
this.successCount++;
|
|
284
|
+
|
|
285
|
+
// After 3 successes in HALF_OPEN, transition to CLOSED
|
|
286
|
+
if (this.successCount >= 3) {
|
|
287
|
+
console.log('✅ Circuit breaker closing (service recovered)');
|
|
288
|
+
this.state = CircuitState.CLOSED;
|
|
289
|
+
this.successCount = 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private onFailure(): void {
|
|
295
|
+
this.failureCount++;
|
|
296
|
+
this.lastFailureTime = Date.now();
|
|
297
|
+
|
|
298
|
+
if (this.failureCount >= this.failureThreshold) {
|
|
299
|
+
console.log(`🚫 Circuit breaker opening (${this.failureCount} failures)`);
|
|
300
|
+
this.state = CircuitState.OPEN;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getState(): CircuitState {
|
|
305
|
+
return this.state;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Dead Letter Queue (DLQ)
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
class DeadLetterQueue {
|
|
314
|
+
constructor(
|
|
315
|
+
private s3: S3DataSource,
|
|
316
|
+
private bucket: string = config.deadLetterQueue.bucket,
|
|
317
|
+
private prefix: string = config.deadLetterQueue.prefix
|
|
318
|
+
) {}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Send failed record to DLQ for manual review
|
|
322
|
+
*/
|
|
323
|
+
async sendToDLQ(
|
|
324
|
+
record: any,
|
|
325
|
+
error: ClassifiedError,
|
|
326
|
+
metadata: {
|
|
327
|
+
originalFile?: string;
|
|
328
|
+
attempt?: number;
|
|
329
|
+
timestamp?: string;
|
|
330
|
+
} = {}
|
|
331
|
+
): Promise<void> {
|
|
332
|
+
const dlqEntry = {
|
|
333
|
+
record,
|
|
334
|
+
error: {
|
|
335
|
+
category: error.category,
|
|
336
|
+
message: error.message,
|
|
337
|
+
stack: error.originalError.stack,
|
|
338
|
+
},
|
|
339
|
+
metadata: {
|
|
340
|
+
...metadata,
|
|
341
|
+
timestamp: metadata.timestamp || new Date().toISOString(),
|
|
342
|
+
dlqTimestamp: new Date().toISOString(),
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const key = `${this.prefix}${Date.now()}-${Math.random().toString(36).substr(2, 9)}.json`;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
await this.s3.putObject(this.bucket, key, Buffer.from(JSON.stringify(dlqEntry, null, 2)));
|
|
350
|
+
|
|
351
|
+
console.log(`📮 Sent to DLQ: s3://${this.bucket}/${key}`);
|
|
352
|
+
} catch (dlqError) {
|
|
353
|
+
console.error('❌ Failed to send to DLQ:', dlqError);
|
|
354
|
+
// Log to local file as fallback
|
|
355
|
+
console.error('DLQ Entry:', JSON.stringify(dlqEntry));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Retrieve failed records from DLQ for reprocessing
|
|
361
|
+
*/
|
|
362
|
+
async retrieveFromDLQ(limit: number = 100): Promise<any[]> {
|
|
363
|
+
const files = await this.s3.listFiles({ prefix: this.prefix });
|
|
364
|
+
|
|
365
|
+
const entries = [];
|
|
366
|
+
|
|
367
|
+
for (const file of files.slice(0, limit)) {
|
|
368
|
+
try {
|
|
369
|
+
const data = await this.s3.downloadFile(file.path);
|
|
370
|
+
const entry = JSON.parse(data.toString());
|
|
371
|
+
entries.push(entry);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error(`Failed to read DLQ entry ${file.path}:`, error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return entries;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Resilient Ingestion Workflow
|
|
383
|
+
// ============================================================================
|
|
384
|
+
|
|
385
|
+
interface IngestionResult {
|
|
386
|
+
totalFiles: number;
|
|
387
|
+
processedFiles: number;
|
|
388
|
+
failedFiles: number;
|
|
389
|
+
totalRecords: number;
|
|
390
|
+
successfulRecords: number;
|
|
391
|
+
failedRecords: number;
|
|
392
|
+
sentToDLQ: number;
|
|
393
|
+
errors: Array<{ file: string; error: string }>;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function resilientIngestion(): Promise<IngestionResult> {
|
|
397
|
+
console.log('🚀 Starting resilient ingestion workflow\n');
|
|
398
|
+
|
|
399
|
+
// Initialize services
|
|
400
|
+
const client = await createClient({ config: config.fluent });
|
|
401
|
+
const s3 = new S3DataSource(
|
|
402
|
+
{
|
|
403
|
+
type: 'S3_CSV',
|
|
404
|
+
connectionId: 's3-error-recovery',
|
|
405
|
+
name: 'S3 Error Recovery',
|
|
406
|
+
s3Config: {
|
|
407
|
+
...config.s3,
|
|
408
|
+
bucket: 'inventory-bucket',
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
console
|
|
412
|
+
);
|
|
413
|
+
const logger = console; // Use console as logger in examples
|
|
414
|
+
const parser = new CSVParserService(logger);
|
|
415
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
416
|
+
const state = new StateService(logger);
|
|
417
|
+
const circuitBreaker = new CircuitBreaker();
|
|
418
|
+
const dlq = new DeadLetterQueue(s3);
|
|
419
|
+
|
|
420
|
+
const result: IngestionResult = {
|
|
421
|
+
totalFiles: 0,
|
|
422
|
+
processedFiles: 0,
|
|
423
|
+
failedFiles: 0,
|
|
424
|
+
totalRecords: 0,
|
|
425
|
+
successfulRecords: 0,
|
|
426
|
+
failedRecords: 0,
|
|
427
|
+
sentToDLQ: 0,
|
|
428
|
+
errors: [],
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// List files
|
|
432
|
+
const files = await retryWithBackoff(() => s3.listFiles({ prefix: 'data/' }), {
|
|
433
|
+
maxRetries: 5,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const csvFiles = files.filter(f => f.path.endsWith('.csv'));
|
|
437
|
+
result.totalFiles = csvFiles.length;
|
|
438
|
+
|
|
439
|
+
console.log(`📁 Processing ${csvFiles.length} files\n`);
|
|
440
|
+
|
|
441
|
+
// Process each file with error recovery
|
|
442
|
+
for (const file of csvFiles) {
|
|
443
|
+
console.log(`📄 File: ${file.path}`);
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
// Check if already processed
|
|
447
|
+
if (await state.isFileProcessed(file.path)) {
|
|
448
|
+
console.log(' ✓ Already processed, skipping\n');
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Acquire lock with retry
|
|
453
|
+
const lock = await retryWithBackoff(() => state.acquireLock(file.path, 60000), {
|
|
454
|
+
maxRetries: 3,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (!lock.acquired) {
|
|
458
|
+
console.log(' ⏸ Locked by another process, skipping\n');
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
// Read file with retry and circuit breaker
|
|
464
|
+
const data = await circuitBreaker.execute(() =>
|
|
465
|
+
retryWithBackoff(() => s3.downloadFile(file.path), {
|
|
466
|
+
maxRetries: 3,
|
|
467
|
+
onRetry: (attempt, delay) => {
|
|
468
|
+
console.log(` ⏳ Retrying S3 read (attempt ${attempt})...`);
|
|
469
|
+
},
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
console.log(` 📖 Read ${data.length} bytes`);
|
|
474
|
+
|
|
475
|
+
// Parse CSV
|
|
476
|
+
const records = await parser.parse(data);
|
|
477
|
+
result.totalRecords += records.length;
|
|
478
|
+
|
|
479
|
+
console.log(` 🔍 Parsed ${records.length} records`);
|
|
480
|
+
|
|
481
|
+
// Process records with partial failure handling
|
|
482
|
+
const successfulRecords = [];
|
|
483
|
+
const failedRecordsForDLQ = [];
|
|
484
|
+
|
|
485
|
+
for (const [index, record] of records.entries()) {
|
|
486
|
+
try {
|
|
487
|
+
// Map individual record
|
|
488
|
+
const mapResult = await mapper.map([record]);
|
|
489
|
+
|
|
490
|
+
if (mapResult.success && mapResult.data.length > 0) {
|
|
491
|
+
successfulRecords.push(mapResult.data[0]);
|
|
492
|
+
} else {
|
|
493
|
+
// Validation error - send to DLQ
|
|
494
|
+
const error: ClassifiedError = {
|
|
495
|
+
category: ErrorCategory.VALIDATION,
|
|
496
|
+
message: mapResult.errors.join('; '),
|
|
497
|
+
retryable: false,
|
|
498
|
+
originalError: new Error(mapResult.errors.join('; ')),
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
failedRecordsForDLQ.push({ record, error });
|
|
502
|
+
result.failedRecords++;
|
|
503
|
+
}
|
|
504
|
+
} catch (error: any) {
|
|
505
|
+
const classified = classifyError(error);
|
|
506
|
+
|
|
507
|
+
console.error(` ⚠️ Record ${index + 1} failed:`, error.message);
|
|
508
|
+
|
|
509
|
+
failedRecordsForDLQ.push({ record, error: classified });
|
|
510
|
+
result.failedRecords++;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Send successful records to Fluent
|
|
515
|
+
if (successfulRecords.length > 0) {
|
|
516
|
+
const job = await circuitBreaker.execute(() =>
|
|
517
|
+
retryWithBackoff(
|
|
518
|
+
() =>
|
|
519
|
+
client.createJob({
|
|
520
|
+
name: `Import - ${file.path}`,
|
|
521
|
+
retailerId: config.fluent.retailerId,
|
|
522
|
+
}),
|
|
523
|
+
{ maxRetries: 3 }
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
await circuitBreaker.execute(() =>
|
|
528
|
+
retryWithBackoff(
|
|
529
|
+
() =>
|
|
530
|
+
client.sendBatch(job.id, {
|
|
531
|
+
action: 'UPSERT',
|
|
532
|
+
entityType: 'INVENTORY',
|
|
533
|
+
source: 'ERROR_RECOVERY_RESILIENT',
|
|
534
|
+
event: 'INVENTORY_UPDATE',
|
|
535
|
+
entities: successfulRecords,
|
|
536
|
+
}),
|
|
537
|
+
{ maxRetries: 3 }
|
|
538
|
+
)
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
result.successfulRecords += successfulRecords.length;
|
|
542
|
+
|
|
543
|
+
console.log(` ✅ Ingested ${successfulRecords.length} records (job: ${job.id})`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Send failed records to DLQ
|
|
547
|
+
if (failedRecordsForDLQ.length > 0) {
|
|
548
|
+
console.log(` 📮 Sending ${failedRecordsForDLQ.length} failed records to DLQ`);
|
|
549
|
+
|
|
550
|
+
for (const { record, error } of failedRecordsForDLQ) {
|
|
551
|
+
await dlq.sendToDLQ(record, error, {
|
|
552
|
+
originalFile: file.path,
|
|
553
|
+
timestamp: new Date().toISOString(),
|
|
554
|
+
});
|
|
555
|
+
result.sentToDLQ++;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Mark file as processed
|
|
560
|
+
await state.markFileProcessed(file.path, {
|
|
561
|
+
processedAt: new Date().toISOString(),
|
|
562
|
+
totalRecords: records.length,
|
|
563
|
+
successfulRecords: successfulRecords.length,
|
|
564
|
+
failedRecords: failedRecordsForDLQ.length,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
result.processedFiles++;
|
|
568
|
+
|
|
569
|
+
console.log(` ✓ File processing complete\n`);
|
|
570
|
+
} finally {
|
|
571
|
+
await state.releaseLock(file.path);
|
|
572
|
+
}
|
|
573
|
+
} catch (error: any) {
|
|
574
|
+
const classified = classifyError(error);
|
|
575
|
+
|
|
576
|
+
console.error(` ❌ File failed (${classified.category}): ${error.message}\n`);
|
|
577
|
+
|
|
578
|
+
result.failedFiles++;
|
|
579
|
+
result.errors.push({
|
|
580
|
+
file: file.path,
|
|
581
|
+
error: `${classified.category}: ${error.message}`,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// For permanent errors, don't retry file
|
|
585
|
+
if (classified.category === ErrorCategory.PERMANENT) {
|
|
586
|
+
console.log(' 🚫 Permanent error - marking file as failed');
|
|
587
|
+
|
|
588
|
+
await state.markFileProcessed(file.path, {
|
|
589
|
+
processedAt: new Date().toISOString(),
|
|
590
|
+
failed: true,
|
|
591
|
+
error: error.message,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// DLQ Reprocessing
|
|
602
|
+
// ============================================================================
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Reprocess failed records from DLQ after fixing issues
|
|
606
|
+
*/
|
|
607
|
+
async function reprocessDLQ(): Promise<void> {
|
|
608
|
+
console.log('🔄 Reprocessing records from DLQ\n');
|
|
609
|
+
|
|
610
|
+
const s3 = new S3DataSource(
|
|
611
|
+
{
|
|
612
|
+
type: 'S3_CSV',
|
|
613
|
+
connectionId: 's3-dlq-reprocess',
|
|
614
|
+
name: 'S3 DLQ Reprocess',
|
|
615
|
+
s3Config: {
|
|
616
|
+
...config.s3,
|
|
617
|
+
bucket: 'dlq-bucket',
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
console
|
|
621
|
+
);
|
|
622
|
+
const client = await createClient({ config: config.fluent });
|
|
623
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
624
|
+
const dlq = new DeadLetterQueue(s3);
|
|
625
|
+
|
|
626
|
+
// Retrieve failed records
|
|
627
|
+
const dlqEntries = await dlq.retrieveFromDLQ(100);
|
|
628
|
+
|
|
629
|
+
console.log(`📮 Found ${dlqEntries.length} DLQ entries\n`);
|
|
630
|
+
|
|
631
|
+
const successfulRetries = [];
|
|
632
|
+
|
|
633
|
+
for (const entry of dlqEntries) {
|
|
634
|
+
try {
|
|
635
|
+
// Remap record (fixes may have been applied)
|
|
636
|
+
const result = await mapper.map([entry.record]);
|
|
637
|
+
|
|
638
|
+
if (result.success && result.data.length > 0) {
|
|
639
|
+
successfulRetries.push(result.data[0]);
|
|
640
|
+
console.log(' ✓ Successfully remapped record');
|
|
641
|
+
} else {
|
|
642
|
+
console.log(' ⚠️ Record still invalid:', result.errors.join('; '));
|
|
643
|
+
}
|
|
644
|
+
} catch (error: any) {
|
|
645
|
+
console.error(' ❌ Reprocessing failed:', error.message);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Send successful retries to Fluent
|
|
650
|
+
if (successfulRetries.length > 0) {
|
|
651
|
+
const job = await client.createJob({
|
|
652
|
+
name: 'DLQ Reprocessing',
|
|
653
|
+
retailerId: config.fluent.retailerId,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
await client.sendBatch(job.id, {
|
|
657
|
+
action: 'UPSERT',
|
|
658
|
+
entityType: 'INVENTORY',
|
|
659
|
+
source: 'DLQ_REPROCESSING',
|
|
660
|
+
event: 'INVENTORY_RECOVERY',
|
|
661
|
+
entities: successfulRetries,
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
console.log(`\n✅ Reprocessed ${successfulRetries.length} records from DLQ`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ============================================================================
|
|
669
|
+
// Main Entry Point
|
|
670
|
+
// ============================================================================
|
|
671
|
+
|
|
672
|
+
async function main() {
|
|
673
|
+
const mode = process.argv[2] || 'ingest';
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
switch (mode) {
|
|
677
|
+
case 'ingest':
|
|
678
|
+
const result = await resilientIngestion();
|
|
679
|
+
|
|
680
|
+
console.log('\n' + '='.repeat(60));
|
|
681
|
+
console.log('📊 Ingestion Summary');
|
|
682
|
+
console.log('='.repeat(60));
|
|
683
|
+
console.log(`Total files: ${result.totalFiles}`);
|
|
684
|
+
console.log(`Processed files: ${result.processedFiles}`);
|
|
685
|
+
console.log(`Failed files: ${result.failedFiles}`);
|
|
686
|
+
console.log(`Total records: ${result.totalRecords}`);
|
|
687
|
+
console.log(`Successful records: ${result.successfulRecords}`);
|
|
688
|
+
console.log(`Failed records: ${result.failedRecords}`);
|
|
689
|
+
console.log(`Sent to DLQ: ${result.sentToDLQ}`);
|
|
690
|
+
|
|
691
|
+
if (result.errors.length > 0) {
|
|
692
|
+
console.log('\n❌ Errors:');
|
|
693
|
+
result.errors.forEach(e => console.log(` - ${e.file}: ${e.error}`));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
console.log('='.repeat(60));
|
|
697
|
+
break;
|
|
698
|
+
|
|
699
|
+
case 'reprocess-dlq':
|
|
700
|
+
await reprocessDLQ();
|
|
701
|
+
break;
|
|
702
|
+
|
|
703
|
+
default:
|
|
704
|
+
console.log('Usage:');
|
|
705
|
+
console.log(' npx tsx examples/error-recovery.ts ingest # Run ingestion');
|
|
706
|
+
console.log(' npx tsx examples/error-recovery.ts reprocess-dlq # Reprocess DLQ');
|
|
707
|
+
}
|
|
708
|
+
} catch (error: any) {
|
|
709
|
+
console.error('\n💥 Fatal error:', error.message);
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Run if executed directly
|
|
715
|
+
if (require.main === module) {
|
|
716
|
+
main();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Export for testing
|
|
720
|
+
export {
|
|
721
|
+
retryWithBackoff,
|
|
722
|
+
classifyError,
|
|
723
|
+
CircuitBreaker,
|
|
724
|
+
DeadLetterQueue,
|
|
725
|
+
resilientIngestion,
|
|
726
|
+
ErrorCategory,
|
|
727
|
+
ClassifiedError,
|
|
728
|
+
};
|