@fluentcommerce/fc-connect-sdk 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -2
- package/README.md +39 -0
- package/dist/cjs/auth/index.d.ts +3 -0
- package/dist/cjs/auth/index.js +13 -0
- package/dist/cjs/auth/profile-loader.d.ts +18 -0
- package/dist/cjs/auth/profile-loader.js +208 -0
- package/dist/cjs/client-factory.d.ts +4 -0
- package/dist/cjs/client-factory.js +10 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/auth/index.d.ts +3 -0
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/profile-loader.d.ts +18 -0
- package/dist/esm/auth/profile-loader.js +169 -0
- package/dist/esm/client-factory.d.ts +4 -0
- package/dist/esm/client-factory.js +9 -0
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/auth/index.d.ts +3 -0
- package/dist/types/auth/profile-loader.d.ts +18 -0
- package/dist/types/client-factory.d.ts +4 -0
- package/dist/types/index.d.ts +3 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -482
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
|
@@ -1,1038 +1,1038 @@
|
|
|
1
|
-
# Extraction Modes Guide
|
|
2
|
-
|
|
3
|
-
**FC Connect SDK - Choosing the Right Extraction Mode**
|
|
4
|
-
|
|
5
|
-
> This guide explains the two extraction modes available in the SDK and when to use each one.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
The FC Connect SDK supports two extraction modes for GraphQL queries:
|
|
12
|
-
|
|
13
|
-
1. **Incremental** - Extract only changed records since last run (recommended for scheduled workflows)
|
|
14
|
-
2. **Date Range** - Extract records within specific date window (for ad-hoc queries, backfills, or historical data)
|
|
15
|
-
|
|
16
|
-
**Note:** "Historical extraction" is simply Date Range mode with a very old start date (e.g., `updatedAfter: '1970-01-01'`). There is no separate "historical mode" - use Date Range with appropriate validation and safeguards.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Quick Comparison Table
|
|
21
|
-
|
|
22
|
-
| Mode | Use Case | Safety Level | Recommended Frequency | Typical Records | Production Risk |
|
|
23
|
-
| --------------- | --------------------------------------------- | ------------ | --------------------- | --------------- | ----------------------------------- |
|
|
24
|
-
| **Incremental** | Scheduled syncs, recurring extractions | ✅ **Safe** | Hourly/Daily | ~10k | ✅ **Low** - Safe for production |
|
|
25
|
-
| **Date Range** | Ad-hoc audits, backfills, historical dumps | ⚠️ **Risky** | One-time only | ~50k | ⚠️ **Medium** - Requires validation |
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Mode 1: Incremental (Recommended) ✅
|
|
30
|
-
|
|
31
|
-
### What It Does
|
|
32
|
-
|
|
33
|
-
Extracts only records updated since the last successful extraction run.
|
|
34
|
-
|
|
35
|
-
### How It Works
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
const startTime = Date.now();
|
|
39
|
-
log.info('📦 Starting incremental extraction');
|
|
40
|
-
|
|
41
|
-
// Load last run timestamp from state
|
|
42
|
-
const lastRunTime = await kv.get(['extraction', 'products', 'lastRunTime']);
|
|
43
|
-
log.info('⏱️ Last run timestamp loaded', { lastRunTime });
|
|
44
|
-
|
|
45
|
-
// Query with buffered timestamp (overlap buffer prevents gaps)
|
|
46
|
-
const bufferedTime = new Date(lastRunTime - 60000).toISOString(); // 60 second buffer
|
|
47
|
-
|
|
48
|
-
const result = await client.graphql({
|
|
49
|
-
query: PRODUCTS_QUERY,
|
|
50
|
-
variables: {
|
|
51
|
-
retailerId: 'my-retailer',
|
|
52
|
-
updatedAfter: bufferedTime, // Only changed records
|
|
53
|
-
first: 200,
|
|
54
|
-
},
|
|
55
|
-
pagination: { maxRecords: 10000 },
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
log.info('✅ Extraction complete', {
|
|
59
|
-
recordCount: result.edges.length,
|
|
60
|
-
duration: Date.now() - startTime
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Save new timestamp after successful extraction
|
|
64
|
-
await kv.set(['extraction', 'products', 'lastRunTime'], {
|
|
65
|
-
timestamp: new Date().toISOString(),
|
|
66
|
-
});
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### When to Use
|
|
70
|
-
|
|
71
|
-
- ✅ **Scheduled extractions** (hourly, daily, every 15 minutes)
|
|
72
|
-
- ✅ **Real-time inventory feeds**
|
|
73
|
-
- ✅ **Order status updates**
|
|
74
|
-
- ✅ **Product catalog syncs**
|
|
75
|
-
- ✅ **Any recurring extraction workflow**
|
|
76
|
-
|
|
77
|
-
### Safety Features
|
|
78
|
-
|
|
79
|
-
- Natural rate limiting via timestamps
|
|
80
|
-
- Overlap buffer prevents missed records
|
|
81
|
-
- State tracking prevents reprocessing
|
|
82
|
-
- Predictable record counts
|
|
83
|
-
- Automatic recovery after failures
|
|
84
|
-
|
|
85
|
-
### Recommended Settings
|
|
86
|
-
|
|
87
|
-
| Entity | Frequency | Max Records | Expected Volume |
|
|
88
|
-
| ------------------------ | ------------ | ----------- | ------------------ |
|
|
89
|
-
| **Products** | Daily | 10,000 | 5k-10k updates/day |
|
|
90
|
-
| **Orders** | Hourly | 5,000 | 100-500/hour |
|
|
91
|
-
| **Fulfillments** | Hourly | 5,000 | 100-500/hour |
|
|
92
|
-
| **Inventory Quantities** | Every 15 min | 10,000 | 500-2k per run |
|
|
93
|
-
| **Inventory Positions** | Daily | 20,000 | 10k-20k per day |
|
|
94
|
-
| **Virtual Positions** | Hourly | 10,000 | 1k-5k per hour |
|
|
95
|
-
|
|
96
|
-
### Configuration Example
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"extractionMode": "incremental",
|
|
101
|
-
"pageSize": 200,
|
|
102
|
-
"maxRecords": 10000,
|
|
103
|
-
"overlapBufferSeconds": 60,
|
|
104
|
-
"fallbackStartDate": "2024-01-01T00:00:00Z"
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Mode 2: Date Range (Use Carefully) ⚠️
|
|
111
|
-
|
|
112
|
-
### What It Does
|
|
113
|
-
|
|
114
|
-
Extracts all records updated within a specific date window.
|
|
115
|
-
|
|
116
|
-
### How It Works
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
const startTime = Date.now();
|
|
120
|
-
log.info('📦 Starting date range extraction');
|
|
121
|
-
|
|
122
|
-
// User provides specific date range
|
|
123
|
-
const startDate = ctx.activation?.getVariable('startDate'); // "2025-01-01T00:00:00Z"
|
|
124
|
-
const endDate = ctx.activation?.getVariable('endDate'); // "2025-01-31T23:59:59Z"
|
|
125
|
-
|
|
126
|
-
log.info('⏱️ Date range configured', { startDate, endDate });
|
|
127
|
-
|
|
128
|
-
const result = await client.graphql({
|
|
129
|
-
query: PRODUCTS_QUERY,
|
|
130
|
-
variables: {
|
|
131
|
-
retailerId: 'my-retailer',
|
|
132
|
-
updatedAfter: startDate,
|
|
133
|
-
updatedBefore: endDate, // Bounded range
|
|
134
|
-
first: 200,
|
|
135
|
-
},
|
|
136
|
-
pagination: { maxRecords: 50000 },
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
log.info('✅ Extraction complete', {
|
|
140
|
-
recordCount: result.edges.length,
|
|
141
|
-
duration: Date.now() - startTime
|
|
142
|
-
});
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### When to Use
|
|
146
|
-
|
|
147
|
-
- ⚠️ **One-time data audits** (e.g., "show me all orders from Q4 2024")
|
|
148
|
-
- ⚠️ **Backfilling missed extractions** (e.g., extraction failed for 3 days)
|
|
149
|
-
- ⚠️ **Historical reporting** (e.g., "export last month's inventory changes")
|
|
150
|
-
- ⚠️ **Data quality investigations**
|
|
151
|
-
|
|
152
|
-
### Risks
|
|
153
|
-
|
|
154
|
-
- Can return tens of thousands of records
|
|
155
|
-
- No natural rate limiting
|
|
156
|
-
- Easy to exceed platform limits
|
|
157
|
-
- Can timeout on large datasets
|
|
158
|
-
- Risk of platform overload
|
|
159
|
-
|
|
160
|
-
### Safety Guardrails Required
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
// ✅ REQUIRED: Validate date range before extraction
|
|
164
|
-
function validateDateRange(startDate: string, endDate: string, log: any) {
|
|
165
|
-
const start = new Date(startDate);
|
|
166
|
-
const end = new Date(endDate);
|
|
167
|
-
const daysDiff = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
|
|
168
|
-
|
|
169
|
-
log.info('🔍 Validating date range', { startDate, endDate, daysDiff });
|
|
170
|
-
|
|
171
|
-
// Enforce maximum date range
|
|
172
|
-
const MAX_DAYS = 30;
|
|
173
|
-
if (daysDiff > MAX_DAYS) {
|
|
174
|
-
log.error('❌ Date range validation failed', {
|
|
175
|
-
daysDiff,
|
|
176
|
-
maxDays: MAX_DAYS,
|
|
177
|
-
recommendation: 'Split into smaller ranges or use incremental mode'
|
|
178
|
-
});
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Date range too large: ${daysDiff} days (max: ${MAX_DAYS}). ` +
|
|
181
|
-
`Split into smaller ranges or use incremental mode.`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
log.info('✅ Date range validation passed', { daysDiff, maxDays: MAX_DAYS });
|
|
186
|
-
return { daysDiff, isValid: true };
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Recommended Limits
|
|
191
|
-
|
|
192
|
-
| Entity | Max Date Range | Max Records | File Splitting |
|
|
193
|
-
| ---------------- | -------------- | ----------- | ----------------- |
|
|
194
|
-
| **Products** | 30 days | 50,000 | Required if > 25k |
|
|
195
|
-
| **Orders** | 7 days | 20,000 | Required if > 10k |
|
|
196
|
-
| **Fulfillments** | 7 days | 20,000 | Required if > 10k |
|
|
197
|
-
| **Inventory** | 14 days | 50,000 | Required if > 25k |
|
|
198
|
-
|
|
199
|
-
### Configuration Example
|
|
200
|
-
|
|
201
|
-
```json
|
|
202
|
-
{
|
|
203
|
-
"extractionMode": "dateRange",
|
|
204
|
-
"startDate": "2025-01-01T00:00:00Z",
|
|
205
|
-
"endDate": "2025-01-31T23:59:59Z",
|
|
206
|
-
"pageSize": 200,
|
|
207
|
-
"maxRecords": 50000,
|
|
208
|
-
"validateDateRange": true,
|
|
209
|
-
"maxDaysAllowed": 30
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Production Restrictions
|
|
214
|
-
|
|
215
|
-
- ❌ **Never schedule** date range extractions
|
|
216
|
-
- ❌ **Never use in recurring workflows**
|
|
217
|
-
- ✅ **Always validate date range** before execution
|
|
218
|
-
- ✅ **Always monitor execution time** and record counts
|
|
219
|
-
- ✅ **Always implement file splitting** for large results
|
|
220
|
-
- ✅ **Always get approval** before running on production data
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## File Splitting for Large Extractions
|
|
225
|
-
|
|
226
|
-
### Overview
|
|
227
|
-
|
|
228
|
-
When ExtractionOrchestrator fetches large datasets (e.g., 50,000 records), all records are loaded into memory. Before writing to destination (SFTP/S3), you can split these records into multiple smaller files for better manageability, parallel processing, and downstream system compatibility.
|
|
229
|
-
|
|
230
|
-
### When to Split Files
|
|
231
|
-
|
|
232
|
-
- ✅ **Extraction returns >10k records** - Split into manageable chunks
|
|
233
|
-
- ✅ **Downstream system has file size limits** - Respect partner constraints
|
|
234
|
-
- ✅ **Parallel processing desired** - Write multiple files concurrently
|
|
235
|
-
- ✅ **Network reliability concerns** - Smaller files = better retry granularity
|
|
236
|
-
- ✅ **Audit trail requirements** - Track processing per file
|
|
237
|
-
|
|
238
|
-
### Basic File Splitting Pattern
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
import { Buffer } from 'node:buffer';
|
|
242
|
-
import { ExtractionOrchestrator, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
243
|
-
|
|
244
|
-
const startTime = Date.now();
|
|
245
|
-
log.info('📦 Starting extraction with file splitting');
|
|
246
|
-
|
|
247
|
-
// STEP 1: Extract all records (loads into memory)
|
|
248
|
-
const result = await orchestrator.extract({
|
|
249
|
-
query: virtualPositionsQuery,
|
|
250
|
-
variables: { retailerId: 'my-retailer', updatedAfter: lastRunTime },
|
|
251
|
-
pagination: { pageSize: 200, maxRecords: 50000 },
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
log.info('✅ Extraction complete', {
|
|
255
|
-
recordCount: result.data.length,
|
|
256
|
-
duration: Date.now() - startTime
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// result.data contains all 50,000 records in memory
|
|
260
|
-
|
|
261
|
-
// STEP 2: Configure chunking
|
|
262
|
-
const RECORDS_PER_FILE = 1000; // Configurable chunk size
|
|
263
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
264
|
-
|
|
265
|
-
// STEP 3: Split records into chunks
|
|
266
|
-
const chunks: any[][] = [];
|
|
267
|
-
for (let i = 0; i < result.data.length; i += RECORDS_PER_FILE) {
|
|
268
|
-
chunks.push(result.data.slice(i, i + RECORDS_PER_FILE));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
log.info('📂 File splitting configured', {
|
|
272
|
-
totalRecords: result.data.length,
|
|
273
|
-
recordsPerFile: RECORDS_PER_FILE,
|
|
274
|
-
filesToCreate: chunks.length,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// STEP 4: Generate files with configurable naming pattern
|
|
278
|
-
const filePromises = chunks.map((chunk, index) => {
|
|
279
|
-
// File naming pattern: entity-timestamp-part-NNN.format
|
|
280
|
-
const partNumber = String(index + 1).padStart(3, '0'); // 001, 002, 003...
|
|
281
|
-
const filename = `virtual-positions-${timestamp}-part-${partNumber}.xml`;
|
|
282
|
-
|
|
283
|
-
// Transform chunk to desired format (XML, CSV, JSON)
|
|
284
|
-
const xmlContent = xmlBuilder.build({ records: chunk });
|
|
285
|
-
|
|
286
|
-
// Return upload promise
|
|
287
|
-
return sftp.uploadFile(
|
|
288
|
-
`/outbound/${filename}`,
|
|
289
|
-
Buffer.from(xmlContent, 'utf8'),
|
|
290
|
-
{ encoding: 'utf8', overwrite: false }
|
|
291
|
-
);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// STEP 5: Write all files in parallel
|
|
295
|
-
const uploadStartTime = Date.now();
|
|
296
|
-
await Promise.all(filePromises);
|
|
297
|
-
|
|
298
|
-
log.info('✅ File splitting complete', {
|
|
299
|
-
filesCreated: chunks.length,
|
|
300
|
-
totalRecords: result.data.length,
|
|
301
|
-
recordsPerFile: RECORDS_PER_FILE,
|
|
302
|
-
uploadDuration: Date.now() - uploadStartTime,
|
|
303
|
-
totalDuration: Date.now() - startTime
|
|
304
|
-
});
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### Configurable File Naming Patterns
|
|
308
|
-
|
|
309
|
-
Support multiple naming patterns via configuration:
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
// Configuration interface
|
|
313
|
-
interface FileSplittingConfig {
|
|
314
|
-
enabled: boolean;
|
|
315
|
-
recordsPerFile: number;
|
|
316
|
-
namingPattern: 'sequential' | 'timestamp' | 'range' | 'custom';
|
|
317
|
-
customPattern?: (index: number, chunk: any[], total: number) => string;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Pattern examples
|
|
321
|
-
const patterns = {
|
|
322
|
-
// Pattern 1: Sequential numbering
|
|
323
|
-
sequential: (index: number) =>
|
|
324
|
-
`virtual-positions-part-${String(index + 1).padStart(3, '0')}.xml`,
|
|
325
|
-
|
|
326
|
-
// Pattern 2: Timestamp-based
|
|
327
|
-
timestamp: (index: number) =>
|
|
328
|
-
`virtual-positions-${new Date().toISOString()}-${index + 1}.xml`,
|
|
329
|
-
|
|
330
|
-
// Pattern 3: Record range
|
|
331
|
-
range: (index: number, chunk: any[]) => {
|
|
332
|
-
const start = index * chunk.length + 1;
|
|
333
|
-
const end = start + chunk.length - 1;
|
|
334
|
-
return `virtual-positions-records-${start}-${end}.xml`;
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
// Pattern 4: Custom with metadata
|
|
338
|
-
custom: (index: number, chunk: any[], totalChunks: number) =>
|
|
339
|
-
`VP_${new Date().toISOString().split('T')[0]}_${index + 1}_of_${totalChunks}.xml`,
|
|
340
|
-
};
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Complete Example: Extraction with File Splitting
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
import { schedule, fn, MemoryInterpreter } from '@versori/run';
|
|
347
|
-
import { Buffer } from 'node:buffer';
|
|
348
|
-
import {
|
|
349
|
-
ExtractionOrchestrator,
|
|
350
|
-
createClient,
|
|
351
|
-
SftpDataSource,
|
|
352
|
-
VersoriKVAdapter,
|
|
353
|
-
XMLBuilder,
|
|
354
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
355
|
-
|
|
356
|
-
export const virtualPositionsExtraction = schedule(
|
|
357
|
-
'virtual-positions-hourly',
|
|
358
|
-
'0 * * * *'
|
|
359
|
-
).then(
|
|
360
|
-
fn('extract', async (ctx) => {
|
|
361
|
-
const { log, activation, openKv } = ctx;
|
|
362
|
-
const startTime = Date.now();
|
|
363
|
-
|
|
364
|
-
log.info('📦 Starting virtual positions extraction');
|
|
365
|
-
|
|
366
|
-
// Configuration from activation variables
|
|
367
|
-
const FILE_SPLITTING_ENABLED = activation.getVariable('fileSplittingEnabled') !== 'false';
|
|
368
|
-
const RECORDS_PER_FILE = parseInt(activation.getVariable('recordsPerFile') || '1000', 10);
|
|
369
|
-
const FILE_NAMING_PATTERN = activation.getVariable('fileNamingPattern') || 'sequential';
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
// Initialize services
|
|
373
|
-
const client = await createClient(ctx);
|
|
374
|
-
log.info('✅ Client initialized');
|
|
375
|
-
|
|
376
|
-
const kv = new VersoriKVAdapter(openKv(':project:'));
|
|
377
|
-
const orchestrator = new ExtractionOrchestrator(client, log);
|
|
378
|
-
|
|
379
|
-
// Extract records (all loaded into memory)
|
|
380
|
-
const extractionStartTime = Date.now();
|
|
381
|
-
const result = await orchestrator.extract({
|
|
382
|
-
query: virtualPositionsQuery,
|
|
383
|
-
variables: {
|
|
384
|
-
retailerId: activation.getVariable('fluentRetailerId'),
|
|
385
|
-
updatedAfter: await kv.get(['extraction', 'lastRunTime']) || '2025-01-01T00:00:00Z',
|
|
386
|
-
},
|
|
387
|
-
pagination: { pageSize: 200, maxRecords: 50000 },
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
log.info('✅ Extraction complete', {
|
|
391
|
-
recordCount: result.data.length,
|
|
392
|
-
fileSplittingEnabled: FILE_SPLITTING_ENABLED,
|
|
393
|
-
extractionDuration: Date.now() - extractionStartTime
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// Initialize SFTP
|
|
397
|
-
const sftp = new SftpDataSource({
|
|
398
|
-
type: 'SFTP_XML',
|
|
399
|
-
connectionId: 'sftp-extractions',
|
|
400
|
-
name: 'extraction-sftp',
|
|
401
|
-
settings: {
|
|
402
|
-
host: activation.getVariable('sftpHost'),
|
|
403
|
-
port: parseInt(activation.getVariable('sftpPort') || '22', 10),
|
|
404
|
-
username: activation.getVariable('sftpUsername'),
|
|
405
|
-
password: activation.getVariable('sftpPassword'),
|
|
406
|
-
remotePath: '/outbound/',
|
|
407
|
-
requireAbsolutePaths: true,
|
|
408
|
-
},
|
|
409
|
-
}, log);
|
|
410
|
-
|
|
411
|
-
await sftp.validateConnection();
|
|
412
|
-
log.info('✅ SFTP connection validated');
|
|
413
|
-
|
|
414
|
-
// File splitting logic
|
|
415
|
-
if (FILE_SPLITTING_ENABLED && result.data.length > RECORDS_PER_FILE) {
|
|
416
|
-
const splittingStartTime = Date.now();
|
|
417
|
-
|
|
418
|
-
// Split into chunks
|
|
419
|
-
const chunks: any[][] = [];
|
|
420
|
-
for (let i = 0; i < result.data.length; i += RECORDS_PER_FILE) {
|
|
421
|
-
chunks.push(result.data.slice(i, i + RECORDS_PER_FILE));
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
log.info('📂 Splitting extraction into multiple files', {
|
|
425
|
-
totalRecords: result.data.length,
|
|
426
|
-
recordsPerFile: RECORDS_PER_FILE,
|
|
427
|
-
filesToCreate: chunks.length,
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// Generate files
|
|
431
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
432
|
-
const xmlBuilder = new XMLBuilder();
|
|
433
|
-
|
|
434
|
-
const filePromises = chunks.map(async (chunk, index) => {
|
|
435
|
-
// Generate filename based on pattern
|
|
436
|
-
let filename: string;
|
|
437
|
-
const partNumber = String(index + 1).padStart(3, '0');
|
|
438
|
-
|
|
439
|
-
switch (FILE_NAMING_PATTERN) {
|
|
440
|
-
case 'timestamp':
|
|
441
|
-
filename = `virtual-positions-${timestamp}-${partNumber}.xml`;
|
|
442
|
-
break;
|
|
443
|
-
case 'range':
|
|
444
|
-
const start = index * RECORDS_PER_FILE + 1;
|
|
445
|
-
const end = Math.min(start + chunk.length - 1, result.data.length);
|
|
446
|
-
filename = `virtual-positions-records-${start}-${end}.xml`;
|
|
447
|
-
break;
|
|
448
|
-
case 'sequential':
|
|
449
|
-
default:
|
|
450
|
-
filename = `virtual-positions-part-${partNumber}.xml`;
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Build XML content
|
|
455
|
-
const xmlContent = xmlBuilder.build({ virtualPositions: chunk });
|
|
456
|
-
|
|
457
|
-
// Upload to SFTP
|
|
458
|
-
await sftp.uploadFile(
|
|
459
|
-
`/outbound/${filename}`,
|
|
460
|
-
Buffer.from(xmlContent, 'utf8'),
|
|
461
|
-
{ encoding: 'utf8', overwrite: false }
|
|
462
|
-
);
|
|
463
|
-
|
|
464
|
-
return { filename, recordCount: chunk.length };
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
// Write all files in parallel
|
|
468
|
-
const uploadedFiles = await Promise.all(filePromises);
|
|
469
|
-
|
|
470
|
-
log.info('✅ File splitting complete', {
|
|
471
|
-
filesCreated: uploadedFiles.length,
|
|
472
|
-
totalRecords: result.data.length,
|
|
473
|
-
files: uploadedFiles,
|
|
474
|
-
splittingDuration: Date.now() - splittingStartTime,
|
|
475
|
-
totalDuration: Date.now() - startTime
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
success: true,
|
|
480
|
-
extractionMode: 'incremental',
|
|
481
|
-
totalRecords: result.data.length,
|
|
482
|
-
filesCreated: uploadedFiles.length,
|
|
483
|
-
files: uploadedFiles,
|
|
484
|
-
duration: Date.now() - startTime
|
|
485
|
-
};
|
|
486
|
-
} else {
|
|
487
|
-
// Single file (no splitting)
|
|
488
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
489
|
-
const filename = `virtual-positions-${timestamp}.xml`;
|
|
490
|
-
const xmlBuilder = new XMLBuilder();
|
|
491
|
-
const xmlContent = xmlBuilder.build({ virtualPositions: result.data });
|
|
492
|
-
|
|
493
|
-
await sftp.uploadFile(
|
|
494
|
-
`/outbound/${filename}`,
|
|
495
|
-
Buffer.from(xmlContent, 'utf8'),
|
|
496
|
-
{ encoding: 'utf8', overwrite: false }
|
|
497
|
-
);
|
|
498
|
-
|
|
499
|
-
log.info('✅ Single file extraction complete', {
|
|
500
|
-
filename,
|
|
501
|
-
recordCount: result.data.length,
|
|
502
|
-
duration: Date.now() - startTime
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
return {
|
|
506
|
-
success: true,
|
|
507
|
-
extractionMode: 'incremental',
|
|
508
|
-
totalRecords: result.data.length,
|
|
509
|
-
filesCreated: 1,
|
|
510
|
-
files: [{ filename, recordCount: result.data.length }],
|
|
511
|
-
duration: Date.now() - startTime
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
} catch (error: any) {
|
|
515
|
-
log.error('❌ Extraction failed', {
|
|
516
|
-
message: error instanceof Error ? error.message : String(error),
|
|
517
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
518
|
-
errorType: error instanceof Error ? error.constructor.name : 'Error',
|
|
519
|
-
duration: Date.now() - startTime
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
return {
|
|
523
|
-
success: false,
|
|
524
|
-
error: error.message,
|
|
525
|
-
duration: Date.now() - startTime
|
|
526
|
-
};
|
|
527
|
-
} finally {
|
|
528
|
-
if (sftp) {
|
|
529
|
-
await sftp.dispose();
|
|
530
|
-
log.info('✅ SFTP connection disposed');
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
})
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
// Export with MemoryInterpreter for Versori platform
|
|
537
|
-
export const interpreter = new MemoryInterpreter({
|
|
538
|
-
workflows: [virtualPositionsExtraction]
|
|
539
|
-
});
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
### Date Range Splitting for Very Large Datasets
|
|
543
|
-
|
|
544
|
-
For extractions spanning multiple years (e.g., historical dumps), split the date range and run multiple smaller extractions:
|
|
545
|
-
|
|
546
|
-
```typescript
|
|
547
|
-
// Split large date range into manageable chunks
|
|
548
|
-
async function extractHistoricalData(
|
|
549
|
-
startDate: string, // "2020-01-01"
|
|
550
|
-
endDate: string, // "2025-01-01"
|
|
551
|
-
chunkMonths: number = 1, // Extract 1 month at a time
|
|
552
|
-
log: any
|
|
553
|
-
) {
|
|
554
|
-
const overallStartTime = Date.now();
|
|
555
|
-
const results = [];
|
|
556
|
-
let currentStart = new Date(startDate);
|
|
557
|
-
const finalEnd = new Date(endDate);
|
|
558
|
-
|
|
559
|
-
log.info('📦 Starting historical data extraction', {
|
|
560
|
-
startDate,
|
|
561
|
-
endDate,
|
|
562
|
-
chunkMonths,
|
|
563
|
-
estimatedChunks: Math.ceil((finalEnd.getTime() - currentStart.getTime()) / (chunkMonths * 30 * 24 * 60 * 60 * 1000))
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
while (currentStart < finalEnd) {
|
|
567
|
-
const chunkStartTime = Date.now();
|
|
568
|
-
|
|
569
|
-
// Calculate chunk end date
|
|
570
|
-
const currentEnd = new Date(currentStart);
|
|
571
|
-
currentEnd.setMonth(currentEnd.getMonth() + chunkMonths);
|
|
572
|
-
|
|
573
|
-
// Ensure we don't exceed final end date
|
|
574
|
-
if (currentEnd > finalEnd) {
|
|
575
|
-
currentEnd.setTime(finalEnd.getTime());
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
log.info('📂 Extracting date range chunk', {
|
|
579
|
-
chunkNumber: results.length + 1,
|
|
580
|
-
startDate: currentStart.toISOString(),
|
|
581
|
-
endDate: currentEnd.toISOString(),
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
// Extract for this date range
|
|
585
|
-
const result = await orchestrator.extract({
|
|
586
|
-
query: virtualPositionsQuery,
|
|
587
|
-
variables: {
|
|
588
|
-
retailerId: 'my-retailer',
|
|
589
|
-
updatedAfter: currentStart.toISOString(),
|
|
590
|
-
updatedBefore: currentEnd.toISOString(),
|
|
591
|
-
},
|
|
592
|
-
pagination: { pageSize: 200, maxRecords: 50000 },
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
log.info('✅ Chunk extraction complete', {
|
|
596
|
-
chunkNumber: results.length + 1,
|
|
597
|
-
recordCount: result.data.length,
|
|
598
|
-
chunkDuration: Date.now() - chunkStartTime
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
// Split this chunk into files if needed
|
|
602
|
-
if (result.data.length > RECORDS_PER_FILE) {
|
|
603
|
-
const chunks: any[][] = [];
|
|
604
|
-
for (let i = 0; i < result.data.length; i += RECORDS_PER_FILE) {
|
|
605
|
-
chunks.push(result.data.slice(i, i + RECORDS_PER_FILE));
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Write chunks in parallel
|
|
609
|
-
const timestamp = currentStart.toISOString().split('T')[0];
|
|
610
|
-
await Promise.all(
|
|
611
|
-
chunks.map((chunk, index) => {
|
|
612
|
-
const filename = `historical-${timestamp}-part-${String(index + 1).padStart(3, '0')}.xml`;
|
|
613
|
-
return sftp.uploadFile(`/outbound/${filename}`, Buffer.from(xmlBuilder.build(chunk), 'utf8'));
|
|
614
|
-
})
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
results.push({
|
|
619
|
-
startDate: currentStart.toISOString(),
|
|
620
|
-
endDate: currentEnd.toISOString(),
|
|
621
|
-
recordCount: result.data.length,
|
|
622
|
-
duration: Date.now() - chunkStartTime
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
// Move to next chunk
|
|
626
|
-
currentStart = new Date(currentEnd);
|
|
627
|
-
|
|
628
|
-
// Rate limiting - wait between chunks
|
|
629
|
-
log.info('⏱️ Rate limiting delay (5 seconds)');
|
|
630
|
-
await new Promise(resolve => setTimeout(resolve, 5000)); // 5 second delay
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
const totalRecords = results.reduce((sum, r) => sum + r.recordCount, 0);
|
|
634
|
-
const totalDuration = Date.now() - overallStartTime;
|
|
635
|
-
|
|
636
|
-
log.info('✅ Historical extraction complete', {
|
|
637
|
-
totalChunks: results.length,
|
|
638
|
-
totalRecords,
|
|
639
|
-
totalDuration,
|
|
640
|
-
averageRecordsPerChunk: Math.round(totalRecords / results.length)
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
return results;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Usage: Extract 5 years of data, 1 month at a time
|
|
647
|
-
const historicalResults = await extractHistoricalData('2020-01-01', '2025-01-01', 1, log);
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
### Configuration Variables for File Splitting
|
|
651
|
-
|
|
652
|
-
Add these to Versori activation variables:
|
|
653
|
-
|
|
654
|
-
```bash
|
|
655
|
-
# File Splitting Configuration
|
|
656
|
-
FILE_SPLITTING_ENABLED=true # Enable/disable file splitting
|
|
657
|
-
RECORDS_PER_FILE=1000 # Records per file (default: 1000)
|
|
658
|
-
FILE_NAMING_PATTERN=sequential # Naming pattern: sequential | timestamp | range
|
|
659
|
-
MAX_RECORDS_PER_EXTRACTION=50000 # Maximum records to extract in one run
|
|
660
|
-
|
|
661
|
-
# Date Range Splitting (for large historical extractions)
|
|
662
|
-
DATE_RANGE_CHUNK_MONTHS=1 # Months per extraction chunk (default: 1)
|
|
663
|
-
RATE_LIMIT_DELAY_MS=5000 # Delay between chunks (milliseconds)
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Benefits of File Splitting
|
|
667
|
-
|
|
668
|
-
- ✅ **Memory efficiency** - Process large datasets without memory overflow
|
|
669
|
-
- ✅ **Parallel writes** - Multiple files written concurrently (faster)
|
|
670
|
-
- ✅ **Better error recovery** - Retry individual files vs entire extraction
|
|
671
|
-
- ✅ **Downstream compatibility** - Honor partner file size limits
|
|
672
|
-
- ✅ **Audit granularity** - Track processing per file
|
|
673
|
-
- ✅ **Network resilience** - Smaller files = better upload success rate
|
|
674
|
-
|
|
675
|
-
### Limitations
|
|
676
|
-
|
|
677
|
-
- All records must fit in memory during extraction (ExtractionOrchestrator loads all pages)
|
|
678
|
-
- File splitting happens post-extraction (not during pagination)
|
|
679
|
-
- Parallel writes limited by available memory and network bandwidth
|
|
680
|
-
- SFTP connection pool size may limit concurrency (default: 10 connections)
|
|
681
|
-
|
|
682
|
-
---
|
|
683
|
-
|
|
684
|
-
## Historical Data Extraction via Date Range
|
|
685
|
-
|
|
686
|
-
### Overview
|
|
687
|
-
|
|
688
|
-
There is **no separate "historical mode"** in the SDK. To extract historical data (e.g., all records from 2020 onwards), use **Date Range mode** with a very old start date and appropriate safeguards.
|
|
689
|
-
|
|
690
|
-
### How to Extract Historical Data
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
// Historical extraction is just Date Range with old start date
|
|
694
|
-
await client.graphql({
|
|
695
|
-
query: PRODUCTS_QUERY,
|
|
696
|
-
variables: {
|
|
697
|
-
retailerId: 'my-retailer',
|
|
698
|
-
updatedAfter: '1970-01-01T00:00:00Z', // Very old date = "all records"
|
|
699
|
-
updatedBefore: new Date().toISOString(), // Up to now
|
|
700
|
-
first: 200,
|
|
701
|
-
},
|
|
702
|
-
pagination: { maxRecords: 50000 },
|
|
703
|
-
});
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
### Required Safeguards for Historical Extraction
|
|
707
|
-
|
|
708
|
-
1. **Date Range Splitting** - Split into smaller chunks (monthly/quarterly)
|
|
709
|
-
2. **File Splitting** - Split large results into multiple files
|
|
710
|
-
3. **Rate Limiting** - Add delays between chunks
|
|
711
|
-
4. **Validation** - Verify date ranges before execution
|
|
712
|
-
5. **Monitoring** - Track progress and alert on anomalies
|
|
713
|
-
6. **Approval** - Get sign-off before running on production
|
|
714
|
-
|
|
715
|
-
### Recommended Approach: Chunked Date Range Extraction
|
|
716
|
-
|
|
717
|
-
```bash
|
|
718
|
-
#!/bin/bash
|
|
719
|
-
# Safe historical extraction via chunked date ranges
|
|
720
|
-
|
|
721
|
-
START_DATE="2020-01-01"
|
|
722
|
-
END_DATE="2025-01-01"
|
|
723
|
-
|
|
724
|
-
# Extract one month at a time
|
|
725
|
-
current=$START_DATE
|
|
726
|
-
while [[ "$current" < "$END_DATE" ]]; do
|
|
727
|
-
# Calculate month end
|
|
728
|
-
monthEnd=$(date -d "$current + 1 month" +%Y-%m-%d)
|
|
729
|
-
|
|
730
|
-
echo "Extracting $current to $monthEnd..."
|
|
731
|
-
|
|
732
|
-
# Trigger date range extraction
|
|
733
|
-
curl -X POST https://versori-webhook.com/extract \
|
|
734
|
-
-H "Content-Type: application/json" \
|
|
735
|
-
-d "{
|
|
736
|
-
\"extractionMode\": \"dateRange\",
|
|
737
|
-
\"startDate\": \"${current}T00:00:00Z\",
|
|
738
|
-
\"endDate\": \"${monthEnd}T00:00:00Z\",
|
|
739
|
-
\"fileSplittingEnabled\": true,
|
|
740
|
-
\"recordsPerFile\": 1000
|
|
741
|
-
}"
|
|
742
|
-
|
|
743
|
-
# Rate limiting - wait 60 seconds between chunks
|
|
744
|
-
sleep 60
|
|
745
|
-
|
|
746
|
-
# Move to next month
|
|
747
|
-
current=$monthEnd
|
|
748
|
-
done
|
|
749
|
-
|
|
750
|
-
echo "Historical extraction complete"
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
### Migration to Incremental After Historical Load
|
|
754
|
-
|
|
755
|
-
After completing a one-time historical extraction, switch to incremental mode for ongoing syncs:
|
|
756
|
-
|
|
757
|
-
```json
|
|
758
|
-
{
|
|
759
|
-
"extractionMode": "incremental",
|
|
760
|
-
"fallbackStartDate": "2025-01-22T00:00:00Z",
|
|
761
|
-
"pageSize": 200,
|
|
762
|
-
"maxRecords": 10000
|
|
763
|
-
}
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
---
|
|
767
|
-
|
|
768
|
-
## Decision Tree: Which Mode to Use?
|
|
769
|
-
|
|
770
|
-
```
|
|
771
|
-
Start Here
|
|
772
|
-
│
|
|
773
|
-
├─ Need recurring extractions? ─────────► Use INCREMENTAL ✅
|
|
774
|
-
│ (hourly/daily/every 15 min)
|
|
775
|
-
│ Tracks state, auto-recovery
|
|
776
|
-
│
|
|
777
|
-
└─ Need specific date range? ────────────► Use DATE RANGE ⚠️
|
|
778
|
-
│ (one-time, validate range)
|
|
779
|
-
│
|
|
780
|
-
├─ Date range < 30 days? ──────────► Single DATE RANGE run
|
|
781
|
-
│ + file splitting if >10k records
|
|
782
|
-
│
|
|
783
|
-
└─ Date range > 30 days? ──────────► Split into monthly chunks
|
|
784
|
-
│ + file splitting per chunk
|
|
785
|
-
│ + rate limiting between chunks
|
|
786
|
-
│
|
|
787
|
-
└─ Historical (all data)? ────► DATE RANGE with old start date
|
|
788
|
-
+ chunked approach (monthly)
|
|
789
|
-
+ approval required
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
---
|
|
793
|
-
|
|
794
|
-
## Monitoring & Alerts
|
|
795
|
-
|
|
796
|
-
Set up alerts for extraction volumes:
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
// In extraction workflow
|
|
800
|
-
const recordCount = edges.length;
|
|
801
|
-
const ALERT_THRESHOLD = 50000;
|
|
802
|
-
|
|
803
|
-
log.info('📊 Checking extraction volume', { recordCount, threshold: ALERT_THRESHOLD });
|
|
804
|
-
|
|
805
|
-
if (recordCount > ALERT_THRESHOLD) {
|
|
806
|
-
log.error('❌ Extraction volume exceeded threshold', {
|
|
807
|
-
recordCount,
|
|
808
|
-
threshold: ALERT_THRESHOLD,
|
|
809
|
-
percentageOver: Math.round(((recordCount - ALERT_THRESHOLD) / ALERT_THRESHOLD) * 100),
|
|
810
|
-
mode: extractionMode,
|
|
811
|
-
recommendation: 'Switch to incremental mode or reduce date range',
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
// Send alert to monitoring system
|
|
815
|
-
await sendAlert({
|
|
816
|
-
severity: 'high',
|
|
817
|
-
message: `Extraction returned ${recordCount} records (threshold: ${ALERT_THRESHOLD})`,
|
|
818
|
-
mode: extractionMode,
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
log.info('✅ Alert sent to monitoring system');
|
|
822
|
-
} else {
|
|
823
|
-
log.info('✅ Extraction volume within acceptable limits', {
|
|
824
|
-
recordCount,
|
|
825
|
-
threshold: ALERT_THRESHOLD,
|
|
826
|
-
percentageUsed: Math.round((recordCount / ALERT_THRESHOLD) * 100)
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
---
|
|
832
|
-
|
|
833
|
-
## Summary & Best Practices
|
|
834
|
-
|
|
835
|
-
### ✅ DO
|
|
836
|
-
|
|
837
|
-
- Use **incremental mode** for all scheduled extractions
|
|
838
|
-
- Validate date ranges before running **dateRange mode**
|
|
839
|
-
- Implement **file splitting** for large extractions (>10k records)
|
|
840
|
-
- Use **parallel writes** with `Promise.all()` for split files
|
|
841
|
-
- Monitor extraction volumes and set alerts
|
|
842
|
-
- Test on staging before production
|
|
843
|
-
- Use overlap buffer (60s) to prevent gaps in incremental mode
|
|
844
|
-
- Track state with VersoriKV
|
|
845
|
-
- Split large historical extractions into monthly chunks
|
|
846
|
-
- Add rate limiting between extraction chunks
|
|
847
|
-
|
|
848
|
-
### ❌ DON'T
|
|
849
|
-
|
|
850
|
-
- Schedule **dateRange** extractions (incremental only)
|
|
851
|
-
- Use date ranges > 30 days without chunking
|
|
852
|
-
- Skip validation checks for date ranges
|
|
853
|
-
- Ignore volume alerts (>50k records)
|
|
854
|
-
- Forget to implement file splitting for large results
|
|
855
|
-
- Run historical extractions without approval and monitoring
|
|
856
|
-
|
|
857
|
-
---
|
|
858
|
-
|
|
859
|
-
## ExtractionOrchestrator (SDK v0.1.27+)
|
|
860
|
-
|
|
861
|
-
The SDK includes **ExtractionOrchestrator** - a high-level service that simplifies extraction workflows with built-in mode handling, pagination, and output management.
|
|
862
|
-
|
|
863
|
-
### Why Use ExtractionOrchestrator?
|
|
864
|
-
|
|
865
|
-
**Instead of manually implementing:**
|
|
866
|
-
|
|
867
|
-
- Mode detection (incremental/dateRange)
|
|
868
|
-
- Pagination loops
|
|
869
|
-
- Path-based field extraction
|
|
870
|
-
- Output formatting (CSV/JSON/Parquet)
|
|
871
|
-
- S3/SFTP uploads
|
|
872
|
-
- Error handling
|
|
873
|
-
|
|
874
|
-
**ExtractionOrchestrator handles it all:**
|
|
875
|
-
|
|
876
|
-
```typescript
|
|
877
|
-
import { ExtractionOrchestrator, createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
878
|
-
|
|
879
|
-
const startTime = Date.now();
|
|
880
|
-
log.info('📦 Initializing ExtractionOrchestrator');
|
|
881
|
-
|
|
882
|
-
const client = await createClient(ctx);
|
|
883
|
-
const orchestrator = new ExtractionOrchestrator(client, log);
|
|
884
|
-
|
|
885
|
-
log.info('✅ Orchestrator initialized');
|
|
886
|
-
|
|
887
|
-
// Both modes supported with single interface
|
|
888
|
-
const result = await orchestrator.extract({
|
|
889
|
-
query: virtualPositionsQuery,
|
|
890
|
-
variables: { retailerId: 'my-retailer' },
|
|
891
|
-
|
|
892
|
-
// Mode: 'incremental' (scheduled) or 'dateRange' (ad-hoc)
|
|
893
|
-
extractionMode: 'incremental',
|
|
894
|
-
stateKey: 'virtual-positions-extraction',
|
|
895
|
-
|
|
896
|
-
// Pagination handled automatically
|
|
897
|
-
pagination: {
|
|
898
|
-
pageSize: 200,
|
|
899
|
-
maxRecords: 10000,
|
|
900
|
-
},
|
|
901
|
-
|
|
902
|
-
// Output format and destination
|
|
903
|
-
outputFormat: 'csv',
|
|
904
|
-
outputDestination: {
|
|
905
|
-
type: 's3',
|
|
906
|
-
bucket: 'my-extracts',
|
|
907
|
-
key: 'virtual-positions/hourly/{{timestamp}}.csv',
|
|
908
|
-
},
|
|
909
|
-
|
|
910
|
-
// Field extraction from nested paths
|
|
911
|
-
fieldPaths: {
|
|
912
|
-
position_ref: 'ref',
|
|
913
|
-
quantity: 'quantity',
|
|
914
|
-
location_ref: 'locationLink.ref',
|
|
915
|
-
location_name: 'locationLink.name',
|
|
916
|
-
},
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
log.info('✅ Extraction and upload complete', {
|
|
920
|
-
recordCount: result.recordCount,
|
|
921
|
-
outputFile: result.outputFile,
|
|
922
|
-
duration: Date.now() - startTime
|
|
923
|
-
});
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
### Features
|
|
927
|
-
|
|
928
|
-
- **Auto-pagination**: Handles cursor-based pagination automatically
|
|
929
|
-
- **Mode support**: Both modes (incremental, dateRange)
|
|
930
|
-
- **State management**: Tracks last run timestamps for incremental mode
|
|
931
|
-
- **Path extraction**: Extracts nested fields from GraphQL responses
|
|
932
|
-
- **Multi-format**: Outputs CSV, JSON, or Parquet
|
|
933
|
-
- **Validation**: Built-in query and response validation
|
|
934
|
-
- **Error recovery**: Graceful failure handling with detailed logs
|
|
935
|
-
- **File splitting**: Post-extraction chunking for large datasets (add manually, see File Splitting section above)
|
|
936
|
-
|
|
937
|
-
### Example: Incremental Extraction with ExtractionOrchestrator
|
|
938
|
-
|
|
939
|
-
```typescript
|
|
940
|
-
import { schedule, fn, MemoryInterpreter } from '@versori/run';
|
|
941
|
-
import { Buffer } from 'node:buffer'; // Required for Deno/Versori runtime
|
|
942
|
-
import {
|
|
943
|
-
ExtractionOrchestrator,
|
|
944
|
-
createClient,
|
|
945
|
-
VersoriKVAdapter,
|
|
946
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
947
|
-
|
|
948
|
-
export const hourlyExtraction = schedule('hourly-virtual-positions', '0 * * * *').then(
|
|
949
|
-
fn('extract', async (ctx) => {
|
|
950
|
-
const { log, openKv, env } = ctx;
|
|
951
|
-
const startTime = Date.now();
|
|
952
|
-
|
|
953
|
-
log.info('📦 Starting hourly virtual positions extraction');
|
|
954
|
-
|
|
955
|
-
const client = await createClient(ctx);
|
|
956
|
-
log.info('✅ Client initialized');
|
|
957
|
-
|
|
958
|
-
const kv = new VersoriKVAdapter(openKv(':project:'));
|
|
959
|
-
const orchestrator = new ExtractionOrchestrator(client, log);
|
|
960
|
-
|
|
961
|
-
const result = await orchestrator.extract({
|
|
962
|
-
query: `
|
|
963
|
-
query GetVirtualPositions($retailerId: String!, $updatedAfter: String, $first: Int, $after: String) {
|
|
964
|
-
virtualPositions(retailerId: $retailerId, updatedAfter: $updatedAfter, first: $first, after: $after) {
|
|
965
|
-
edges {
|
|
966
|
-
node {
|
|
967
|
-
ref
|
|
968
|
-
quantity
|
|
969
|
-
productRef
|
|
970
|
-
locationLink { ref name }
|
|
971
|
-
updatedOn
|
|
972
|
-
}
|
|
973
|
-
cursor
|
|
974
|
-
}
|
|
975
|
-
pageInfo {
|
|
976
|
-
hasNextPage
|
|
977
|
-
# Note: Fluent doesn't return endCursor - cursors are in edges[].cursor
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
`,
|
|
982
|
-
variables: { retailerId: env.FLUENT_RETAILER_ID },
|
|
983
|
-
extractionMode: 'incremental',
|
|
984
|
-
stateAdapter: kv,
|
|
985
|
-
stateKey: 'hourly-extraction',
|
|
986
|
-
fallbackStartDate: '2025-01-01T00:00:00Z',
|
|
987
|
-
pagination: { pageSize: 200, maxRecords: 10000 },
|
|
988
|
-
outputFormat: 'csv',
|
|
989
|
-
outputDestination: {
|
|
990
|
-
type: 's3',
|
|
991
|
-
bucket: env.S3_BUCKET,
|
|
992
|
-
key: `virtual-positions/{{date}}/{{timestamp}}.csv`,
|
|
993
|
-
config: {
|
|
994
|
-
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
995
|
-
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
996
|
-
region: env.AWS_REGION,
|
|
997
|
-
},
|
|
998
|
-
},
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
log.info('✅ Extraction complete', {
|
|
1002
|
-
success: result.success,
|
|
1003
|
-
recordCount: result.recordCount,
|
|
1004
|
-
outputFile: result.outputFile,
|
|
1005
|
-
duration: Date.now() - startTime
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1008
|
-
return {
|
|
1009
|
-
success: result.success,
|
|
1010
|
-
recordCount: result.recordCount,
|
|
1011
|
-
outputFile: result.outputFile,
|
|
1012
|
-
duration: Date.now() - startTime
|
|
1013
|
-
};
|
|
1014
|
-
})
|
|
1015
|
-
);
|
|
1016
|
-
|
|
1017
|
-
// Export with MemoryInterpreter for Versori platform
|
|
1018
|
-
export const interpreter = new MemoryInterpreter({
|
|
1019
|
-
workflows: [hourlyExtraction]
|
|
1020
|
-
});
|
|
1021
|
-
```
|
|
1022
|
-
|
|
1023
|
-
### When to Use ExtractionOrchestrator
|
|
1024
|
-
|
|
1025
|
-
- **✅ Use for**: New extraction workflows, scheduled extractions, standard use cases
|
|
1026
|
-
- **⚠️ Manual approach**: Complex transformations, custom business logic, non-standard outputs
|
|
1027
|
-
|
|
1028
|
-
See [ExtractionOrchestrator API Reference](../../../../02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md) for complete documentation.
|
|
1029
|
-
|
|
1030
|
-
---
|
|
1031
|
-
|
|
1032
|
-
## See Also
|
|
1033
|
-
|
|
1034
|
-
- [CLI Validation Workflow](../../../../02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md) - Validate queries and mappings
|
|
1035
|
-
- [Production Safety Guide](../../../../02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md) - General safety practices
|
|
1036
|
-
- [GraphQL Query Examples](./graphql-queries/) - Sample extraction queries
|
|
1037
|
-
- [Universal Mapping Guide](../../../../02-CORE-GUIDES/advanced-services/advanced-services-readme.md) - Field mapping documentation
|
|
1038
|
-
- [ExtractionOrchestrator Examples](../../../../03-PATTERN-GUIDES/examples/test-data/03-PATTERN-GUIDES-readme.md) - Complete working examples
|
|
1
|
+
# Extraction Modes Guide
|
|
2
|
+
|
|
3
|
+
**FC Connect SDK - Choosing the Right Extraction Mode**
|
|
4
|
+
|
|
5
|
+
> This guide explains the two extraction modes available in the SDK and when to use each one.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
The FC Connect SDK supports two extraction modes for GraphQL queries:
|
|
12
|
+
|
|
13
|
+
1. **Incremental** - Extract only changed records since last run (recommended for scheduled workflows)
|
|
14
|
+
2. **Date Range** - Extract records within specific date window (for ad-hoc queries, backfills, or historical data)
|
|
15
|
+
|
|
16
|
+
**Note:** "Historical extraction" is simply Date Range mode with a very old start date (e.g., `updatedAfter: '1970-01-01'`). There is no separate "historical mode" - use Date Range with appropriate validation and safeguards.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick Comparison Table
|
|
21
|
+
|
|
22
|
+
| Mode | Use Case | Safety Level | Recommended Frequency | Typical Records | Production Risk |
|
|
23
|
+
| --------------- | --------------------------------------------- | ------------ | --------------------- | --------------- | ----------------------------------- |
|
|
24
|
+
| **Incremental** | Scheduled syncs, recurring extractions | ✅ **Safe** | Hourly/Daily | ~10k | ✅ **Low** - Safe for production |
|
|
25
|
+
| **Date Range** | Ad-hoc audits, backfills, historical dumps | ⚠️ **Risky** | One-time only | ~50k | ⚠️ **Medium** - Requires validation |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Mode 1: Incremental (Recommended) ✅
|
|
30
|
+
|
|
31
|
+
### What It Does
|
|
32
|
+
|
|
33
|
+
Extracts only records updated since the last successful extraction run.
|
|
34
|
+
|
|
35
|
+
### How It Works
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
log.info('📦 Starting incremental extraction');
|
|
40
|
+
|
|
41
|
+
// Load last run timestamp from state
|
|
42
|
+
const lastRunTime = await kv.get(['extraction', 'products', 'lastRunTime']);
|
|
43
|
+
log.info('⏱️ Last run timestamp loaded', { lastRunTime });
|
|
44
|
+
|
|
45
|
+
// Query with buffered timestamp (overlap buffer prevents gaps)
|
|
46
|
+
const bufferedTime = new Date(lastRunTime - 60000).toISOString(); // 60 second buffer
|
|
47
|
+
|
|
48
|
+
const result = await client.graphql({
|
|
49
|
+
query: PRODUCTS_QUERY,
|
|
50
|
+
variables: {
|
|
51
|
+
retailerId: 'my-retailer',
|
|
52
|
+
updatedAfter: bufferedTime, // Only changed records
|
|
53
|
+
first: 200,
|
|
54
|
+
},
|
|
55
|
+
pagination: { maxRecords: 10000 },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
log.info('✅ Extraction complete', {
|
|
59
|
+
recordCount: result.edges.length,
|
|
60
|
+
duration: Date.now() - startTime
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Save new timestamp after successful extraction
|
|
64
|
+
await kv.set(['extraction', 'products', 'lastRunTime'], {
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### When to Use
|
|
70
|
+
|
|
71
|
+
- ✅ **Scheduled extractions** (hourly, daily, every 15 minutes)
|
|
72
|
+
- ✅ **Real-time inventory feeds**
|
|
73
|
+
- ✅ **Order status updates**
|
|
74
|
+
- ✅ **Product catalog syncs**
|
|
75
|
+
- ✅ **Any recurring extraction workflow**
|
|
76
|
+
|
|
77
|
+
### Safety Features
|
|
78
|
+
|
|
79
|
+
- Natural rate limiting via timestamps
|
|
80
|
+
- Overlap buffer prevents missed records
|
|
81
|
+
- State tracking prevents reprocessing
|
|
82
|
+
- Predictable record counts
|
|
83
|
+
- Automatic recovery after failures
|
|
84
|
+
|
|
85
|
+
### Recommended Settings
|
|
86
|
+
|
|
87
|
+
| Entity | Frequency | Max Records | Expected Volume |
|
|
88
|
+
| ------------------------ | ------------ | ----------- | ------------------ |
|
|
89
|
+
| **Products** | Daily | 10,000 | 5k-10k updates/day |
|
|
90
|
+
| **Orders** | Hourly | 5,000 | 100-500/hour |
|
|
91
|
+
| **Fulfillments** | Hourly | 5,000 | 100-500/hour |
|
|
92
|
+
| **Inventory Quantities** | Every 15 min | 10,000 | 500-2k per run |
|
|
93
|
+
| **Inventory Positions** | Daily | 20,000 | 10k-20k per day |
|
|
94
|
+
| **Virtual Positions** | Hourly | 10,000 | 1k-5k per hour |
|
|
95
|
+
|
|
96
|
+
### Configuration Example
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"extractionMode": "incremental",
|
|
101
|
+
"pageSize": 200,
|
|
102
|
+
"maxRecords": 10000,
|
|
103
|
+
"overlapBufferSeconds": 60,
|
|
104
|
+
"fallbackStartDate": "2024-01-01T00:00:00Z"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Mode 2: Date Range (Use Carefully) ⚠️
|
|
111
|
+
|
|
112
|
+
### What It Does
|
|
113
|
+
|
|
114
|
+
Extracts all records updated within a specific date window.
|
|
115
|
+
|
|
116
|
+
### How It Works
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
log.info('📦 Starting date range extraction');
|
|
121
|
+
|
|
122
|
+
// User provides specific date range
|
|
123
|
+
const startDate = ctx.activation?.getVariable('startDate'); // "2025-01-01T00:00:00Z"
|
|
124
|
+
const endDate = ctx.activation?.getVariable('endDate'); // "2025-01-31T23:59:59Z"
|
|
125
|
+
|
|
126
|
+
log.info('⏱️ Date range configured', { startDate, endDate });
|
|
127
|
+
|
|
128
|
+
const result = await client.graphql({
|
|
129
|
+
query: PRODUCTS_QUERY,
|
|
130
|
+
variables: {
|
|
131
|
+
retailerId: 'my-retailer',
|
|
132
|
+
updatedAfter: startDate,
|
|
133
|
+
updatedBefore: endDate, // Bounded range
|
|
134
|
+
first: 200,
|
|
135
|
+
},
|
|
136
|
+
pagination: { maxRecords: 50000 },
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
log.info('✅ Extraction complete', {
|
|
140
|
+
recordCount: result.edges.length,
|
|
141
|
+
duration: Date.now() - startTime
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### When to Use
|
|
146
|
+
|
|
147
|
+
- ⚠️ **One-time data audits** (e.g., "show me all orders from Q4 2024")
|
|
148
|
+
- ⚠️ **Backfilling missed extractions** (e.g., extraction failed for 3 days)
|
|
149
|
+
- ⚠️ **Historical reporting** (e.g., "export last month's inventory changes")
|
|
150
|
+
- ⚠️ **Data quality investigations**
|
|
151
|
+
|
|
152
|
+
### Risks
|
|
153
|
+
|
|
154
|
+
- Can return tens of thousands of records
|
|
155
|
+
- No natural rate limiting
|
|
156
|
+
- Easy to exceed platform limits
|
|
157
|
+
- Can timeout on large datasets
|
|
158
|
+
- Risk of platform overload
|
|
159
|
+
|
|
160
|
+
### Safety Guardrails Required
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// ✅ REQUIRED: Validate date range before extraction
|
|
164
|
+
function validateDateRange(startDate: string, endDate: string, log: any) {
|
|
165
|
+
const start = new Date(startDate);
|
|
166
|
+
const end = new Date(endDate);
|
|
167
|
+
const daysDiff = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
|
|
168
|
+
|
|
169
|
+
log.info('🔍 Validating date range', { startDate, endDate, daysDiff });
|
|
170
|
+
|
|
171
|
+
// Enforce maximum date range
|
|
172
|
+
const MAX_DAYS = 30;
|
|
173
|
+
if (daysDiff > MAX_DAYS) {
|
|
174
|
+
log.error('❌ Date range validation failed', {
|
|
175
|
+
daysDiff,
|
|
176
|
+
maxDays: MAX_DAYS,
|
|
177
|
+
recommendation: 'Split into smaller ranges or use incremental mode'
|
|
178
|
+
});
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Date range too large: ${daysDiff} days (max: ${MAX_DAYS}). ` +
|
|
181
|
+
`Split into smaller ranges or use incremental mode.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
log.info('✅ Date range validation passed', { daysDiff, maxDays: MAX_DAYS });
|
|
186
|
+
return { daysDiff, isValid: true };
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Recommended Limits
|
|
191
|
+
|
|
192
|
+
| Entity | Max Date Range | Max Records | File Splitting |
|
|
193
|
+
| ---------------- | -------------- | ----------- | ----------------- |
|
|
194
|
+
| **Products** | 30 days | 50,000 | Required if > 25k |
|
|
195
|
+
| **Orders** | 7 days | 20,000 | Required if > 10k |
|
|
196
|
+
| **Fulfillments** | 7 days | 20,000 | Required if > 10k |
|
|
197
|
+
| **Inventory** | 14 days | 50,000 | Required if > 25k |
|
|
198
|
+
|
|
199
|
+
### Configuration Example
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"extractionMode": "dateRange",
|
|
204
|
+
"startDate": "2025-01-01T00:00:00Z",
|
|
205
|
+
"endDate": "2025-01-31T23:59:59Z",
|
|
206
|
+
"pageSize": 200,
|
|
207
|
+
"maxRecords": 50000,
|
|
208
|
+
"validateDateRange": true,
|
|
209
|
+
"maxDaysAllowed": 30
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Production Restrictions
|
|
214
|
+
|
|
215
|
+
- ❌ **Never schedule** date range extractions
|
|
216
|
+
- ❌ **Never use in recurring workflows**
|
|
217
|
+
- ✅ **Always validate date range** before execution
|
|
218
|
+
- ✅ **Always monitor execution time** and record counts
|
|
219
|
+
- ✅ **Always implement file splitting** for large results
|
|
220
|
+
- ✅ **Always get approval** before running on production data
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## File Splitting for Large Extractions
|
|
225
|
+
|
|
226
|
+
### Overview
|
|
227
|
+
|
|
228
|
+
When ExtractionOrchestrator fetches large datasets (e.g., 50,000 records), all records are loaded into memory. Before writing to destination (SFTP/S3), you can split these records into multiple smaller files for better manageability, parallel processing, and downstream system compatibility.
|
|
229
|
+
|
|
230
|
+
### When to Split Files
|
|
231
|
+
|
|
232
|
+
- ✅ **Extraction returns >10k records** - Split into manageable chunks
|
|
233
|
+
- ✅ **Downstream system has file size limits** - Respect partner constraints
|
|
234
|
+
- ✅ **Parallel processing desired** - Write multiple files concurrently
|
|
235
|
+
- ✅ **Network reliability concerns** - Smaller files = better retry granularity
|
|
236
|
+
- ✅ **Audit trail requirements** - Track processing per file
|
|
237
|
+
|
|
238
|
+
### Basic File Splitting Pattern
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { Buffer } from 'node:buffer';
|
|
242
|
+
import { ExtractionOrchestrator, SftpDataSource } from '@fluentcommerce/fc-connect-sdk';
|
|
243
|
+
|
|
244
|
+
const startTime = Date.now();
|
|
245
|
+
log.info('📦 Starting extraction with file splitting');
|
|
246
|
+
|
|
247
|
+
// STEP 1: Extract all records (loads into memory)
|
|
248
|
+
const result = await orchestrator.extract({
|
|
249
|
+
query: virtualPositionsQuery,
|
|
250
|
+
variables: { retailerId: 'my-retailer', updatedAfter: lastRunTime },
|
|
251
|
+
pagination: { pageSize: 200, maxRecords: 50000 },
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
log.info('✅ Extraction complete', {
|
|
255
|
+
recordCount: result.data.length,
|
|
256
|
+
duration: Date.now() - startTime
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// result.data contains all 50,000 records in memory
|
|
260
|
+
|
|
261
|
+
// STEP 2: Configure chunking
|
|
262
|
+
const RECORDS_PER_FILE = 1000; // Configurable chunk size
|
|
263
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
264
|
+
|
|
265
|
+
// STEP 3: Split records into chunks
|
|
266
|
+
const chunks: any[][] = [];
|
|
267
|
+
for (let i = 0; i < result.data.length; i += RECORDS_PER_FILE) {
|
|
268
|
+
chunks.push(result.data.slice(i, i + RECORDS_PER_FILE));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
log.info('📂 File splitting configured', {
|
|
272
|
+
totalRecords: result.data.length,
|
|
273
|
+
recordsPerFile: RECORDS_PER_FILE,
|
|
274
|
+
filesToCreate: chunks.length,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// STEP 4: Generate files with configurable naming pattern
|
|
278
|
+
const filePromises = chunks.map((chunk, index) => {
|
|
279
|
+
// File naming pattern: entity-timestamp-part-NNN.format
|
|
280
|
+
const partNumber = String(index + 1).padStart(3, '0'); // 001, 002, 003...
|
|
281
|
+
const filename = `virtual-positions-${timestamp}-part-${partNumber}.xml`;
|
|
282
|
+
|
|
283
|
+
// Transform chunk to desired format (XML, CSV, JSON)
|
|
284
|
+
const xmlContent = xmlBuilder.build({ records: chunk });
|
|
285
|
+
|
|
286
|
+
// Return upload promise
|
|
287
|
+
return sftp.uploadFile(
|
|
288
|
+
`/outbound/${filename}`,
|
|
289
|
+
Buffer.from(xmlContent, 'utf8'),
|
|
290
|
+
{ encoding: 'utf8', overwrite: false }
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// STEP 5: Write all files in parallel
|
|
295
|
+
const uploadStartTime = Date.now();
|
|
296
|
+
await Promise.all(filePromises);
|
|
297
|
+
|
|
298
|
+
log.info('✅ File splitting complete', {
|
|
299
|
+
filesCreated: chunks.length,
|
|
300
|
+
totalRecords: result.data.length,
|
|
301
|
+
recordsPerFile: RECORDS_PER_FILE,
|
|
302
|
+
uploadDuration: Date.now() - uploadStartTime,
|
|
303
|
+
totalDuration: Date.now() - startTime
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Configurable File Naming Patterns
|
|
308
|
+
|
|
309
|
+
Support multiple naming patterns via configuration:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Configuration interface
|
|
313
|
+
interface FileSplittingConfig {
|
|
314
|
+
enabled: boolean;
|
|
315
|
+
recordsPerFile: number;
|
|
316
|
+
namingPattern: 'sequential' | 'timestamp' | 'range' | 'custom';
|
|
317
|
+
customPattern?: (index: number, chunk: any[], total: number) => string;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Pattern examples
|
|
321
|
+
const patterns = {
|
|
322
|
+
// Pattern 1: Sequential numbering
|
|
323
|
+
sequential: (index: number) =>
|
|
324
|
+
`virtual-positions-part-${String(index + 1).padStart(3, '0')}.xml`,
|
|
325
|
+
|
|
326
|
+
// Pattern 2: Timestamp-based
|
|
327
|
+
timestamp: (index: number) =>
|
|
328
|
+
`virtual-positions-${new Date().toISOString()}-${index + 1}.xml`,
|
|
329
|
+
|
|
330
|
+
// Pattern 3: Record range
|
|
331
|
+
range: (index: number, chunk: any[]) => {
|
|
332
|
+
const start = index * chunk.length + 1;
|
|
333
|
+
const end = start + chunk.length - 1;
|
|
334
|
+
return `virtual-positions-records-${start}-${end}.xml`;
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// Pattern 4: Custom with metadata
|
|
338
|
+
custom: (index: number, chunk: any[], totalChunks: number) =>
|
|
339
|
+
`VP_${new Date().toISOString().split('T')[0]}_${index + 1}_of_${totalChunks}.xml`,
|
|
340
|
+
};
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Complete Example: Extraction with File Splitting
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { schedule, fn, MemoryInterpreter } from '@versori/run';
|
|
347
|
+
import { Buffer } from 'node:buffer';
|
|
348
|
+
import {
|
|
349
|
+
ExtractionOrchestrator,
|
|
350
|
+
createClient,
|
|
351
|
+
SftpDataSource,
|
|
352
|
+
VersoriKVAdapter,
|
|
353
|
+
XMLBuilder,
|
|
354
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
355
|
+
|
|
356
|
+
export const virtualPositionsExtraction = schedule(
|
|
357
|
+
'virtual-positions-hourly',
|
|
358
|
+
'0 * * * *'
|
|
359
|
+
).then(
|
|
360
|
+
fn('extract', async (ctx) => {
|
|
361
|
+
const { log, activation, openKv } = ctx;
|
|
362
|
+
const startTime = Date.now();
|
|
363
|
+
|
|
364
|
+
log.info('📦 Starting virtual positions extraction');
|
|
365
|
+
|
|
366
|
+
// Configuration from activation variables
|
|
367
|
+
const FILE_SPLITTING_ENABLED = activation.getVariable('fileSplittingEnabled') !== 'false';
|
|
368
|
+
const RECORDS_PER_FILE = parseInt(activation.getVariable('recordsPerFile') || '1000', 10);
|
|
369
|
+
const FILE_NAMING_PATTERN = activation.getVariable('fileNamingPattern') || 'sequential';
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
// Initialize services
|
|
373
|
+
const client = await createClient(ctx);
|
|
374
|
+
log.info('✅ Client initialized');
|
|
375
|
+
|
|
376
|
+
const kv = new VersoriKVAdapter(openKv(':project:'));
|
|
377
|
+
const orchestrator = new ExtractionOrchestrator(client, log);
|
|
378
|
+
|
|
379
|
+
// Extract records (all loaded into memory)
|
|
380
|
+
const extractionStartTime = Date.now();
|
|
381
|
+
const result = await orchestrator.extract({
|
|
382
|
+
query: virtualPositionsQuery,
|
|
383
|
+
variables: {
|
|
384
|
+
retailerId: activation.getVariable('fluentRetailerId'),
|
|
385
|
+
updatedAfter: await kv.get(['extraction', 'lastRunTime']) || '2025-01-01T00:00:00Z',
|
|
386
|
+
},
|
|
387
|
+
pagination: { pageSize: 200, maxRecords: 50000 },
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
log.info('✅ Extraction complete', {
|
|
391
|
+
recordCount: result.data.length,
|
|
392
|
+
fileSplittingEnabled: FILE_SPLITTING_ENABLED,
|
|
393
|
+
extractionDuration: Date.now() - extractionStartTime
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Initialize SFTP
|
|
397
|
+
const sftp = new SftpDataSource({
|
|
398
|
+
type: 'SFTP_XML',
|
|
399
|
+
connectionId: 'sftp-extractions',
|
|
400
|
+
name: 'extraction-sftp',
|
|
401
|
+
settings: {
|
|
402
|
+
host: activation.getVariable('sftpHost'),
|
|
403
|
+
port: parseInt(activation.getVariable('sftpPort') || '22', 10),
|
|
404
|
+
username: activation.getVariable('sftpUsername'),
|
|
405
|
+
password: activation.getVariable('sftpPassword'),
|
|
406
|
+
remotePath: '/outbound/',
|
|
407
|
+
requireAbsolutePaths: true,
|
|
408
|
+
},
|
|
409
|
+
}, log);
|
|
410
|
+
|
|
411
|
+
await sftp.validateConnection();
|
|
412
|
+
log.info('✅ SFTP connection validated');
|
|
413
|
+
|
|
414
|
+
// File splitting logic
|
|
415
|
+
if (FILE_SPLITTING_ENABLED && result.data.length > RECORDS_PER_FILE) {
|
|
416
|
+
const splittingStartTime = Date.now();
|
|
417
|
+
|
|
418
|
+
// Split into chunks
|
|
419
|
+
const chunks: any[][] = [];
|
|
420
|
+
for (let i = 0; i < result.data.length; i += RECORDS_PER_FILE) {
|
|
421
|
+
chunks.push(result.data.slice(i, i + RECORDS_PER_FILE));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
log.info('📂 Splitting extraction into multiple files', {
|
|
425
|
+
totalRecords: result.data.length,
|
|
426
|
+
recordsPerFile: RECORDS_PER_FILE,
|
|
427
|
+
filesToCreate: chunks.length,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Generate files
|
|
431
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
432
|
+
const xmlBuilder = new XMLBuilder();
|
|
433
|
+
|
|
434
|
+
const filePromises = chunks.map(async (chunk, index) => {
|
|
435
|
+
// Generate filename based on pattern
|
|
436
|
+
let filename: string;
|
|
437
|
+
const partNumber = String(index + 1).padStart(3, '0');
|
|
438
|
+
|
|
439
|
+
switch (FILE_NAMING_PATTERN) {
|
|
440
|
+
case 'timestamp':
|
|
441
|
+
filename = `virtual-positions-${timestamp}-${partNumber}.xml`;
|
|
442
|
+
break;
|
|
443
|
+
case 'range':
|
|
444
|
+
const start = index * RECORDS_PER_FILE + 1;
|
|
445
|
+
const end = Math.min(start + chunk.length - 1, result.data.length);
|
|
446
|
+
filename = `virtual-positions-records-${start}-${end}.xml`;
|
|
447
|
+
break;
|
|
448
|
+
case 'sequential':
|
|
449
|
+
default:
|
|
450
|
+
filename = `virtual-positions-part-${partNumber}.xml`;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Build XML content
|
|
455
|
+
const xmlContent = xmlBuilder.build({ virtualPositions: chunk });
|
|
456
|
+
|
|
457
|
+
// Upload to SFTP
|
|
458
|
+
await sftp.uploadFile(
|
|
459
|
+
`/outbound/${filename}`,
|
|
460
|
+
Buffer.from(xmlContent, 'utf8'),
|
|
461
|
+
{ encoding: 'utf8', overwrite: false }
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
return { filename, recordCount: chunk.length };
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Write all files in parallel
|
|
468
|
+
const uploadedFiles = await Promise.all(filePromises);
|
|
469
|
+
|
|
470
|
+
log.info('✅ File splitting complete', {
|
|
471
|
+
filesCreated: uploadedFiles.length,
|
|
472
|
+
totalRecords: result.data.length,
|
|
473
|
+
files: uploadedFiles,
|
|
474
|
+
splittingDuration: Date.now() - splittingStartTime,
|
|
475
|
+
totalDuration: Date.now() - startTime
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
success: true,
|
|
480
|
+
extractionMode: 'incremental',
|
|
481
|
+
totalRecords: result.data.length,
|
|
482
|
+
filesCreated: uploadedFiles.length,
|
|
483
|
+
files: uploadedFiles,
|
|
484
|
+
duration: Date.now() - startTime
|
|
485
|
+
};
|
|
486
|
+
} else {
|
|
487
|
+
// Single file (no splitting)
|
|
488
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
489
|
+
const filename = `virtual-positions-${timestamp}.xml`;
|
|
490
|
+
const xmlBuilder = new XMLBuilder();
|
|
491
|
+
const xmlContent = xmlBuilder.build({ virtualPositions: result.data });
|
|
492
|
+
|
|
493
|
+
await sftp.uploadFile(
|
|
494
|
+
`/outbound/${filename}`,
|
|
495
|
+
Buffer.from(xmlContent, 'utf8'),
|
|
496
|
+
{ encoding: 'utf8', overwrite: false }
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
log.info('✅ Single file extraction complete', {
|
|
500
|
+
filename,
|
|
501
|
+
recordCount: result.data.length,
|
|
502
|
+
duration: Date.now() - startTime
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
success: true,
|
|
507
|
+
extractionMode: 'incremental',
|
|
508
|
+
totalRecords: result.data.length,
|
|
509
|
+
filesCreated: 1,
|
|
510
|
+
files: [{ filename, recordCount: result.data.length }],
|
|
511
|
+
duration: Date.now() - startTime
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
} catch (error: any) {
|
|
515
|
+
log.error('❌ Extraction failed', {
|
|
516
|
+
message: error instanceof Error ? error.message : String(error),
|
|
517
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
518
|
+
errorType: error instanceof Error ? error.constructor.name : 'Error',
|
|
519
|
+
duration: Date.now() - startTime
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
success: false,
|
|
524
|
+
error: error.message,
|
|
525
|
+
duration: Date.now() - startTime
|
|
526
|
+
};
|
|
527
|
+
} finally {
|
|
528
|
+
if (sftp) {
|
|
529
|
+
await sftp.dispose();
|
|
530
|
+
log.info('✅ SFTP connection disposed');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
// Export with MemoryInterpreter for Versori platform
|
|
537
|
+
export const interpreter = new MemoryInterpreter({
|
|
538
|
+
workflows: [virtualPositionsExtraction]
|
|
539
|
+
});
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### Date Range Splitting for Very Large Datasets
|
|
543
|
+
|
|
544
|
+
For extractions spanning multiple years (e.g., historical dumps), split the date range and run multiple smaller extractions:
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
// Split large date range into manageable chunks
|
|
548
|
+
async function extractHistoricalData(
|
|
549
|
+
startDate: string, // "2020-01-01"
|
|
550
|
+
endDate: string, // "2025-01-01"
|
|
551
|
+
chunkMonths: number = 1, // Extract 1 month at a time
|
|
552
|
+
log: any
|
|
553
|
+
) {
|
|
554
|
+
const overallStartTime = Date.now();
|
|
555
|
+
const results = [];
|
|
556
|
+
let currentStart = new Date(startDate);
|
|
557
|
+
const finalEnd = new Date(endDate);
|
|
558
|
+
|
|
559
|
+
log.info('📦 Starting historical data extraction', {
|
|
560
|
+
startDate,
|
|
561
|
+
endDate,
|
|
562
|
+
chunkMonths,
|
|
563
|
+
estimatedChunks: Math.ceil((finalEnd.getTime() - currentStart.getTime()) / (chunkMonths * 30 * 24 * 60 * 60 * 1000))
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
while (currentStart < finalEnd) {
|
|
567
|
+
const chunkStartTime = Date.now();
|
|
568
|
+
|
|
569
|
+
// Calculate chunk end date
|
|
570
|
+
const currentEnd = new Date(currentStart);
|
|
571
|
+
currentEnd.setMonth(currentEnd.getMonth() + chunkMonths);
|
|
572
|
+
|
|
573
|
+
// Ensure we don't exceed final end date
|
|
574
|
+
if (currentEnd > finalEnd) {
|
|
575
|
+
currentEnd.setTime(finalEnd.getTime());
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
log.info('📂 Extracting date range chunk', {
|
|
579
|
+
chunkNumber: results.length + 1,
|
|
580
|
+
startDate: currentStart.toISOString(),
|
|
581
|
+
endDate: currentEnd.toISOString(),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Extract for this date range
|
|
585
|
+
const result = await orchestrator.extract({
|
|
586
|
+
query: virtualPositionsQuery,
|
|
587
|
+
variables: {
|
|
588
|
+
retailerId: 'my-retailer',
|
|
589
|
+
updatedAfter: currentStart.toISOString(),
|
|
590
|
+
updatedBefore: currentEnd.toISOString(),
|
|
591
|
+
},
|
|
592
|
+
pagination: { pageSize: 200, maxRecords: 50000 },
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
log.info('✅ Chunk extraction complete', {
|
|
596
|
+
chunkNumber: results.length + 1,
|
|
597
|
+
recordCount: result.data.length,
|
|
598
|
+
chunkDuration: Date.now() - chunkStartTime
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// Split this chunk into files if needed
|
|
602
|
+
if (result.data.length > RECORDS_PER_FILE) {
|
|
603
|
+
const chunks: any[][] = [];
|
|
604
|
+
for (let i = 0; i < result.data.length; i += RECORDS_PER_FILE) {
|
|
605
|
+
chunks.push(result.data.slice(i, i + RECORDS_PER_FILE));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Write chunks in parallel
|
|
609
|
+
const timestamp = currentStart.toISOString().split('T')[0];
|
|
610
|
+
await Promise.all(
|
|
611
|
+
chunks.map((chunk, index) => {
|
|
612
|
+
const filename = `historical-${timestamp}-part-${String(index + 1).padStart(3, '0')}.xml`;
|
|
613
|
+
return sftp.uploadFile(`/outbound/${filename}`, Buffer.from(xmlBuilder.build(chunk), 'utf8'));
|
|
614
|
+
})
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
results.push({
|
|
619
|
+
startDate: currentStart.toISOString(),
|
|
620
|
+
endDate: currentEnd.toISOString(),
|
|
621
|
+
recordCount: result.data.length,
|
|
622
|
+
duration: Date.now() - chunkStartTime
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Move to next chunk
|
|
626
|
+
currentStart = new Date(currentEnd);
|
|
627
|
+
|
|
628
|
+
// Rate limiting - wait between chunks
|
|
629
|
+
log.info('⏱️ Rate limiting delay (5 seconds)');
|
|
630
|
+
await new Promise(resolve => setTimeout(resolve, 5000)); // 5 second delay
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const totalRecords = results.reduce((sum, r) => sum + r.recordCount, 0);
|
|
634
|
+
const totalDuration = Date.now() - overallStartTime;
|
|
635
|
+
|
|
636
|
+
log.info('✅ Historical extraction complete', {
|
|
637
|
+
totalChunks: results.length,
|
|
638
|
+
totalRecords,
|
|
639
|
+
totalDuration,
|
|
640
|
+
averageRecordsPerChunk: Math.round(totalRecords / results.length)
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
return results;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Usage: Extract 5 years of data, 1 month at a time
|
|
647
|
+
const historicalResults = await extractHistoricalData('2020-01-01', '2025-01-01', 1, log);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Configuration Variables for File Splitting
|
|
651
|
+
|
|
652
|
+
Add these to Versori activation variables:
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
# File Splitting Configuration
|
|
656
|
+
FILE_SPLITTING_ENABLED=true # Enable/disable file splitting
|
|
657
|
+
RECORDS_PER_FILE=1000 # Records per file (default: 1000)
|
|
658
|
+
FILE_NAMING_PATTERN=sequential # Naming pattern: sequential | timestamp | range
|
|
659
|
+
MAX_RECORDS_PER_EXTRACTION=50000 # Maximum records to extract in one run
|
|
660
|
+
|
|
661
|
+
# Date Range Splitting (for large historical extractions)
|
|
662
|
+
DATE_RANGE_CHUNK_MONTHS=1 # Months per extraction chunk (default: 1)
|
|
663
|
+
RATE_LIMIT_DELAY_MS=5000 # Delay between chunks (milliseconds)
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Benefits of File Splitting
|
|
667
|
+
|
|
668
|
+
- ✅ **Memory efficiency** - Process large datasets without memory overflow
|
|
669
|
+
- ✅ **Parallel writes** - Multiple files written concurrently (faster)
|
|
670
|
+
- ✅ **Better error recovery** - Retry individual files vs entire extraction
|
|
671
|
+
- ✅ **Downstream compatibility** - Honor partner file size limits
|
|
672
|
+
- ✅ **Audit granularity** - Track processing per file
|
|
673
|
+
- ✅ **Network resilience** - Smaller files = better upload success rate
|
|
674
|
+
|
|
675
|
+
### Limitations
|
|
676
|
+
|
|
677
|
+
- All records must fit in memory during extraction (ExtractionOrchestrator loads all pages)
|
|
678
|
+
- File splitting happens post-extraction (not during pagination)
|
|
679
|
+
- Parallel writes limited by available memory and network bandwidth
|
|
680
|
+
- SFTP connection pool size may limit concurrency (default: 10 connections)
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## Historical Data Extraction via Date Range
|
|
685
|
+
|
|
686
|
+
### Overview
|
|
687
|
+
|
|
688
|
+
There is **no separate "historical mode"** in the SDK. To extract historical data (e.g., all records from 2020 onwards), use **Date Range mode** with a very old start date and appropriate safeguards.
|
|
689
|
+
|
|
690
|
+
### How to Extract Historical Data
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
// Historical extraction is just Date Range with old start date
|
|
694
|
+
await client.graphql({
|
|
695
|
+
query: PRODUCTS_QUERY,
|
|
696
|
+
variables: {
|
|
697
|
+
retailerId: 'my-retailer',
|
|
698
|
+
updatedAfter: '1970-01-01T00:00:00Z', // Very old date = "all records"
|
|
699
|
+
updatedBefore: new Date().toISOString(), // Up to now
|
|
700
|
+
first: 200,
|
|
701
|
+
},
|
|
702
|
+
pagination: { maxRecords: 50000 },
|
|
703
|
+
});
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Required Safeguards for Historical Extraction
|
|
707
|
+
|
|
708
|
+
1. **Date Range Splitting** - Split into smaller chunks (monthly/quarterly)
|
|
709
|
+
2. **File Splitting** - Split large results into multiple files
|
|
710
|
+
3. **Rate Limiting** - Add delays between chunks
|
|
711
|
+
4. **Validation** - Verify date ranges before execution
|
|
712
|
+
5. **Monitoring** - Track progress and alert on anomalies
|
|
713
|
+
6. **Approval** - Get sign-off before running on production
|
|
714
|
+
|
|
715
|
+
### Recommended Approach: Chunked Date Range Extraction
|
|
716
|
+
|
|
717
|
+
```bash
|
|
718
|
+
#!/bin/bash
|
|
719
|
+
# Safe historical extraction via chunked date ranges
|
|
720
|
+
|
|
721
|
+
START_DATE="2020-01-01"
|
|
722
|
+
END_DATE="2025-01-01"
|
|
723
|
+
|
|
724
|
+
# Extract one month at a time
|
|
725
|
+
current=$START_DATE
|
|
726
|
+
while [[ "$current" < "$END_DATE" ]]; do
|
|
727
|
+
# Calculate month end
|
|
728
|
+
monthEnd=$(date -d "$current + 1 month" +%Y-%m-%d)
|
|
729
|
+
|
|
730
|
+
echo "Extracting $current to $monthEnd..."
|
|
731
|
+
|
|
732
|
+
# Trigger date range extraction
|
|
733
|
+
curl -X POST https://versori-webhook.com/extract \
|
|
734
|
+
-H "Content-Type: application/json" \
|
|
735
|
+
-d "{
|
|
736
|
+
\"extractionMode\": \"dateRange\",
|
|
737
|
+
\"startDate\": \"${current}T00:00:00Z\",
|
|
738
|
+
\"endDate\": \"${monthEnd}T00:00:00Z\",
|
|
739
|
+
\"fileSplittingEnabled\": true,
|
|
740
|
+
\"recordsPerFile\": 1000
|
|
741
|
+
}"
|
|
742
|
+
|
|
743
|
+
# Rate limiting - wait 60 seconds between chunks
|
|
744
|
+
sleep 60
|
|
745
|
+
|
|
746
|
+
# Move to next month
|
|
747
|
+
current=$monthEnd
|
|
748
|
+
done
|
|
749
|
+
|
|
750
|
+
echo "Historical extraction complete"
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Migration to Incremental After Historical Load
|
|
754
|
+
|
|
755
|
+
After completing a one-time historical extraction, switch to incremental mode for ongoing syncs:
|
|
756
|
+
|
|
757
|
+
```json
|
|
758
|
+
{
|
|
759
|
+
"extractionMode": "incremental",
|
|
760
|
+
"fallbackStartDate": "2025-01-22T00:00:00Z",
|
|
761
|
+
"pageSize": 200,
|
|
762
|
+
"maxRecords": 10000
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Decision Tree: Which Mode to Use?
|
|
769
|
+
|
|
770
|
+
```
|
|
771
|
+
Start Here
|
|
772
|
+
│
|
|
773
|
+
├─ Need recurring extractions? ─────────► Use INCREMENTAL ✅
|
|
774
|
+
│ (hourly/daily/every 15 min)
|
|
775
|
+
│ Tracks state, auto-recovery
|
|
776
|
+
│
|
|
777
|
+
└─ Need specific date range? ────────────► Use DATE RANGE ⚠️
|
|
778
|
+
│ (one-time, validate range)
|
|
779
|
+
│
|
|
780
|
+
├─ Date range < 30 days? ──────────► Single DATE RANGE run
|
|
781
|
+
│ + file splitting if >10k records
|
|
782
|
+
│
|
|
783
|
+
└─ Date range > 30 days? ──────────► Split into monthly chunks
|
|
784
|
+
│ + file splitting per chunk
|
|
785
|
+
│ + rate limiting between chunks
|
|
786
|
+
│
|
|
787
|
+
└─ Historical (all data)? ────► DATE RANGE with old start date
|
|
788
|
+
+ chunked approach (monthly)
|
|
789
|
+
+ approval required
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
## Monitoring & Alerts
|
|
795
|
+
|
|
796
|
+
Set up alerts for extraction volumes:
|
|
797
|
+
|
|
798
|
+
```typescript
|
|
799
|
+
// In extraction workflow
|
|
800
|
+
const recordCount = edges.length;
|
|
801
|
+
const ALERT_THRESHOLD = 50000;
|
|
802
|
+
|
|
803
|
+
log.info('📊 Checking extraction volume', { recordCount, threshold: ALERT_THRESHOLD });
|
|
804
|
+
|
|
805
|
+
if (recordCount > ALERT_THRESHOLD) {
|
|
806
|
+
log.error('❌ Extraction volume exceeded threshold', {
|
|
807
|
+
recordCount,
|
|
808
|
+
threshold: ALERT_THRESHOLD,
|
|
809
|
+
percentageOver: Math.round(((recordCount - ALERT_THRESHOLD) / ALERT_THRESHOLD) * 100),
|
|
810
|
+
mode: extractionMode,
|
|
811
|
+
recommendation: 'Switch to incremental mode or reduce date range',
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// Send alert to monitoring system
|
|
815
|
+
await sendAlert({
|
|
816
|
+
severity: 'high',
|
|
817
|
+
message: `Extraction returned ${recordCount} records (threshold: ${ALERT_THRESHOLD})`,
|
|
818
|
+
mode: extractionMode,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
log.info('✅ Alert sent to monitoring system');
|
|
822
|
+
} else {
|
|
823
|
+
log.info('✅ Extraction volume within acceptable limits', {
|
|
824
|
+
recordCount,
|
|
825
|
+
threshold: ALERT_THRESHOLD,
|
|
826
|
+
percentageUsed: Math.round((recordCount / ALERT_THRESHOLD) * 100)
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## Summary & Best Practices
|
|
834
|
+
|
|
835
|
+
### ✅ DO
|
|
836
|
+
|
|
837
|
+
- Use **incremental mode** for all scheduled extractions
|
|
838
|
+
- Validate date ranges before running **dateRange mode**
|
|
839
|
+
- Implement **file splitting** for large extractions (>10k records)
|
|
840
|
+
- Use **parallel writes** with `Promise.all()` for split files
|
|
841
|
+
- Monitor extraction volumes and set alerts
|
|
842
|
+
- Test on staging before production
|
|
843
|
+
- Use overlap buffer (60s) to prevent gaps in incremental mode
|
|
844
|
+
- Track state with VersoriKV
|
|
845
|
+
- Split large historical extractions into monthly chunks
|
|
846
|
+
- Add rate limiting between extraction chunks
|
|
847
|
+
|
|
848
|
+
### ❌ DON'T
|
|
849
|
+
|
|
850
|
+
- Schedule **dateRange** extractions (incremental only)
|
|
851
|
+
- Use date ranges > 30 days without chunking
|
|
852
|
+
- Skip validation checks for date ranges
|
|
853
|
+
- Ignore volume alerts (>50k records)
|
|
854
|
+
- Forget to implement file splitting for large results
|
|
855
|
+
- Run historical extractions without approval and monitoring
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
## ExtractionOrchestrator (SDK v0.1.27+)
|
|
860
|
+
|
|
861
|
+
The SDK includes **ExtractionOrchestrator** - a high-level service that simplifies extraction workflows with built-in mode handling, pagination, and output management.
|
|
862
|
+
|
|
863
|
+
### Why Use ExtractionOrchestrator?
|
|
864
|
+
|
|
865
|
+
**Instead of manually implementing:**
|
|
866
|
+
|
|
867
|
+
- Mode detection (incremental/dateRange)
|
|
868
|
+
- Pagination loops
|
|
869
|
+
- Path-based field extraction
|
|
870
|
+
- Output formatting (CSV/JSON/Parquet)
|
|
871
|
+
- S3/SFTP uploads
|
|
872
|
+
- Error handling
|
|
873
|
+
|
|
874
|
+
**ExtractionOrchestrator handles it all:**
|
|
875
|
+
|
|
876
|
+
```typescript
|
|
877
|
+
import { ExtractionOrchestrator, createClient } from '@fluentcommerce/fc-connect-sdk';
|
|
878
|
+
|
|
879
|
+
const startTime = Date.now();
|
|
880
|
+
log.info('📦 Initializing ExtractionOrchestrator');
|
|
881
|
+
|
|
882
|
+
const client = await createClient(ctx);
|
|
883
|
+
const orchestrator = new ExtractionOrchestrator(client, log);
|
|
884
|
+
|
|
885
|
+
log.info('✅ Orchestrator initialized');
|
|
886
|
+
|
|
887
|
+
// Both modes supported with single interface
|
|
888
|
+
const result = await orchestrator.extract({
|
|
889
|
+
query: virtualPositionsQuery,
|
|
890
|
+
variables: { retailerId: 'my-retailer' },
|
|
891
|
+
|
|
892
|
+
// Mode: 'incremental' (scheduled) or 'dateRange' (ad-hoc)
|
|
893
|
+
extractionMode: 'incremental',
|
|
894
|
+
stateKey: 'virtual-positions-extraction',
|
|
895
|
+
|
|
896
|
+
// Pagination handled automatically
|
|
897
|
+
pagination: {
|
|
898
|
+
pageSize: 200,
|
|
899
|
+
maxRecords: 10000,
|
|
900
|
+
},
|
|
901
|
+
|
|
902
|
+
// Output format and destination
|
|
903
|
+
outputFormat: 'csv',
|
|
904
|
+
outputDestination: {
|
|
905
|
+
type: 's3',
|
|
906
|
+
bucket: 'my-extracts',
|
|
907
|
+
key: 'virtual-positions/hourly/{{timestamp}}.csv',
|
|
908
|
+
},
|
|
909
|
+
|
|
910
|
+
// Field extraction from nested paths
|
|
911
|
+
fieldPaths: {
|
|
912
|
+
position_ref: 'ref',
|
|
913
|
+
quantity: 'quantity',
|
|
914
|
+
location_ref: 'locationLink.ref',
|
|
915
|
+
location_name: 'locationLink.name',
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
log.info('✅ Extraction and upload complete', {
|
|
920
|
+
recordCount: result.recordCount,
|
|
921
|
+
outputFile: result.outputFile,
|
|
922
|
+
duration: Date.now() - startTime
|
|
923
|
+
});
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
### Features
|
|
927
|
+
|
|
928
|
+
- **Auto-pagination**: Handles cursor-based pagination automatically
|
|
929
|
+
- **Mode support**: Both modes (incremental, dateRange)
|
|
930
|
+
- **State management**: Tracks last run timestamps for incremental mode
|
|
931
|
+
- **Path extraction**: Extracts nested fields from GraphQL responses
|
|
932
|
+
- **Multi-format**: Outputs CSV, JSON, or Parquet
|
|
933
|
+
- **Validation**: Built-in query and response validation
|
|
934
|
+
- **Error recovery**: Graceful failure handling with detailed logs
|
|
935
|
+
- **File splitting**: Post-extraction chunking for large datasets (add manually, see File Splitting section above)
|
|
936
|
+
|
|
937
|
+
### Example: Incremental Extraction with ExtractionOrchestrator
|
|
938
|
+
|
|
939
|
+
```typescript
|
|
940
|
+
import { schedule, fn, MemoryInterpreter } from '@versori/run';
|
|
941
|
+
import { Buffer } from 'node:buffer'; // Required for Deno/Versori runtime
|
|
942
|
+
import {
|
|
943
|
+
ExtractionOrchestrator,
|
|
944
|
+
createClient,
|
|
945
|
+
VersoriKVAdapter,
|
|
946
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
947
|
+
|
|
948
|
+
export const hourlyExtraction = schedule('hourly-virtual-positions', '0 * * * *').then(
|
|
949
|
+
fn('extract', async (ctx) => {
|
|
950
|
+
const { log, openKv, env } = ctx;
|
|
951
|
+
const startTime = Date.now();
|
|
952
|
+
|
|
953
|
+
log.info('📦 Starting hourly virtual positions extraction');
|
|
954
|
+
|
|
955
|
+
const client = await createClient(ctx);
|
|
956
|
+
log.info('✅ Client initialized');
|
|
957
|
+
|
|
958
|
+
const kv = new VersoriKVAdapter(openKv(':project:'));
|
|
959
|
+
const orchestrator = new ExtractionOrchestrator(client, log);
|
|
960
|
+
|
|
961
|
+
const result = await orchestrator.extract({
|
|
962
|
+
query: `
|
|
963
|
+
query GetVirtualPositions($retailerId: String!, $updatedAfter: String, $first: Int, $after: String) {
|
|
964
|
+
virtualPositions(retailerId: $retailerId, updatedAfter: $updatedAfter, first: $first, after: $after) {
|
|
965
|
+
edges {
|
|
966
|
+
node {
|
|
967
|
+
ref
|
|
968
|
+
quantity
|
|
969
|
+
productRef
|
|
970
|
+
locationLink { ref name }
|
|
971
|
+
updatedOn
|
|
972
|
+
}
|
|
973
|
+
cursor
|
|
974
|
+
}
|
|
975
|
+
pageInfo {
|
|
976
|
+
hasNextPage
|
|
977
|
+
# Note: Fluent doesn't return endCursor - cursors are in edges[].cursor
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
`,
|
|
982
|
+
variables: { retailerId: env.FLUENT_RETAILER_ID },
|
|
983
|
+
extractionMode: 'incremental',
|
|
984
|
+
stateAdapter: kv,
|
|
985
|
+
stateKey: 'hourly-extraction',
|
|
986
|
+
fallbackStartDate: '2025-01-01T00:00:00Z',
|
|
987
|
+
pagination: { pageSize: 200, maxRecords: 10000 },
|
|
988
|
+
outputFormat: 'csv',
|
|
989
|
+
outputDestination: {
|
|
990
|
+
type: 's3',
|
|
991
|
+
bucket: env.S3_BUCKET,
|
|
992
|
+
key: `virtual-positions/{{date}}/{{timestamp}}.csv`,
|
|
993
|
+
config: {
|
|
994
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
995
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
996
|
+
region: env.AWS_REGION,
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
log.info('✅ Extraction complete', {
|
|
1002
|
+
success: result.success,
|
|
1003
|
+
recordCount: result.recordCount,
|
|
1004
|
+
outputFile: result.outputFile,
|
|
1005
|
+
duration: Date.now() - startTime
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
return {
|
|
1009
|
+
success: result.success,
|
|
1010
|
+
recordCount: result.recordCount,
|
|
1011
|
+
outputFile: result.outputFile,
|
|
1012
|
+
duration: Date.now() - startTime
|
|
1013
|
+
};
|
|
1014
|
+
})
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
// Export with MemoryInterpreter for Versori platform
|
|
1018
|
+
export const interpreter = new MemoryInterpreter({
|
|
1019
|
+
workflows: [hourlyExtraction]
|
|
1020
|
+
});
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
### When to Use ExtractionOrchestrator
|
|
1024
|
+
|
|
1025
|
+
- **✅ Use for**: New extraction workflows, scheduled extractions, standard use cases
|
|
1026
|
+
- **⚠️ Manual approach**: Complex transformations, custom business logic, non-standard outputs
|
|
1027
|
+
|
|
1028
|
+
See [ExtractionOrchestrator API Reference](../../../../02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md) for complete documentation.
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
1032
|
+
## See Also
|
|
1033
|
+
|
|
1034
|
+
- [CLI Validation Workflow](../../../../02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md) - Validate queries and mappings
|
|
1035
|
+
- [Production Safety Guide](../../../../02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md) - General safety practices
|
|
1036
|
+
- [GraphQL Query Examples](./graphql-queries/) - Sample extraction queries
|
|
1037
|
+
- [Universal Mapping Guide](../../../../02-CORE-GUIDES/advanced-services/advanced-services-readme.md) - Field mapping documentation
|
|
1038
|
+
- [ExtractionOrchestrator Examples](../../../../03-PATTERN-GUIDES/examples/test-data/03-PATTERN-GUIDES-readme.md) - Complete working examples
|