@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,2369 +1,2369 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Understanding Mapping - JavaScript Object Paths
|
|
3
|
-
description: Comprehensive guide to how mapping configurations work with parsed data structures
|
|
4
|
-
category: mapping
|
|
5
|
-
priority: high
|
|
6
|
-
related:
|
|
7
|
-
- mapping/modules/01-foundations.md
|
|
8
|
-
- mapping/modules/06-helpers-resolvers.md
|
|
9
|
-
- parsers/modules/04-xml-parser.md
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Understanding Mapping: JavaScript Object Paths
|
|
13
|
-
|
|
14
|
-
**Critical Concept:** Mapping paths reference **JavaScript object structure**, not source file format.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Table of Contents
|
|
19
|
-
|
|
20
|
-
1. [Core Concept: Mapping AFTER Parsing](#core-concept-mapping-after-parsing)
|
|
21
|
-
2. [The ETL Pattern](#the-etl-pattern)
|
|
22
|
-
3. [Format Examples](#format-examples)
|
|
23
|
-
- [CSV Examples](#csv-examples)
|
|
24
|
-
- [JSON Examples](#json-examples)
|
|
25
|
-
- [XML Examples](#xml-examples)
|
|
26
|
-
- [Complex XML with Attributes](#complex-xml-with-attributes)
|
|
27
|
-
- [Complex JSON with Deep Nesting](#complex-json-with-deep-nesting)
|
|
28
|
-
- [XML with Shared Parent Data](#xml-with-shared-parent-data)
|
|
29
|
-
4. [Common Transformation Patterns](#common-transformation-patterns)
|
|
30
|
-
5. [Visual Flow Diagrams](#visual-flow-diagrams)
|
|
31
|
-
6. [Debugging Guide](#debugging-guide)
|
|
32
|
-
7. [Real-World Example: InventoryStatus](#real-world-example-inventorystatus)
|
|
33
|
-
8. [Best Practices](#best-practices)
|
|
34
|
-
9. [Common Mistakes to Avoid](#common-mistakes-to-avoid)
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Core Concept: Mapping AFTER Parsing
|
|
39
|
-
|
|
40
|
-
### The Key Principle
|
|
41
|
-
|
|
42
|
-
**Mapping configurations map JAVASCRIPT OBJECTS, not file formats.**
|
|
43
|
-
|
|
44
|
-
When you write a mapping configuration like this:
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"fields": {
|
|
49
|
-
"skuRef": { "source": "product.sku" }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The path `"product.sku"` refers to **JavaScript object property access**, not:
|
|
55
|
-
- NOT CSV column names directly
|
|
56
|
-
- NOT XPath expressions
|
|
57
|
-
- NOT JSONPath queries
|
|
58
|
-
- NOT file format structure
|
|
59
|
-
|
|
60
|
-
### Why This Matters
|
|
61
|
-
|
|
62
|
-
Understanding this principle prevents confusion when working with different data sources:
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
// This is what the mapper receives - ALWAYS a JavaScript object
|
|
66
|
-
const jsObject = {
|
|
67
|
-
product: {
|
|
68
|
-
sku: "SKU-001",
|
|
69
|
-
name: "Widget"
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Your mapping config uses JavaScript paths
|
|
74
|
-
const mappingConfig = {
|
|
75
|
-
fields: {
|
|
76
|
-
skuRef: { source: "product.sku" } // JavaScript object path
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
The mapper **NEVER** sees the original CSV, XML, or JSON file. It only sees the JavaScript object that results from parsing and transformation.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## The ETL Pattern
|
|
86
|
-
|
|
87
|
-
All SDK data flows follow this standard Extract-Transform-Load pattern:
|
|
88
|
-
|
|
89
|
-
```mermaid
|
|
90
|
-
flowchart TD
|
|
91
|
-
A[Source File<br/>CSV/JSON/XML] --> B[Step 1: Parse<br/>→ JavaScript Objects]
|
|
92
|
-
B --> C[Step 2: Extract/Normalize<br/>→ Array of Records]
|
|
93
|
-
C --> D[Step 3: Map Each Record<br/>→ Fluent Format]
|
|
94
|
-
D --> E[Step 4: Load<br/>→ Batch API/GraphQL]
|
|
95
|
-
|
|
96
|
-
style A fill:#e1f5ff
|
|
97
|
-
style B fill:#fff4e1
|
|
98
|
-
style C fill:#fff4e1
|
|
99
|
-
style D fill:#e8f5e9
|
|
100
|
-
style E fill:#f3e5f5
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**Text Version:**
|
|
104
|
-
```
|
|
105
|
-
Source File (CSV/JSON/XML)
|
|
106
|
-
↓
|
|
107
|
-
[Step 1] Parse → JavaScript Objects
|
|
108
|
-
↓
|
|
109
|
-
[Step 2] Extract/Normalize → Array of Records
|
|
110
|
-
↓
|
|
111
|
-
[Step 3] Map Each Record → Fluent Format
|
|
112
|
-
↓
|
|
113
|
-
[Step 4] Load → Batch API/GraphQL
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**Detailed Breakdown:**
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
120
|
-
│ ETL WORKFLOW PATTERN │
|
|
121
|
-
└─────────────────────────────────────────────────────────────────────────────┘
|
|
122
|
-
|
|
123
|
-
Step 1: SOURCE FILE
|
|
124
|
-
┌────────────────────┐
|
|
125
|
-
│ inventory.csv │ File format: CSV, JSON, XML, Parquet
|
|
126
|
-
│ inventory.json │ Location: S3, SFTP, Local
|
|
127
|
-
│ inventory.xml │
|
|
128
|
-
└────────────────────┘
|
|
129
|
-
│
|
|
130
|
-
│ Read from data source
|
|
131
|
-
↓
|
|
132
|
-
Step 2: PARSE → JavaScript Objects
|
|
133
|
-
┌────────────────────┐
|
|
134
|
-
│ CSVParserService │ Converts file format to JavaScript
|
|
135
|
-
│ XMLParserService │ Result: Array of objects or nested structure
|
|
136
|
-
│ JSONParserService │
|
|
137
|
-
│ ParquetParser │
|
|
138
|
-
└────────────────────┘
|
|
139
|
-
│
|
|
140
|
-
│ Parser output: JavaScript objects
|
|
141
|
-
↓
|
|
142
|
-
Step 3: EXTRACT/NORMALIZE → Array of Records
|
|
143
|
-
┌────────────────────┐
|
|
144
|
-
│ Extract items │ Navigate to the data you need
|
|
145
|
-
│ Normalize arrays │ Flatten nested structures
|
|
146
|
-
│ Enrich with │ Add shared context data
|
|
147
|
-
│ parent data │
|
|
148
|
-
└────────────────────┘
|
|
149
|
-
│
|
|
150
|
-
│ What gets passed to mapper
|
|
151
|
-
↓
|
|
152
|
-
Step 4: MAP EACH RECORD → Fluent Format
|
|
153
|
-
┌────────────────────┐
|
|
154
|
-
│ UniversalMapper │ Maps JavaScript object paths to Fluent fields
|
|
155
|
-
│ or │ Uses mapping configuration
|
|
156
|
-
│ GraphQLMutation │ Applies resolvers for transformations
|
|
157
|
-
│ Mapper │
|
|
158
|
-
└────────────────────┘
|
|
159
|
-
│
|
|
160
|
-
│ Fluent-formatted data
|
|
161
|
-
↓
|
|
162
|
-
Step 5: LOAD → Fluent Commerce
|
|
163
|
-
┌────────────────────┐
|
|
164
|
-
│ Batch API │ Send data to Fluent
|
|
165
|
-
│ Event API │ create/update entities
|
|
166
|
-
│ GraphQL Mutations │
|
|
167
|
-
└────────────────────┘
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
**Key Insight:** Mapping (Step 4) operates on JavaScript objects from Step 3, not on the original file from Step 1. **Mapping configuration matches Step 3** - the structure you pass to `mapper.map()`, which is the result of Step 2.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Format Examples
|
|
175
|
-
|
|
176
|
-
### CSV Examples
|
|
177
|
-
|
|
178
|
-
#### Simple CSV: No Transformation Needed
|
|
179
|
-
|
|
180
|
-
**Original CSV File:**
|
|
181
|
-
```csv
|
|
182
|
-
sku_id,quantity,location_code,status
|
|
183
|
-
SKU-001,100,LOC-A,AVAILABLE
|
|
184
|
-
SKU-002,50,LOC-B,RESERVED
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
**Step 1: Parse**
|
|
188
|
-
```typescript
|
|
189
|
-
const csvParser = new CSVParserService();
|
|
190
|
-
const records = await csvParser.parse(csvContent);
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
**Step 2: Parsed JavaScript Object (CSVParserService output)**
|
|
194
|
-
```javascript
|
|
195
|
-
[
|
|
196
|
-
{
|
|
197
|
-
sku_id: "SKU-001",
|
|
198
|
-
quantity: "100", // Note: strings by default
|
|
199
|
-
location_code: "LOC-A",
|
|
200
|
-
status: "AVAILABLE"
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
sku_id: "SKU-002",
|
|
204
|
-
quantity: "50",
|
|
205
|
-
location_code: "LOC-B",
|
|
206
|
-
status: "RESERVED"
|
|
207
|
-
}
|
|
208
|
-
]
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**Step 3: Extraction (No transformation needed)**
|
|
212
|
-
```javascript
|
|
213
|
-
// CSV parser already gives us an array of flat objects
|
|
214
|
-
const records = parsedCsv; // Use directly
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
**Step 4: Mapping Configuration**
|
|
218
|
-
```json
|
|
219
|
-
{
|
|
220
|
-
"fields": {
|
|
221
|
-
"skuRef": {
|
|
222
|
-
"source": "sku_id",
|
|
223
|
-
"required": true
|
|
224
|
-
},
|
|
225
|
-
"qty": {
|
|
226
|
-
"source": "quantity",
|
|
227
|
-
"resolver": "sdk.parseInt"
|
|
228
|
-
},
|
|
229
|
-
"locationRef": {
|
|
230
|
-
"source": "location_code"
|
|
231
|
-
},
|
|
232
|
-
"status": {
|
|
233
|
-
"source": "status",
|
|
234
|
-
"resolver": "sdk.uppercase"
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
**What gets passed to mapper.map():**
|
|
241
|
-
```javascript
|
|
242
|
-
// For each record in the array:
|
|
243
|
-
await mapper.map({
|
|
244
|
-
sku_id: "SKU-001",
|
|
245
|
-
quantity: "100",
|
|
246
|
-
location_code: "LOC-A",
|
|
247
|
-
status: "AVAILABLE"
|
|
248
|
-
});
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
**Key Points:**
|
|
252
|
-
- CSV columns become JavaScript object keys
|
|
253
|
-
- Column names in file match `source` paths in mapping config
|
|
254
|
-
- No dot notation needed (flat structure)
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
#### Complex CSV: Requires Transformation
|
|
259
|
-
|
|
260
|
-
**CSV File:**
|
|
261
|
-
```csv
|
|
262
|
-
ref,price_default_currency,price_default_value,price_special_currency,price_special_value
|
|
263
|
-
PROD-001,USD,50.00,USD,35.00
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**Step 1: Parse**
|
|
267
|
-
```typescript
|
|
268
|
-
const csvParser = new CSVParserService();
|
|
269
|
-
const records = await csvParser.parse(csvContent);
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
**Parsed JavaScript Object:**
|
|
273
|
-
```javascript
|
|
274
|
-
[
|
|
275
|
-
{
|
|
276
|
-
ref: "PROD-001",
|
|
277
|
-
price_default_currency: "USD",
|
|
278
|
-
price_default_value: "50.00",
|
|
279
|
-
price_special_currency: "USD",
|
|
280
|
-
price_special_value: "35.00"
|
|
281
|
-
}
|
|
282
|
-
]
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
**Step 2: Extract/Normalize**
|
|
286
|
-
```typescript
|
|
287
|
-
// CSV is flat, but we need nested structure for prices
|
|
288
|
-
// Two options:
|
|
289
|
-
|
|
290
|
-
// Option A: Transform in code before mapping
|
|
291
|
-
const records = parsed.map(row => ({
|
|
292
|
-
ref: row.ref,
|
|
293
|
-
price: [
|
|
294
|
-
{ type: "DEFAULT", currency: row.price_default_currency, value: row.price_default_value },
|
|
295
|
-
{ type: "SPECIAL", currency: row.price_special_currency, value: row.price_special_value }
|
|
296
|
-
]
|
|
297
|
-
}));
|
|
298
|
-
// Result: [{ ref: "PROD-001", price: [{ type: "DEFAULT", ... }, { type: "SPECIAL", ... }] }]
|
|
299
|
-
|
|
300
|
-
// Option B: Use custom resolver in mapping (see Step 3)
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
**Step 3: Map**
|
|
304
|
-
|
|
305
|
-
**Option A - After Code Transformation:**
|
|
306
|
-
```typescript
|
|
307
|
-
const mapper = new UniversalMapper({
|
|
308
|
-
fields: {
|
|
309
|
-
ref: { source: "ref" },
|
|
310
|
-
price: {
|
|
311
|
-
source: "price", // ← Reads from transformed record.price
|
|
312
|
-
isArray: true,
|
|
313
|
-
fields: {
|
|
314
|
-
type: { source: "type" },
|
|
315
|
-
currency: { source: "currency" },
|
|
316
|
-
value: { source: "value", resolver: "sdk.parseFloat" }
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
for (const record of records) {
|
|
323
|
-
const result = await mapper.map(record); // ← Pass transformed structure
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
**Option B - Using Custom Resolver:**
|
|
328
|
-
```typescript
|
|
329
|
-
const mapper = new UniversalMapper({
|
|
330
|
-
fields: {
|
|
331
|
-
ref: { source: "ref" },
|
|
332
|
-
price: {
|
|
333
|
-
resolver: "custom.buildPriceArray", // ← Custom resolver builds nested structure
|
|
334
|
-
// No source - resolver receives full record
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}, {
|
|
338
|
-
customResolvers: {
|
|
339
|
-
'custom.buildPriceArray': (value, sourceData) => {
|
|
340
|
-
// sourceData = full CSV row
|
|
341
|
-
return [
|
|
342
|
-
{
|
|
343
|
-
type: "DEFAULT",
|
|
344
|
-
currency: sourceData.price_default_currency,
|
|
345
|
-
value: parseFloat(sourceData.price_default_value)
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
type: "SPECIAL",
|
|
349
|
-
currency: sourceData.price_special_currency,
|
|
350
|
-
value: parseFloat(sourceData.price_special_value)
|
|
351
|
-
}
|
|
352
|
-
];
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
for (const record of records) {
|
|
358
|
-
const result = await mapper.map(record); // ← Pass flat CSV row
|
|
359
|
-
}
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
**Mapping Matches:**
|
|
363
|
-
- Option A: Transformed structure (`record.price[]`)
|
|
364
|
-
- Option B: Flat CSV row (`record.price_default_currency` via resolver)
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
### JSON Examples
|
|
369
|
-
|
|
370
|
-
#### Simple JSON: Array Extraction Needed
|
|
371
|
-
|
|
372
|
-
**Original JSON File:**
|
|
373
|
-
```json
|
|
374
|
-
{
|
|
375
|
-
"timestamp": "2025-01-15T10:00:00Z",
|
|
376
|
-
"warehouse": "WH-01",
|
|
377
|
-
"inventory": [
|
|
378
|
-
{
|
|
379
|
-
"product_code": "SKU-001",
|
|
380
|
-
"stock_level": 100,
|
|
381
|
-
"location": "A1"
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
"product_code": "SKU-002",
|
|
385
|
-
"stock_level": 50,
|
|
386
|
-
"location": "B2"
|
|
387
|
-
}
|
|
388
|
-
]
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
**Step 1: Parse**
|
|
393
|
-
```typescript
|
|
394
|
-
const jsonParser = new JSONParserService();
|
|
395
|
-
const parsed = await jsonParser.parse(jsonContent);
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
**Step 2: Parsed JavaScript Object (JSONParserService output)**
|
|
399
|
-
```javascript
|
|
400
|
-
{
|
|
401
|
-
timestamp: "2025-01-15T10:00:00Z",
|
|
402
|
-
warehouse: "WH-01",
|
|
403
|
-
inventory: [
|
|
404
|
-
{
|
|
405
|
-
product_code: "SKU-001",
|
|
406
|
-
stock_level: 100,
|
|
407
|
-
location: "A1"
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
product_code: "SKU-002",
|
|
411
|
-
stock_level: 50,
|
|
412
|
-
location: "B2"
|
|
413
|
-
}
|
|
414
|
-
]
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
**Step 3: Extraction**
|
|
419
|
-
```javascript
|
|
420
|
-
// Extract the array we want to process
|
|
421
|
-
const parsed = await jsonParser.parse(jsonContent);
|
|
422
|
-
const records = parsed.inventory; // Extract nested array
|
|
423
|
-
|
|
424
|
-
// Now we have an array of inventory items
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
**Step 4: Mapping Configuration**
|
|
428
|
-
```json
|
|
429
|
-
{
|
|
430
|
-
"fields": {
|
|
431
|
-
"skuRef": {
|
|
432
|
-
"source": "product_code",
|
|
433
|
-
"required": true
|
|
434
|
-
},
|
|
435
|
-
"qty": {
|
|
436
|
-
"source": "stock_level",
|
|
437
|
-
"resolver": "sdk.parseInt"
|
|
438
|
-
},
|
|
439
|
-
"locationRef": {
|
|
440
|
-
"source": "location"
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
**What gets passed to mapper.map():**
|
|
447
|
-
```javascript
|
|
448
|
-
// For each item in the inventory array:
|
|
449
|
-
await mapper.map({
|
|
450
|
-
product_code: "SKU-001",
|
|
451
|
-
stock_level: 100,
|
|
452
|
-
location: "A1"
|
|
453
|
-
});
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
**Key Points:**
|
|
457
|
-
- JSON parsing preserves nested structure
|
|
458
|
-
- You extract the array you need (`parsed.inventory`)
|
|
459
|
-
- Mapping config references the **extracted object** structure, not the full JSON
|
|
460
|
-
|
|
461
|
-
---
|
|
462
|
-
|
|
463
|
-
#### Complex JSON: Nested Structure with Transformation Options
|
|
464
|
-
|
|
465
|
-
**JSON File:**
|
|
466
|
-
```json
|
|
467
|
-
{
|
|
468
|
-
"inventory": {
|
|
469
|
-
"warehouse": "WH-001",
|
|
470
|
-
"items": [
|
|
471
|
-
{
|
|
472
|
-
"product": {
|
|
473
|
-
"sku": "SKU-001",
|
|
474
|
-
"details": {
|
|
475
|
-
"name": "Product 1"
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
"stock": {
|
|
479
|
-
"quantity": 100,
|
|
480
|
-
"status": "available"
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
"product": {
|
|
485
|
-
"sku": "SKU-002",
|
|
486
|
-
"details": {
|
|
487
|
-
"name": "Product 2"
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
"stock": {
|
|
491
|
-
"quantity": 50,
|
|
492
|
-
"status": "reserved"
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
]
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
**Step 1: Parse**
|
|
501
|
-
```typescript
|
|
502
|
-
const jsonParser = new JSONParserService();
|
|
503
|
-
const parsed = await jsonParser.parse(jsonContent);
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
**Parsed JavaScript Object:**
|
|
507
|
-
```javascript
|
|
508
|
-
{
|
|
509
|
-
inventory: {
|
|
510
|
-
warehouse: "WH-001",
|
|
511
|
-
items: [
|
|
512
|
-
{
|
|
513
|
-
product: { sku: "SKU-001", details: { name: "Product 1" } },
|
|
514
|
-
stock: { quantity: 100, status: "available" }
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
product: { sku: "SKU-002", details: { name: "Product 2" } },
|
|
518
|
-
stock: { quantity: 50, status: "reserved" }
|
|
519
|
-
}
|
|
520
|
-
]
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
**Step 2: Extract/Normalize**
|
|
526
|
-
```typescript
|
|
527
|
-
// Extract items array, and add warehouse from parent
|
|
528
|
-
const warehouse = parsed.inventory.warehouse;
|
|
529
|
-
const items = parsed.inventory.items;
|
|
530
|
-
|
|
531
|
-
// Option A: Transform to add warehouse to each item
|
|
532
|
-
const records = items.map(item => ({
|
|
533
|
-
warehouse: warehouse, // ← Add shared data
|
|
534
|
-
product: item.product,
|
|
535
|
-
stock: item.stock
|
|
536
|
-
}));
|
|
537
|
-
// Result: [{ warehouse: "WH-001", product: {...}, stock: {...} }, ...]
|
|
538
|
-
|
|
539
|
-
// Option B: Pass full structure and use dot notation in mapping
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
**Step 3: Map**
|
|
543
|
-
|
|
544
|
-
**Option A - After Transformation:**
|
|
545
|
-
```typescript
|
|
546
|
-
const mapper = new UniversalMapper({
|
|
547
|
-
fields: {
|
|
548
|
-
locationRef: { source: "warehouse" }, // ← From transformed record
|
|
549
|
-
productRef: { source: "product.sku" }, // ← From transformed record
|
|
550
|
-
quantity: { source: "stock.quantity" }, // ← From transformed record
|
|
551
|
-
status: { source: "stock.status" } // ← From transformed record
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
for (const record of records) {
|
|
556
|
-
const result = await mapper.map(record); // ← Pass transformed structure
|
|
557
|
-
}
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
**Option B - Direct Mapping (No Transformation):**
|
|
561
|
-
```typescript
|
|
562
|
-
// Map each item directly, accessing parent via root context
|
|
563
|
-
const mapper = new UniversalMapper({
|
|
564
|
-
fields: {
|
|
565
|
-
items: {
|
|
566
|
-
source: "inventory.items", // ← Extract items array
|
|
567
|
-
isArray: true, // ← Iterate over items
|
|
568
|
-
fields: {
|
|
569
|
-
locationRef: {
|
|
570
|
-
// Access parent warehouse from root context
|
|
571
|
-
// Note: You may need a custom resolver to access parent data
|
|
572
|
-
resolver: "custom.getWarehouse" // Custom resolver accesses root
|
|
573
|
-
},
|
|
574
|
-
productRef: { source: "product.sku" }, // ← From current item
|
|
575
|
-
quantity: { source: "stock.quantity" }, // ← From current item
|
|
576
|
-
status: { source: "stock.status" } // ← From current item
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
const result = await mapper.map(parsed); // ← Pass full parsed structure
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
**Mapping Matches:**
|
|
586
|
-
- Option A: Transformed structure (`record.warehouse`, `record.product.sku`)
|
|
587
|
-
- Option B: Full parsed structure (`inventory.items[]`, requires custom resolver for parent access)
|
|
588
|
-
|
|
589
|
-
---
|
|
590
|
-
|
|
591
|
-
#### Simple XML: Array Normalization Required
|
|
592
|
-
|
|
593
|
-
**Original XML File:**
|
|
594
|
-
```xml
|
|
595
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
596
|
-
<products>
|
|
597
|
-
<product>
|
|
598
|
-
<sku>SKU-001</sku>
|
|
599
|
-
<name>Product 1</name>
|
|
600
|
-
<quantity>100</quantity>
|
|
601
|
-
<location>LOC-A</location>
|
|
602
|
-
</product>
|
|
603
|
-
<product>
|
|
604
|
-
<sku>SKU-002</sku>
|
|
605
|
-
<name>Product 2</name>
|
|
606
|
-
<quantity>50</quantity>
|
|
607
|
-
<location>LOC-B</location>
|
|
608
|
-
</product>
|
|
609
|
-
</products>
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
**Step 1: Parse**
|
|
613
|
-
```typescript
|
|
614
|
-
const xmlParser = new XMLParserService();
|
|
615
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
**Step 2: Parsed JavaScript Object (XMLParserService output)**
|
|
619
|
-
```javascript
|
|
620
|
-
{
|
|
621
|
-
products: {
|
|
622
|
-
product: [ // Multiple <product> elements → array
|
|
623
|
-
{
|
|
624
|
-
sku: "SKU-001",
|
|
625
|
-
name: "Product 1",
|
|
626
|
-
quantity: 100, // Auto-parsed as number
|
|
627
|
-
location: "LOC-A"
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
sku: "SKU-002",
|
|
631
|
-
name: "Product 2",
|
|
632
|
-
quantity: 50,
|
|
633
|
-
location: "LOC-B"
|
|
634
|
-
}
|
|
635
|
-
]
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
**CRITICAL: XML Array Normalization**
|
|
641
|
-
|
|
642
|
-
XML parsing has a gotcha: single vs multiple elements
|
|
643
|
-
```javascript
|
|
644
|
-
// Multiple <product> elements
|
|
645
|
-
parsed.products.product // → array []
|
|
646
|
-
|
|
647
|
-
// Single <product> element (gotcha!)
|
|
648
|
-
parsed.products.product // → object {} (NOT array!)
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
**Step 3: Extraction with Normalization**
|
|
652
|
-
```javascript
|
|
653
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
654
|
-
|
|
655
|
-
// ALWAYS normalize XML arrays
|
|
656
|
-
const items = parsed.products?.product;
|
|
657
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
658
|
-
|
|
659
|
-
// Now records is ALWAYS an array
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
**Step 4: Mapping Configuration**
|
|
663
|
-
```json
|
|
664
|
-
{
|
|
665
|
-
"fields": {
|
|
666
|
-
"skuRef": {
|
|
667
|
-
"source": "sku",
|
|
668
|
-
"required": true
|
|
669
|
-
},
|
|
670
|
-
"name": {
|
|
671
|
-
"source": "name"
|
|
672
|
-
},
|
|
673
|
-
"qty": {
|
|
674
|
-
"source": "quantity",
|
|
675
|
-
"resolver": "sdk.parseInt"
|
|
676
|
-
},
|
|
677
|
-
"locationRef": {
|
|
678
|
-
"source": "location"
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
**What gets passed to mapper.map():**
|
|
685
|
-
```javascript
|
|
686
|
-
// For each normalized item:
|
|
687
|
-
await mapper.map({
|
|
688
|
-
sku: "SKU-001",
|
|
689
|
-
name: "Product 1",
|
|
690
|
-
quantity: 100,
|
|
691
|
-
location: "LOC-A"
|
|
692
|
-
});
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
**Key Points:**
|
|
696
|
-
- XML element names become JavaScript object keys
|
|
697
|
-
- Multiple elements create arrays
|
|
698
|
-
- Single element creates object (must normalize!)
|
|
699
|
-
- Always use: `Array.isArray(items) ? items : items ? [items] : []`
|
|
700
|
-
|
|
701
|
-
---
|
|
702
|
-
|
|
703
|
-
### Complex XML with Attributes
|
|
704
|
-
|
|
705
|
-
#### XML Attributes Use @ Prefix
|
|
706
|
-
|
|
707
|
-
**Original XML File:**
|
|
708
|
-
```xml
|
|
709
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
710
|
-
<inventory>
|
|
711
|
-
<item locationRef="LOC-A" skuRef="SKU-001">
|
|
712
|
-
<qty>100</qty>
|
|
713
|
-
<status>AVAILABLE</status>
|
|
714
|
-
<expectedOn>2025-01-20</expectedOn>
|
|
715
|
-
</item>
|
|
716
|
-
<item locationRef="LOC-B" skuRef="SKU-002">
|
|
717
|
-
<qty>50</qty>
|
|
718
|
-
<status>RESERVED</status>
|
|
719
|
-
</item>
|
|
720
|
-
</inventory>
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
**Step 2: Parsed JavaScript Object (XMLParserService output)**
|
|
724
|
-
```javascript
|
|
725
|
-
{
|
|
726
|
-
inventory: {
|
|
727
|
-
item: [
|
|
728
|
-
{
|
|
729
|
-
"@locationRef": "LOC-A", // Attributes use @ prefix
|
|
730
|
-
"@skuRef": "SKU-001",
|
|
731
|
-
qty: 100,
|
|
732
|
-
status: "AVAILABLE",
|
|
733
|
-
expectedOn: "2025-01-20"
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
"@locationRef": "LOC-B",
|
|
737
|
-
"@skuRef": "SKU-002",
|
|
738
|
-
qty: 50,
|
|
739
|
-
status: "RESERVED"
|
|
740
|
-
}
|
|
741
|
-
]
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
**Step 3: Extraction with Normalization**
|
|
747
|
-
```javascript
|
|
748
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
749
|
-
|
|
750
|
-
// Normalize array
|
|
751
|
-
const items = parsed.inventory?.item;
|
|
752
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
**Step 4: Mapping Configuration**
|
|
756
|
-
```json
|
|
757
|
-
{
|
|
758
|
-
"fields": {
|
|
759
|
-
"locationRef": {
|
|
760
|
-
"source": "@locationRef",
|
|
761
|
-
"required": true,
|
|
762
|
-
"resolver": "sdk.trim"
|
|
763
|
-
},
|
|
764
|
-
"skuRef": {
|
|
765
|
-
"source": "@skuRef",
|
|
766
|
-
"required": true,
|
|
767
|
-
"resolver": "sdk.trim"
|
|
768
|
-
},
|
|
769
|
-
"qty": {
|
|
770
|
-
"source": "qty",
|
|
771
|
-
"required": true,
|
|
772
|
-
"resolver": "sdk.parseInt"
|
|
773
|
-
},
|
|
774
|
-
"status": {
|
|
775
|
-
"source": "status",
|
|
776
|
-
"required": true,
|
|
777
|
-
"resolver": "sdk.uppercase"
|
|
778
|
-
},
|
|
779
|
-
"expectedOn": {
|
|
780
|
-
"source": "expectedOn",
|
|
781
|
-
"resolver": "sdk.formatDate"
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
**What gets passed to mapper.map():**
|
|
788
|
-
```javascript
|
|
789
|
-
// For each normalized item:
|
|
790
|
-
await mapper.map({
|
|
791
|
-
"@locationRef": "LOC-A", // @ prefix in JavaScript object
|
|
792
|
-
"@skuRef": "SKU-001",
|
|
793
|
-
qty: 100,
|
|
794
|
-
status: "AVAILABLE",
|
|
795
|
-
expectedOn: "2025-01-20"
|
|
796
|
-
});
|
|
797
|
-
```
|
|
798
|
-
|
|
799
|
-
**Key Points:**
|
|
800
|
-
- XML attributes use `@` prefix in parsed JavaScript object
|
|
801
|
-
- Mapping config uses `@` prefix to reference attributes
|
|
802
|
-
- This is a JavaScript object key, not XPath syntax
|
|
803
|
-
- Elements without `@` are child elements
|
|
804
|
-
|
|
805
|
-
---
|
|
806
|
-
|
|
807
|
-
#### Complex XML: Orders with Attributes and Transformation Options
|
|
808
|
-
|
|
809
|
-
**XML File:**
|
|
810
|
-
```xml
|
|
811
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
812
|
-
<Orders>
|
|
813
|
-
<Order id="ORD-001" orderDate="2025-01-22">
|
|
814
|
-
<Customer>
|
|
815
|
-
<Name>John Doe</Name>
|
|
816
|
-
<Email>john@example.com</Email>
|
|
817
|
-
</Customer>
|
|
818
|
-
<Items>
|
|
819
|
-
<Item sku="PROD-001" qty="2">
|
|
820
|
-
<Price>29.99</Price>
|
|
821
|
-
</Item>
|
|
822
|
-
<Item sku="PROD-002" qty="1">
|
|
823
|
-
<Price>49.99</Price>
|
|
824
|
-
</Item>
|
|
825
|
-
</Items>
|
|
826
|
-
</Order>
|
|
827
|
-
<Order id="ORD-002" orderDate="2025-01-23">
|
|
828
|
-
<Customer>
|
|
829
|
-
<Name>Jane Smith</Name>
|
|
830
|
-
<Email>jane@example.com</Email>
|
|
831
|
-
</Customer>
|
|
832
|
-
<Items>
|
|
833
|
-
<Item sku="PROD-003" qty="3">
|
|
834
|
-
<Price>19.99</Price>
|
|
835
|
-
</Item>
|
|
836
|
-
</Items>
|
|
837
|
-
</Order>
|
|
838
|
-
</Orders>
|
|
839
|
-
```
|
|
840
|
-
|
|
841
|
-
**Step 1: Parse (with attributes)**
|
|
842
|
-
```typescript
|
|
843
|
-
const xmlParser = new XMLParserService();
|
|
844
|
-
const parsed = await xmlParser.parse(xmlContent, { includeAttributes: true });
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
**Parsed JavaScript Object:**
|
|
848
|
-
```javascript
|
|
849
|
-
{
|
|
850
|
-
Orders: {
|
|
851
|
-
Order: [
|
|
852
|
-
{
|
|
853
|
-
"@id": "ORD-001", // ← Attributes prefixed with @
|
|
854
|
-
"@orderDate": "2025-01-22",
|
|
855
|
-
Customer: {
|
|
856
|
-
Name: "John Doe",
|
|
857
|
-
Email: "john@example.com"
|
|
858
|
-
},
|
|
859
|
-
Items: {
|
|
860
|
-
Item: [
|
|
861
|
-
{
|
|
862
|
-
"@sku": "PROD-001", // ← Attributes prefixed with @
|
|
863
|
-
"@qty": "2",
|
|
864
|
-
Price: "29.99"
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
"@sku": "PROD-002",
|
|
868
|
-
"@qty": "1",
|
|
869
|
-
Price: "49.99"
|
|
870
|
-
}
|
|
871
|
-
]
|
|
872
|
-
}
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
"@id": "ORD-002",
|
|
876
|
-
"@orderDate": "2025-01-23",
|
|
877
|
-
Customer: { ... },
|
|
878
|
-
Items: { ... }
|
|
879
|
-
}
|
|
880
|
-
]
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
```
|
|
884
|
-
|
|
885
|
-
**Step 2: Extract/Normalize**
|
|
886
|
-
```typescript
|
|
887
|
-
const orders = parsed.Orders.Order;
|
|
888
|
-
const records = Array.isArray(orders) ? orders : orders ? [orders] : [];
|
|
889
|
-
// Result: [{ "@id": "ORD-001", "@orderDate": "...", Customer: {...}, Items: {...} }, ...]
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
**Step 3: Map**
|
|
893
|
-
|
|
894
|
-
**Option A: Map Each Order, Then Items Separately**
|
|
895
|
-
```typescript
|
|
896
|
-
const orderMapper = new UniversalMapper({
|
|
897
|
-
fields: {
|
|
898
|
-
orderRef: { source: "@id" }, // ← XML attribute
|
|
899
|
-
orderDate: { source: "@orderDate", resolver: "sdk.parseDate" },
|
|
900
|
-
customerName: { source: "Customer.Name" },
|
|
901
|
-
customerEmail: { source: "Customer.Email" },
|
|
902
|
-
items: {
|
|
903
|
-
source: "Items.Item", // ← Extract items array
|
|
904
|
-
isArray: true, // ← Iterate over items
|
|
905
|
-
fields: {
|
|
906
|
-
productRef: { source: "@sku" }, // ← Attribute from item
|
|
907
|
-
quantity: { source: "@qty", resolver: "sdk.parseInt" },
|
|
908
|
-
price: { source: "Price", resolver: "sdk.parseFloat" }
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
for (const order of records) {
|
|
915
|
-
const result = await orderMapper.map({ Order: order }); // ← Wrap for context
|
|
916
|
-
// Mapping: Order.@id, Order.Customer.Name, Order.Items.Item[].@sku
|
|
917
|
-
}
|
|
918
|
-
```
|
|
919
|
-
|
|
920
|
-
**Option B: Transform First, Then Map**
|
|
921
|
-
```typescript
|
|
922
|
-
// Extract items and flatten with order data
|
|
923
|
-
const allItems = [];
|
|
924
|
-
for (const order of records) {
|
|
925
|
-
const orderId = order["@id"];
|
|
926
|
-
const orderDate = order["@orderDate"];
|
|
927
|
-
const items = Array.isArray(order.Items.Item)
|
|
928
|
-
? order.Items.Item
|
|
929
|
-
: order.Items.Item
|
|
930
|
-
? [order.Items.Item]
|
|
931
|
-
: [];
|
|
932
|
-
|
|
933
|
-
for (const item of items) {
|
|
934
|
-
allItems.push({
|
|
935
|
-
orderId: orderId, // ← Add from parent
|
|
936
|
-
orderDate: orderDate, // ← Add from parent
|
|
937
|
-
sku: item["@sku"], // ← From item attribute
|
|
938
|
-
qty: item["@qty"], // ← From item attribute
|
|
939
|
-
price: item.Price // ← From item element
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// Now map flattened structure
|
|
945
|
-
const itemMapper = new UniversalMapper({
|
|
946
|
-
fields: {
|
|
947
|
-
orderRef: { source: "orderId" }, // ← From transformed structure
|
|
948
|
-
orderDate: { source: "orderDate", resolver: "sdk.parseDate" },
|
|
949
|
-
productRef: { source: "sku" }, // ← From transformed structure
|
|
950
|
-
quantity: { source: "qty", resolver: "sdk.parseInt" },
|
|
951
|
-
price: { source: "price", resolver: "sdk.parseFloat" }
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
for (const item of allItems) {
|
|
956
|
-
const result = await itemMapper.map(item); // ← Pass transformed structure
|
|
957
|
-
}
|
|
958
|
-
```
|
|
959
|
-
|
|
960
|
-
**Mapping Matches:**
|
|
961
|
-
- Option A: Parsed XML structure (`Order.@id`, `Order.Items.Item[].@sku`)
|
|
962
|
-
- Option B: Transformed structure (`item.orderId`, `item.sku`)
|
|
963
|
-
|
|
964
|
-
---
|
|
965
|
-
|
|
966
|
-
### Complex JSON with Deep Nesting
|
|
967
|
-
|
|
968
|
-
#### Nested Path Navigation
|
|
969
|
-
|
|
970
|
-
**Original JSON File:**
|
|
971
|
-
```json
|
|
972
|
-
{
|
|
973
|
-
"warehouse": {
|
|
974
|
-
"id": "WH-01",
|
|
975
|
-
"location": {
|
|
976
|
-
"code": "LOC-A",
|
|
977
|
-
"name": "Warehouse A"
|
|
978
|
-
},
|
|
979
|
-
"inventory": {
|
|
980
|
-
"items": [
|
|
981
|
-
{
|
|
982
|
-
"product": {
|
|
983
|
-
"sku": "SKU-001",
|
|
984
|
-
"details": {
|
|
985
|
-
"name": "Widget",
|
|
986
|
-
"category": "Electronics"
|
|
987
|
-
}
|
|
988
|
-
},
|
|
989
|
-
"stock": {
|
|
990
|
-
"available": 100,
|
|
991
|
-
"reserved": 20
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
]
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
**Step 2: Parsed JavaScript Object (JSONParserService output)**
|
|
1001
|
-
```javascript
|
|
1002
|
-
{
|
|
1003
|
-
warehouse: {
|
|
1004
|
-
id: "WH-01",
|
|
1005
|
-
location: {
|
|
1006
|
-
code: "LOC-A",
|
|
1007
|
-
name: "Warehouse A"
|
|
1008
|
-
},
|
|
1009
|
-
inventory: {
|
|
1010
|
-
items: [
|
|
1011
|
-
{
|
|
1012
|
-
product: {
|
|
1013
|
-
sku: "SKU-001",
|
|
1014
|
-
details: {
|
|
1015
|
-
name: "Widget",
|
|
1016
|
-
category: "Electronics"
|
|
1017
|
-
}
|
|
1018
|
-
},
|
|
1019
|
-
stock: {
|
|
1020
|
-
available: 100,
|
|
1021
|
-
reserved: 20
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
]
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
**Step 3: Extraction**
|
|
1031
|
-
```javascript
|
|
1032
|
-
const parsed = await jsonParser.parse(jsonContent);
|
|
1033
|
-
|
|
1034
|
-
// Extract the nested array
|
|
1035
|
-
const records = parsed.warehouse.inventory.items;
|
|
1036
|
-
|
|
1037
|
-
// For shared context, store parent data
|
|
1038
|
-
const warehouseCode = parsed.warehouse.location.code;
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
**Step 4: Mapping Configuration**
|
|
1042
|
-
```json
|
|
1043
|
-
{
|
|
1044
|
-
"fields": {
|
|
1045
|
-
"skuRef": {
|
|
1046
|
-
"source": "product.sku",
|
|
1047
|
-
"required": true
|
|
1048
|
-
},
|
|
1049
|
-
"name": {
|
|
1050
|
-
"source": "product.details.name"
|
|
1051
|
-
},
|
|
1052
|
-
"qty": {
|
|
1053
|
-
"source": "stock.available",
|
|
1054
|
-
"resolver": "sdk.parseInt"
|
|
1055
|
-
},
|
|
1056
|
-
"locationRef": {
|
|
1057
|
-
"value": "LOC-A"
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
**What gets passed to mapper.map():**
|
|
1064
|
-
```javascript
|
|
1065
|
-
// For each item in the items array:
|
|
1066
|
-
await mapper.map({
|
|
1067
|
-
product: {
|
|
1068
|
-
sku: "SKU-001",
|
|
1069
|
-
details: {
|
|
1070
|
-
name: "Widget",
|
|
1071
|
-
category: "Electronics"
|
|
1072
|
-
}
|
|
1073
|
-
},
|
|
1074
|
-
stock: {
|
|
1075
|
-
available: 100,
|
|
1076
|
-
reserved: 20
|
|
1077
|
-
}
|
|
1078
|
-
});
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
**Key Points:**
|
|
1082
|
-
- Dot notation navigates nested JavaScript objects
|
|
1083
|
-
- `"product.sku"` becomes `obj.product.sku` in JavaScript
|
|
1084
|
-
- `"product.details.name"` becomes `obj.product.details.name`
|
|
1085
|
-
- You can reference any depth of nesting
|
|
1086
|
-
|
|
1087
|
-
---
|
|
1088
|
-
|
|
1089
|
-
### XML with Shared Parent Data
|
|
1090
|
-
|
|
1091
|
-
#### Real-World Pattern: Enriching Child Records
|
|
1092
|
-
|
|
1093
|
-
This is a common pattern where parent-level data needs to be added to each child record.
|
|
1094
|
-
|
|
1095
|
-
**Original XML File:**
|
|
1096
|
-
```xml
|
|
1097
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
1098
|
-
<InventoryStatus>
|
|
1099
|
-
<ItemInventory>
|
|
1100
|
-
<FacilityId>WAREHOUSE-01</FacilityId>
|
|
1101
|
-
<Item>
|
|
1102
|
-
<SKU>SKU-001</SKU>
|
|
1103
|
-
<Quantity>100</Quantity>
|
|
1104
|
-
<Status>AVAILABLE</Status>
|
|
1105
|
-
</Item>
|
|
1106
|
-
<Item>
|
|
1107
|
-
<SKU>SKU-002</SKU>
|
|
1108
|
-
<Quantity>50</Quantity>
|
|
1109
|
-
<Status>RESERVED</Status>
|
|
1110
|
-
</Item>
|
|
1111
|
-
</ItemInventory>
|
|
1112
|
-
</InventoryStatus>
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
**Step 2: Parsed JavaScript Object (XMLParserService output)**
|
|
1116
|
-
```javascript
|
|
1117
|
-
{
|
|
1118
|
-
InventoryStatus: {
|
|
1119
|
-
ItemInventory: {
|
|
1120
|
-
FacilityId: "WAREHOUSE-01",
|
|
1121
|
-
Item: [ // Multiple Items
|
|
1122
|
-
{
|
|
1123
|
-
SKU: "SKU-001",
|
|
1124
|
-
Quantity: 100,
|
|
1125
|
-
Status: "AVAILABLE"
|
|
1126
|
-
},
|
|
1127
|
-
{
|
|
1128
|
-
SKU: "SKU-002",
|
|
1129
|
-
Quantity: 50,
|
|
1130
|
-
Status: "RESERVED"
|
|
1131
|
-
}
|
|
1132
|
-
]
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
```
|
|
1137
|
-
|
|
1138
|
-
**Step 3: Transformation - Enrich Each Item**
|
|
1139
|
-
```javascript
|
|
1140
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1141
|
-
|
|
1142
|
-
// Extract parent data
|
|
1143
|
-
const facilityId = parsed.InventoryStatus.ItemInventory.FacilityId;
|
|
1144
|
-
|
|
1145
|
-
// Extract items
|
|
1146
|
-
const items = parsed.InventoryStatus.ItemInventory.Item;
|
|
1147
|
-
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1148
|
-
|
|
1149
|
-
// TRANSFORM: Wrap each item with facility context
|
|
1150
|
-
const enrichedRecords = normalizedItems.map(item => ({
|
|
1151
|
-
itemInventory: {
|
|
1152
|
-
FacilityId: facilityId, // Add parent data to each item
|
|
1153
|
-
...item // Spread item properties
|
|
1154
|
-
}
|
|
1155
|
-
}));
|
|
1156
|
-
|
|
1157
|
-
// Now each record has the FacilityId embedded
|
|
1158
|
-
```
|
|
1159
|
-
|
|
1160
|
-
**Result of Transformation:**
|
|
1161
|
-
```javascript
|
|
1162
|
-
[
|
|
1163
|
-
{
|
|
1164
|
-
itemInventory: {
|
|
1165
|
-
FacilityId: "WAREHOUSE-01", // Parent data added
|
|
1166
|
-
SKU: "SKU-001",
|
|
1167
|
-
Quantity: 100,
|
|
1168
|
-
Status: "AVAILABLE"
|
|
1169
|
-
}
|
|
1170
|
-
},
|
|
1171
|
-
{
|
|
1172
|
-
itemInventory: {
|
|
1173
|
-
FacilityId: "WAREHOUSE-01", // Parent data added
|
|
1174
|
-
SKU: "SKU-002",
|
|
1175
|
-
Quantity: 50,
|
|
1176
|
-
Status: "RESERVED"
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
]
|
|
1180
|
-
```
|
|
1181
|
-
|
|
1182
|
-
**Step 4: Mapping Configuration**
|
|
1183
|
-
```json
|
|
1184
|
-
{
|
|
1185
|
-
"fields": {
|
|
1186
|
-
"locationRef": {
|
|
1187
|
-
"source": "itemInventory.FacilityId",
|
|
1188
|
-
"required": true
|
|
1189
|
-
},
|
|
1190
|
-
"skuRef": {
|
|
1191
|
-
"source": "itemInventory.SKU",
|
|
1192
|
-
"required": true
|
|
1193
|
-
},
|
|
1194
|
-
"qty": {
|
|
1195
|
-
"source": "itemInventory.Quantity",
|
|
1196
|
-
"resolver": "sdk.parseInt"
|
|
1197
|
-
},
|
|
1198
|
-
"status": {
|
|
1199
|
-
"source": "itemInventory.Status",
|
|
1200
|
-
"resolver": "sdk.uppercase"
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
```
|
|
1205
|
-
|
|
1206
|
-
**What gets passed to mapper.map():**
|
|
1207
|
-
```javascript
|
|
1208
|
-
// For each enriched record:
|
|
1209
|
-
await mapper.map({
|
|
1210
|
-
itemInventory: {
|
|
1211
|
-
FacilityId: "WAREHOUSE-01",
|
|
1212
|
-
SKU: "SKU-001",
|
|
1213
|
-
Quantity: 100,
|
|
1214
|
-
Status: "AVAILABLE"
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
```
|
|
1218
|
-
|
|
1219
|
-
**Why This Works:**
|
|
1220
|
-
- You transformed the data BEFORE mapping
|
|
1221
|
-
- The mapping config references the TRANSFORMED structure
|
|
1222
|
-
- Path `"itemInventory.FacilityId"` exists in the JavaScript object
|
|
1223
|
-
- This is NOT XPath - it's JavaScript property access
|
|
1224
|
-
|
|
1225
|
-
**Why Transformation Needed:**
|
|
1226
|
-
- XML has `FacilityId` in parent `ItemInventory` (shared by all Items)
|
|
1227
|
-
- Each `Item` needs its own `FacilityId` to create complete records
|
|
1228
|
-
- Mapping can't easily access parent data when iterating arrays
|
|
1229
|
-
- Solution: Transform to combine parent data with each child before mapping
|
|
1230
|
-
|
|
1231
|
-
**Complete Example Code:**
|
|
1232
|
-
```typescript
|
|
1233
|
-
import { XMLParserService, UniversalMapper } from '@fluentcommerce/fc-connect-sdk';
|
|
1234
|
-
|
|
1235
|
-
const xmlParser = new XMLParserService();
|
|
1236
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1237
|
-
|
|
1238
|
-
// Step 1: Extract parent data
|
|
1239
|
-
const facilityId = parsed.InventoryStatus.ItemInventory.FacilityId;
|
|
1240
|
-
|
|
1241
|
-
// Step 2: Extract and normalize items
|
|
1242
|
-
const items = parsed.InventoryStatus.ItemInventory.Item;
|
|
1243
|
-
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1244
|
-
|
|
1245
|
-
// Step 3: Transform - wrap each item
|
|
1246
|
-
const enrichedRecords = normalizedItems.map(item => ({
|
|
1247
|
-
itemInventory: {
|
|
1248
|
-
FacilityId: facilityId,
|
|
1249
|
-
...item
|
|
1250
|
-
}
|
|
1251
|
-
}));
|
|
1252
|
-
|
|
1253
|
-
// Step 4: Map each enriched record
|
|
1254
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
1255
|
-
|
|
1256
|
-
for (const record of enrichedRecords) {
|
|
1257
|
-
const result = await mapper.map(record);
|
|
1258
|
-
|
|
1259
|
-
if (result.success) {
|
|
1260
|
-
console.log('Mapped:', result.data);
|
|
1261
|
-
} else {
|
|
1262
|
-
console.error('Mapping failed:', result.errors);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
```
|
|
1266
|
-
|
|
1267
|
-
---
|
|
1268
|
-
|
|
1269
|
-
## Common Transformation Patterns
|
|
1270
|
-
|
|
1271
|
-
### Pattern 1: No Transformation (Flat CSV)
|
|
1272
|
-
|
|
1273
|
-
**Use Case:** CSV with flat structure
|
|
1274
|
-
|
|
1275
|
-
```javascript
|
|
1276
|
-
// Parsed CSV is already array of flat objects
|
|
1277
|
-
const records = parsedCsv;
|
|
1278
|
-
|
|
1279
|
-
// Map directly
|
|
1280
|
-
for (const record of records) {
|
|
1281
|
-
await mapper.map(record);
|
|
1282
|
-
}
|
|
1283
|
-
```
|
|
1284
|
-
|
|
1285
|
-
### Pattern 2: Array Extraction (Nested JSON)
|
|
1286
|
-
|
|
1287
|
-
**Use Case:** JSON with nested data array
|
|
1288
|
-
|
|
1289
|
-
```javascript
|
|
1290
|
-
// Extract the array you need
|
|
1291
|
-
const parsed = await jsonParser.parse(jsonContent);
|
|
1292
|
-
const records = parsed.data.inventory.items;
|
|
1293
|
-
|
|
1294
|
-
// Map each item
|
|
1295
|
-
for (const record of records) {
|
|
1296
|
-
await mapper.map(record);
|
|
1297
|
-
}
|
|
1298
|
-
```
|
|
1299
|
-
|
|
1300
|
-
### Pattern 3: Array Normalization (XML)
|
|
1301
|
-
|
|
1302
|
-
**Use Case:** XML with repeating elements
|
|
1303
|
-
|
|
1304
|
-
```javascript
|
|
1305
|
-
// Always normalize XML arrays
|
|
1306
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1307
|
-
const items = parsed.products?.product;
|
|
1308
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1309
|
-
|
|
1310
|
-
// Now safe to iterate
|
|
1311
|
-
for (const record of records) {
|
|
1312
|
-
await mapper.map(record);
|
|
1313
|
-
}
|
|
1314
|
-
```
|
|
1315
|
-
|
|
1316
|
-
### Pattern 4: Parent Data Enrichment (XML with Context)
|
|
1317
|
-
|
|
1318
|
-
**Use Case:** Child elements need parent data
|
|
1319
|
-
|
|
1320
|
-
```javascript
|
|
1321
|
-
// Extract parent data
|
|
1322
|
-
const facilityId = parsed.warehouse.facility.id;
|
|
1323
|
-
|
|
1324
|
-
// Extract items
|
|
1325
|
-
const items = parsed.warehouse.facility.inventory;
|
|
1326
|
-
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1327
|
-
|
|
1328
|
-
// Enrich each item
|
|
1329
|
-
const enrichedRecords = normalizedItems.map(item => ({
|
|
1330
|
-
...item,
|
|
1331
|
-
facilityId: facilityId // Add parent data
|
|
1332
|
-
}));
|
|
1333
|
-
|
|
1334
|
-
// Map enriched records
|
|
1335
|
-
for (const record of enrichedRecords) {
|
|
1336
|
-
await mapper.map(record);
|
|
1337
|
-
}
|
|
1338
|
-
```
|
|
1339
|
-
|
|
1340
|
-
### Pattern 5: Attribute Handling (XML Attributes)
|
|
1341
|
-
|
|
1342
|
-
**Use Case:** XML with attributes and elements
|
|
1343
|
-
|
|
1344
|
-
```javascript
|
|
1345
|
-
// XML Parser adds @ prefix for attributes
|
|
1346
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1347
|
-
const items = parsed.catalog?.product;
|
|
1348
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1349
|
-
|
|
1350
|
-
// Mapping config uses @ prefix
|
|
1351
|
-
const mappingConfig = {
|
|
1352
|
-
fields: {
|
|
1353
|
-
id: { source: "@id" }, // Attribute
|
|
1354
|
-
name: { source: "name" }, // Element
|
|
1355
|
-
price: { source: "@price" } // Attribute
|
|
1356
|
-
}
|
|
1357
|
-
};
|
|
1358
|
-
```
|
|
1359
|
-
|
|
1360
|
-
---
|
|
1361
|
-
|
|
1362
|
-
## Key Takeaways
|
|
1363
|
-
|
|
1364
|
-
### 1. Mapping Uses Parsed JavaScript Objects
|
|
1365
|
-
|
|
1366
|
-
✅ **Mapping paths match the JavaScript object structure you pass to `mapper.map()`**
|
|
1367
|
-
|
|
1368
|
-
```typescript
|
|
1369
|
-
// What you pass:
|
|
1370
|
-
await mapper.map({ itemInventory: { FacilityId: "...", Item: {...} } });
|
|
1371
|
-
|
|
1372
|
-
// Mapping paths must match:
|
|
1373
|
-
{ source: "itemInventory.FacilityId" } // ✅ Matches
|
|
1374
|
-
{ source: "InventoryStatus.ItemInventory.FacilityId" } // ❌ Doesn't match
|
|
1375
|
-
```
|
|
1376
|
-
|
|
1377
|
-
### 2. When Transformation is Needed
|
|
1378
|
-
|
|
1379
|
-
**Transform when:**
|
|
1380
|
-
- ✅ Parent data needs to be combined with child records (like XML with shared parent data)
|
|
1381
|
-
- ✅ Flat structure needs to become nested (CSV → nested JSON)
|
|
1382
|
-
- ✅ Multiple data sources need to be combined
|
|
1383
|
-
- ✅ Complex business logic is easier in code than mapping
|
|
1384
|
-
|
|
1385
|
-
**Don't transform when:**
|
|
1386
|
-
- ✅ Structure already matches target format
|
|
1387
|
-
- ✅ Simple field renaming is sufficient
|
|
1388
|
-
- ✅ Resolvers can handle the transformation
|
|
1389
|
-
|
|
1390
|
-
### 3. XML Attributes
|
|
1391
|
-
|
|
1392
|
-
**XML attributes are prefixed with `@` in parsed objects:**
|
|
1393
|
-
```xml
|
|
1394
|
-
<Order id="ORD-001">
|
|
1395
|
-
```
|
|
1396
|
-
```javascript
|
|
1397
|
-
{ Order: { "@id": "ORD-001" } }
|
|
1398
|
-
```
|
|
1399
|
-
```json
|
|
1400
|
-
{ "source": "Order@id" } // ← No dot before @
|
|
1401
|
-
```
|
|
1402
|
-
|
|
1403
|
-
### 4. Array Normalization
|
|
1404
|
-
|
|
1405
|
-
**Always normalize XML arrays (single element → object, multiple → array):**
|
|
1406
|
-
```typescript
|
|
1407
|
-
const items = parsed.products?.product;
|
|
1408
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1409
|
-
```
|
|
1410
|
-
|
|
1411
|
-
### 5. Mapping Pattern Summary
|
|
1412
|
-
|
|
1413
|
-
| Format | Parse Result | Extract/Normalize | Mapping Matches |
|
|
1414
|
-
|--------|-------------|-------------------|-----------------|
|
|
1415
|
-
| **CSV** | Array of flat objects | Already normalized | Flat record structure |
|
|
1416
|
-
| **JSON** | Nested objects | Extract array, optionally transform | Passed structure |
|
|
1417
|
-
| **XML** | Nested objects with `@` attributes | Extract array, normalize, optionally transform | Passed structure |
|
|
1418
|
-
|
|
1419
|
-
---
|
|
1420
|
-
|
|
1421
|
-
## Visual Flow Diagrams
|
|
1422
|
-
|
|
1423
|
-
### Data Flow: CSV to Fluent
|
|
1424
|
-
|
|
1425
|
-
```
|
|
1426
|
-
┌───────────────────────────────────────────────────────────────────┐
|
|
1427
|
-
│ CSV FILE │
|
|
1428
|
-
├───────────────────────────────────────────────────────────────────┤
|
|
1429
|
-
│ sku_id,quantity,location_code │
|
|
1430
|
-
│ SKU-001,100,LOC-A │
|
|
1431
|
-
│ SKU-002,50,LOC-B │
|
|
1432
|
-
└───────────────────────────────────────────────────────────────────┘
|
|
1433
|
-
│
|
|
1434
|
-
│ CSVParserService.parse()
|
|
1435
|
-
↓
|
|
1436
|
-
┌───────────────────────────────────────────────────────────────────┐
|
|
1437
|
-
│ PARSED JAVASCRIPT ARRAY │
|
|
1438
|
-
├───────────────────────────────────────────────────────────────────┤
|
|
1439
|
-
│ [ │
|
|
1440
|
-
│ { sku_id: "SKU-001", quantity: "100", location_code: "LOC-A" }, │
|
|
1441
|
-
│ { sku_id: "SKU-002", quantity: "50", location_code: "LOC-B" } │
|
|
1442
|
-
│ ] │
|
|
1443
|
-
└───────────────────────────────────────────────────────────────────┘
|
|
1444
|
-
│
|
|
1445
|
-
│ No transformation needed (flat)
|
|
1446
|
-
↓
|
|
1447
|
-
┌───────────────────────────────────────────────────────────────────┐
|
|
1448
|
-
│ WHAT MAPPER RECEIVES │
|
|
1449
|
-
├───────────────────────────────────────────────────────────────────┤
|
|
1450
|
-
│ { │
|
|
1451
|
-
│ sku_id: "SKU-001", ← mapping: "skuRef" from "sku_id" │
|
|
1452
|
-
│ quantity: "100", ← mapping: "qty" from "quantity" │
|
|
1453
|
-
│ location_code: "LOC-A" ← mapping: "locationRef" from ... │
|
|
1454
|
-
│ } │
|
|
1455
|
-
└───────────────────────────────────────────────────────────────────┘
|
|
1456
|
-
│
|
|
1457
|
-
│ UniversalMapper.map()
|
|
1458
|
-
↓
|
|
1459
|
-
┌───────────────────────────────────────────────────────────────────┐
|
|
1460
|
-
│ FLUENT FORMAT │
|
|
1461
|
-
├───────────────────────────────────────────────────────────────────┤
|
|
1462
|
-
│ { │
|
|
1463
|
-
│ skuRef: "SKU-001", │
|
|
1464
|
-
│ qty: 100, ← Converted to number by sdk.parseInt │
|
|
1465
|
-
│ locationRef: "LOC-A" │
|
|
1466
|
-
│ } │
|
|
1467
|
-
└───────────────────────────────────────────────────────────────────┘
|
|
1468
|
-
```
|
|
1469
|
-
|
|
1470
|
-
### Data Flow: XML with Attributes to Fluent
|
|
1471
|
-
|
|
1472
|
-
```
|
|
1473
|
-
┌────────────────────────────────────────────────────────────────────┐
|
|
1474
|
-
│ XML FILE │
|
|
1475
|
-
├────────────────────────────────────────────────────────────────────┤
|
|
1476
|
-
│ <inventory> │
|
|
1477
|
-
│ <item locationRef="LOC-A" skuRef="SKU-001"> │
|
|
1478
|
-
│ <qty>100</qty> │
|
|
1479
|
-
│ <status>AVAILABLE</status> │
|
|
1480
|
-
│ </item> │
|
|
1481
|
-
│ </inventory> │
|
|
1482
|
-
└────────────────────────────────────────────────────────────────────┘
|
|
1483
|
-
│
|
|
1484
|
-
│ XMLParserService.parse()
|
|
1485
|
-
↓
|
|
1486
|
-
┌────────────────────────────────────────────────────────────────────┐
|
|
1487
|
-
│ PARSED JAVASCRIPT OBJECT │
|
|
1488
|
-
├────────────────────────────────────────────────────────────────────┤
|
|
1489
|
-
│ { │
|
|
1490
|
-
│ inventory: { │
|
|
1491
|
-
│ item: { │
|
|
1492
|
-
│ "@locationRef": "LOC-A", ← @ prefix for attributes │
|
|
1493
|
-
│ "@skuRef": "SKU-001", │
|
|
1494
|
-
│ qty: 100, ← Elements without @ │
|
|
1495
|
-
│ status: "AVAILABLE" │
|
|
1496
|
-
│ } │
|
|
1497
|
-
│ } │
|
|
1498
|
-
│ } │
|
|
1499
|
-
└────────────────────────────────────────────────────────────────────┘
|
|
1500
|
-
│
|
|
1501
|
-
│ Extract: parsed.inventory.item
|
|
1502
|
-
│ Normalize: single element, wrap in array
|
|
1503
|
-
↓
|
|
1504
|
-
┌────────────────────────────────────────────────────────────────────┐
|
|
1505
|
-
│ WHAT MAPPER RECEIVES │
|
|
1506
|
-
├────────────────────────────────────────────────────────────────────┤
|
|
1507
|
-
│ { │
|
|
1508
|
-
│ "@locationRef": "LOC-A", ← mapping: source "@locationRef" │
|
|
1509
|
-
│ "@skuRef": "SKU-001", ← mapping: source "@skuRef" │
|
|
1510
|
-
│ qty: 100, ← mapping: source "qty" │
|
|
1511
|
-
│ status: "AVAILABLE" ← mapping: source "status" │
|
|
1512
|
-
│ } │
|
|
1513
|
-
└────────────────────────────────────────────────────────────────────┘
|
|
1514
|
-
│
|
|
1515
|
-
│ UniversalMapper.map()
|
|
1516
|
-
↓
|
|
1517
|
-
┌────────────────────────────────────────────────────────────────────┐
|
|
1518
|
-
│ FLUENT FORMAT │
|
|
1519
|
-
├────────────────────────────────────────────────────────────────────┤
|
|
1520
|
-
│ { │
|
|
1521
|
-
│ locationRef: "LOC-A", │
|
|
1522
|
-
│ skuRef: "SKU-001", │
|
|
1523
|
-
│ qty: 100, │
|
|
1524
|
-
│ status: "AVAILABLE" │
|
|
1525
|
-
│ } │
|
|
1526
|
-
└────────────────────────────────────────────────────────────────────┘
|
|
1527
|
-
```
|
|
1528
|
-
|
|
1529
|
-
### Data Flow: XML with Parent Data Enrichment
|
|
1530
|
-
|
|
1531
|
-
```
|
|
1532
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
1533
|
-
│ XML FILE │
|
|
1534
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
1535
|
-
│ <InventoryStatus> │
|
|
1536
|
-
│ <ItemInventory> │
|
|
1537
|
-
│ <FacilityId>WH-01</FacilityId> ← Parent data │
|
|
1538
|
-
│ <Item><SKU>SKU-001</SKU><Qty>100</Qty></Item> ← Children │
|
|
1539
|
-
│ <Item><SKU>SKU-002</SKU><Qty>50</Qty></Item> │
|
|
1540
|
-
│ </ItemInventory> │
|
|
1541
|
-
│ </InventoryStatus> │
|
|
1542
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
1543
|
-
│
|
|
1544
|
-
│ XMLParserService.parse()
|
|
1545
|
-
↓
|
|
1546
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
1547
|
-
│ PARSED JAVASCRIPT OBJECT │
|
|
1548
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
1549
|
-
│ { │
|
|
1550
|
-
│ InventoryStatus: { │
|
|
1551
|
-
│ ItemInventory: { │
|
|
1552
|
-
│ FacilityId: "WH-01", ← Parent data │
|
|
1553
|
-
│ Item: [ ← Array of children │
|
|
1554
|
-
│ { SKU: "SKU-001", Qty: 100 }, │
|
|
1555
|
-
│ { SKU: "SKU-002", Qty: 50 } │
|
|
1556
|
-
│ ] │
|
|
1557
|
-
│ } │
|
|
1558
|
-
│ } │
|
|
1559
|
-
│ } │
|
|
1560
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
1561
|
-
│
|
|
1562
|
-
│ TRANSFORMATION:
|
|
1563
|
-
│ 1. Extract facilityId
|
|
1564
|
-
│ 2. Normalize Item array
|
|
1565
|
-
│ 3. Wrap each item with facilityId
|
|
1566
|
-
↓
|
|
1567
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
1568
|
-
│ TRANSFORMED RECORDS │
|
|
1569
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
1570
|
-
│ [ │
|
|
1571
|
-
│ { │
|
|
1572
|
-
│ itemInventory: { │
|
|
1573
|
-
│ FacilityId: "WH-01", ← Parent data added to each item │
|
|
1574
|
-
│ SKU: "SKU-001", │
|
|
1575
|
-
│ Qty: 100 │
|
|
1576
|
-
│ } │
|
|
1577
|
-
│ }, │
|
|
1578
|
-
│ { │
|
|
1579
|
-
│ itemInventory: { │
|
|
1580
|
-
│ FacilityId: "WH-01", ← Same parent data │
|
|
1581
|
-
│ SKU: "SKU-002", │
|
|
1582
|
-
│ Qty: 50 │
|
|
1583
|
-
│ } │
|
|
1584
|
-
│ } │
|
|
1585
|
-
│ ] │
|
|
1586
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
1587
|
-
│
|
|
1588
|
-
│ For each record...
|
|
1589
|
-
↓
|
|
1590
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
1591
|
-
│ WHAT MAPPER RECEIVES │
|
|
1592
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
1593
|
-
│ { │
|
|
1594
|
-
│ itemInventory: { │
|
|
1595
|
-
│ FacilityId: "WH-01", ← source: "itemInventory.FacilityId" │
|
|
1596
|
-
│ SKU: "SKU-001", ← source: "itemInventory.SKU" │
|
|
1597
|
-
│ Qty: 100 ← source: "itemInventory.Qty" │
|
|
1598
|
-
│ } │
|
|
1599
|
-
│ } │
|
|
1600
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
1601
|
-
│
|
|
1602
|
-
│ UniversalMapper.map()
|
|
1603
|
-
↓
|
|
1604
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
1605
|
-
│ FLUENT FORMAT │
|
|
1606
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
1607
|
-
│ { │
|
|
1608
|
-
│ locationRef: "WH-01", │
|
|
1609
|
-
│ skuRef: "SKU-001", │
|
|
1610
|
-
│ qty: 100 │
|
|
1611
|
-
│ } │
|
|
1612
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
1613
|
-
```
|
|
1614
|
-
|
|
1615
|
-
---
|
|
1616
|
-
|
|
1617
|
-
## Debugging Guide
|
|
1618
|
-
|
|
1619
|
-
### How to Figure Out What Paths to Use
|
|
1620
|
-
|
|
1621
|
-
#### Step 1: Log the Parsed Structure
|
|
1622
|
-
|
|
1623
|
-
```typescript
|
|
1624
|
-
import { XMLParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
1625
|
-
|
|
1626
|
-
const xmlParser = new XMLParserService();
|
|
1627
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1628
|
-
|
|
1629
|
-
// Log the ENTIRE parsed structure
|
|
1630
|
-
console.log('Parsed structure:', JSON.stringify(parsed, null, 2));
|
|
1631
|
-
```
|
|
1632
|
-
|
|
1633
|
-
This shows you the exact JavaScript object structure.
|
|
1634
|
-
|
|
1635
|
-
#### Step 2: Identify What You Need
|
|
1636
|
-
|
|
1637
|
-
Look at the output and find:
|
|
1638
|
-
- Where is the array of items you need to process?
|
|
1639
|
-
- What are the property names in the JavaScript object?
|
|
1640
|
-
- Are there any `@` prefixes (XML attributes)?
|
|
1641
|
-
|
|
1642
|
-
#### Step 3: Test Your Extraction
|
|
1643
|
-
|
|
1644
|
-
```typescript
|
|
1645
|
-
// Try to extract the array
|
|
1646
|
-
const items = parsed.inventory?.item;
|
|
1647
|
-
console.log('Extracted items:', items);
|
|
1648
|
-
|
|
1649
|
-
// Check if it's an array
|
|
1650
|
-
console.log('Is array?', Array.isArray(items));
|
|
1651
|
-
|
|
1652
|
-
// Normalize if needed
|
|
1653
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1654
|
-
console.log('Normalized records:', records);
|
|
1655
|
-
```
|
|
1656
|
-
|
|
1657
|
-
#### Step 4: Check a Single Record
|
|
1658
|
-
|
|
1659
|
-
```typescript
|
|
1660
|
-
// Look at one record
|
|
1661
|
-
const firstRecord = records[0];
|
|
1662
|
-
console.log('First record:', JSON.stringify(firstRecord, null, 2));
|
|
1663
|
-
```
|
|
1664
|
-
|
|
1665
|
-
This shows you exactly what structure the mapper will receive.
|
|
1666
|
-
|
|
1667
|
-
#### Step 5: Match Mapping Config to Structure
|
|
1668
|
-
|
|
1669
|
-
```typescript
|
|
1670
|
-
// Your mapping config paths must match the JavaScript object
|
|
1671
|
-
const mappingConfig = {
|
|
1672
|
-
fields: {
|
|
1673
|
-
// If you see: { "sku": "SKU-001" }
|
|
1674
|
-
skuRef: { source: "sku" },
|
|
1675
|
-
|
|
1676
|
-
// If you see: { "@sku": "SKU-001" }
|
|
1677
|
-
skuRef: { source: "@sku" },
|
|
1678
|
-
|
|
1679
|
-
// If you see: { "product": { "sku": "SKU-001" } }
|
|
1680
|
-
skuRef: { source: "product.sku" }
|
|
1681
|
-
}
|
|
1682
|
-
};
|
|
1683
|
-
```
|
|
1684
|
-
|
|
1685
|
-
#### Step 6: Test Mapping on One Record
|
|
1686
|
-
|
|
1687
|
-
```typescript
|
|
1688
|
-
import { UniversalMapper } from '@fluentcommerce/fc-connect-sdk';
|
|
1689
|
-
|
|
1690
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
1691
|
-
const result = await mapper.map(firstRecord);
|
|
1692
|
-
|
|
1693
|
-
if (result.success) {
|
|
1694
|
-
console.log('Mapping succeeded:', result.data);
|
|
1695
|
-
} else {
|
|
1696
|
-
console.error('Mapping failed:', result.errors);
|
|
1697
|
-
|
|
1698
|
-
// Check each error
|
|
1699
|
-
result.errors.forEach(error => {
|
|
1700
|
-
console.error('Field:', error.field);
|
|
1701
|
-
console.error('Message:', error.message);
|
|
1702
|
-
console.error('Path:', error.path);
|
|
1703
|
-
});
|
|
1704
|
-
}
|
|
1705
|
-
```
|
|
1706
|
-
|
|
1707
|
-
### Common Debugging Mistakes
|
|
1708
|
-
|
|
1709
|
-
#### Mistake 1: Using File Structure Instead of Object Structure
|
|
1710
|
-
|
|
1711
|
-
**WRONG:**
|
|
1712
|
-
```typescript
|
|
1713
|
-
// CSV file has column "sku_id"
|
|
1714
|
-
const mappingConfig = {
|
|
1715
|
-
fields: {
|
|
1716
|
-
skuRef: { source: "columns.sku_id" } // WRONG!
|
|
1717
|
-
}
|
|
1718
|
-
};
|
|
1719
|
-
```
|
|
1720
|
-
|
|
1721
|
-
**CORRECT:**
|
|
1722
|
-
```typescript
|
|
1723
|
-
// Parsed CSV gives you { sku_id: "SKU-001" }
|
|
1724
|
-
const mappingConfig = {
|
|
1725
|
-
fields: {
|
|
1726
|
-
skuRef: { source: "sku_id" } // CORRECT
|
|
1727
|
-
}
|
|
1728
|
-
};
|
|
1729
|
-
```
|
|
1730
|
-
|
|
1731
|
-
#### Mistake 2: Using XPath for XML
|
|
1732
|
-
|
|
1733
|
-
**WRONG:**
|
|
1734
|
-
```typescript
|
|
1735
|
-
// XML has <product><sku>SKU-001</sku></product>
|
|
1736
|
-
const mappingConfig = {
|
|
1737
|
-
fields: {
|
|
1738
|
-
skuRef: { source: "//product/sku" } // WRONG! Not XPath
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
```
|
|
1742
|
-
|
|
1743
|
-
**CORRECT:**
|
|
1744
|
-
```typescript
|
|
1745
|
-
// Parsed XML gives you { product: { sku: "SKU-001" } }
|
|
1746
|
-
const mappingConfig = {
|
|
1747
|
-
fields: {
|
|
1748
|
-
skuRef: { source: "product.sku" } // CORRECT: JavaScript path
|
|
1749
|
-
}
|
|
1750
|
-
};
|
|
1751
|
-
```
|
|
1752
|
-
|
|
1753
|
-
#### Mistake 3: Forgetting to Normalize XML Arrays
|
|
1754
|
-
|
|
1755
|
-
**WRONG:**
|
|
1756
|
-
```typescript
|
|
1757
|
-
const items = parsed.products.product;
|
|
1758
|
-
|
|
1759
|
-
// If there's only one <product>, this breaks!
|
|
1760
|
-
for (const item of items) { // ERROR: items is not iterable
|
|
1761
|
-
await mapper.map(item);
|
|
1762
|
-
}
|
|
1763
|
-
```
|
|
1764
|
-
|
|
1765
|
-
**CORRECT:**
|
|
1766
|
-
```typescript
|
|
1767
|
-
const items = parsed.products?.product;
|
|
1768
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1769
|
-
|
|
1770
|
-
// Now always works
|
|
1771
|
-
for (const record of records) {
|
|
1772
|
-
await mapper.map(record);
|
|
1773
|
-
}
|
|
1774
|
-
```
|
|
1775
|
-
|
|
1776
|
-
#### Mistake 4: Missing @ Prefix for XML Attributes
|
|
1777
|
-
|
|
1778
|
-
**WRONG:**
|
|
1779
|
-
```typescript
|
|
1780
|
-
// XML: <item locationRef="LOC-A">
|
|
1781
|
-
const mappingConfig = {
|
|
1782
|
-
fields: {
|
|
1783
|
-
locationRef: { source: "locationRef" } // WRONG: Missing @
|
|
1784
|
-
}
|
|
1785
|
-
};
|
|
1786
|
-
```
|
|
1787
|
-
|
|
1788
|
-
**CORRECT:**
|
|
1789
|
-
```typescript
|
|
1790
|
-
// Parsed: { "@locationRef": "LOC-A" }
|
|
1791
|
-
const mappingConfig = {
|
|
1792
|
-
fields: {
|
|
1793
|
-
locationRef: { source: "@locationRef" } // CORRECT
|
|
1794
|
-
}
|
|
1795
|
-
};
|
|
1796
|
-
```
|
|
1797
|
-
|
|
1798
|
-
### Debugging Checklist
|
|
1799
|
-
|
|
1800
|
-
When your mapping fails, check:
|
|
1801
|
-
|
|
1802
|
-
- [ ] Did you log the parsed structure with `JSON.stringify(parsed, null, 2)`?
|
|
1803
|
-
- [ ] Did you check what gets passed to `mapper.map()` at line X?
|
|
1804
|
-
- [ ] Are you using JavaScript object paths, not file format paths?
|
|
1805
|
-
- [ ] Did you normalize XML arrays? (`Array.isArray() ? : []:[]`)
|
|
1806
|
-
- [ ] Are you using `@` prefix for XML attributes?
|
|
1807
|
-
- [ ] Did you check for typos in property names?
|
|
1808
|
-
- [ ] Did you verify the property actually exists in the object?
|
|
1809
|
-
- [ ] Are you mapping nested properties with dot notation correctly?
|
|
1810
|
-
|
|
1811
|
-
---
|
|
1812
|
-
|
|
1813
|
-
## Real-World Example: InventoryStatus
|
|
1814
|
-
|
|
1815
|
-
This is a real user case that demonstrates the XML parent data enrichment pattern.
|
|
1816
|
-
|
|
1817
|
-
### The Problem
|
|
1818
|
-
|
|
1819
|
-
User has XML with a parent-level `FacilityId` that needs to be added to each child `Item`:
|
|
1820
|
-
|
|
1821
|
-
```xml
|
|
1822
|
-
<InventoryStatus>
|
|
1823
|
-
<ItemInventory>
|
|
1824
|
-
<FacilityId>WAREHOUSE-01</FacilityId>
|
|
1825
|
-
<Item>
|
|
1826
|
-
<SKU>SKU-001</SKU>
|
|
1827
|
-
<Quantity>100</Quantity>
|
|
1828
|
-
</Item>
|
|
1829
|
-
<Item>
|
|
1830
|
-
<SKU>SKU-002</SKU>
|
|
1831
|
-
<Quantity>50</Quantity>
|
|
1832
|
-
</Item>
|
|
1833
|
-
</ItemInventory>
|
|
1834
|
-
</InventoryStatus>
|
|
1835
|
-
```
|
|
1836
|
-
|
|
1837
|
-
**Challenge:** Each `Item` needs the `FacilityId` for the Fluent `locationRef` field, but it's at the parent `ItemInventory` level.
|
|
1838
|
-
|
|
1839
|
-
### The Solution
|
|
1840
|
-
|
|
1841
|
-
**Step 1: Parse XML**
|
|
1842
|
-
```typescript
|
|
1843
|
-
import { XMLParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
1844
|
-
|
|
1845
|
-
const xmlParser = new XMLParserService();
|
|
1846
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1847
|
-
|
|
1848
|
-
// Result:
|
|
1849
|
-
// {
|
|
1850
|
-
// InventoryStatus: {
|
|
1851
|
-
// ItemInventory: {
|
|
1852
|
-
// FacilityId: "WAREHOUSE-01",
|
|
1853
|
-
// Item: [
|
|
1854
|
-
// { SKU: "SKU-001", Quantity: 100 },
|
|
1855
|
-
// { SKU: "SKU-002", Quantity: 50 }
|
|
1856
|
-
// ]
|
|
1857
|
-
// }
|
|
1858
|
-
// }
|
|
1859
|
-
// }
|
|
1860
|
-
```
|
|
1861
|
-
|
|
1862
|
-
**Step 2: Extract Parent and Child Data**
|
|
1863
|
-
```typescript
|
|
1864
|
-
const itemInventory = parsed.InventoryStatus.ItemInventory;
|
|
1865
|
-
const facilityId = itemInventory.FacilityId;
|
|
1866
|
-
|
|
1867
|
-
// Normalize array (handle single vs multiple Items)
|
|
1868
|
-
const items = itemInventory.Item;
|
|
1869
|
-
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1870
|
-
```
|
|
1871
|
-
|
|
1872
|
-
**Step 3: Transform - Wrap Each Item with Parent Context**
|
|
1873
|
-
```typescript
|
|
1874
|
-
const enrichedRecords = normalizedItems.map(item => ({
|
|
1875
|
-
itemInventory: {
|
|
1876
|
-
FacilityId: facilityId, // Parent data
|
|
1877
|
-
...item // Child data
|
|
1878
|
-
}
|
|
1879
|
-
}));
|
|
1880
|
-
|
|
1881
|
-
// Result:
|
|
1882
|
-
// [
|
|
1883
|
-
// {
|
|
1884
|
-
// itemInventory: {
|
|
1885
|
-
// FacilityId: "WAREHOUSE-01",
|
|
1886
|
-
// SKU: "SKU-001",
|
|
1887
|
-
// Quantity: 100
|
|
1888
|
-
// }
|
|
1889
|
-
// },
|
|
1890
|
-
// ...
|
|
1891
|
-
// ]
|
|
1892
|
-
```
|
|
1893
|
-
|
|
1894
|
-
**Step 4: Mapping Configuration**
|
|
1895
|
-
```json
|
|
1896
|
-
{
|
|
1897
|
-
"fields": {
|
|
1898
|
-
"locationRef": {
|
|
1899
|
-
"source": "itemInventory.FacilityId",
|
|
1900
|
-
"required": true,
|
|
1901
|
-
"resolver": "sdk.trim"
|
|
1902
|
-
},
|
|
1903
|
-
"skuRef": {
|
|
1904
|
-
"source": "itemInventory.SKU",
|
|
1905
|
-
"required": true,
|
|
1906
|
-
"resolver": "sdk.trim"
|
|
1907
|
-
},
|
|
1908
|
-
"qty": {
|
|
1909
|
-
"source": "itemInventory.Quantity",
|
|
1910
|
-
"required": true,
|
|
1911
|
-
"resolver": "sdk.parseInt"
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
```
|
|
1916
|
-
|
|
1917
|
-
**Step 5: Map Each Record**
|
|
1918
|
-
```typescript
|
|
1919
|
-
import { UniversalMapper } from '@fluentcommerce/fc-connect-sdk';
|
|
1920
|
-
|
|
1921
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
1922
|
-
|
|
1923
|
-
for (const record of enrichedRecords) {
|
|
1924
|
-
const result = await mapper.map(record);
|
|
1925
|
-
|
|
1926
|
-
if (result.success) {
|
|
1927
|
-
console.log('Mapped record:', result.data);
|
|
1928
|
-
// {
|
|
1929
|
-
// locationRef: "WAREHOUSE-01",
|
|
1930
|
-
// skuRef: "SKU-001",
|
|
1931
|
-
// qty: 100
|
|
1932
|
-
// }
|
|
1933
|
-
} else {
|
|
1934
|
-
console.error('Mapping failed:', result.errors);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
```
|
|
1938
|
-
|
|
1939
|
-
### Why This Works
|
|
1940
|
-
|
|
1941
|
-
1. **Parsing creates JavaScript object** - XML → `{ InventoryStatus: { ItemInventory: { ... } } }`
|
|
1942
|
-
2. **Transformation wraps each item** - Added `FacilityId` to each item's structure
|
|
1943
|
-
3. **Mapping uses transformed structure** - Paths like `itemInventory.FacilityId` exist in the JavaScript object
|
|
1944
|
-
4. **Not XPath, not XML structure** - Pure JavaScript object property access
|
|
1945
|
-
|
|
1946
|
-
### Complete Working Example
|
|
1947
|
-
|
|
1948
|
-
```typescript
|
|
1949
|
-
import {
|
|
1950
|
-
XMLParserService,
|
|
1951
|
-
UniversalMapper,
|
|
1952
|
-
createClient
|
|
1953
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
1954
|
-
|
|
1955
|
-
async function processInventoryStatus(xmlContent: string) {
|
|
1956
|
-
// Parse XML
|
|
1957
|
-
const xmlParser = new XMLParserService();
|
|
1958
|
-
const parsed = await xmlParser.parse(xmlContent);
|
|
1959
|
-
|
|
1960
|
-
// Extract parent data
|
|
1961
|
-
const itemInventory = parsed.InventoryStatus.ItemInventory;
|
|
1962
|
-
const facilityId = itemInventory.FacilityId;
|
|
1963
|
-
|
|
1964
|
-
// Extract and normalize items
|
|
1965
|
-
const items = itemInventory.Item;
|
|
1966
|
-
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1967
|
-
|
|
1968
|
-
// Transform: wrap each item with parent context
|
|
1969
|
-
const enrichedRecords = normalizedItems.map(item => ({
|
|
1970
|
-
itemInventory: {
|
|
1971
|
-
FacilityId: facilityId,
|
|
1972
|
-
...item
|
|
1973
|
-
}
|
|
1974
|
-
}));
|
|
1975
|
-
|
|
1976
|
-
// Mapping configuration
|
|
1977
|
-
const mappingConfig = {
|
|
1978
|
-
fields: {
|
|
1979
|
-
locationRef: {
|
|
1980
|
-
source: "itemInventory.FacilityId",
|
|
1981
|
-
required: true,
|
|
1982
|
-
resolver: "sdk.trim"
|
|
1983
|
-
},
|
|
1984
|
-
skuRef: {
|
|
1985
|
-
source: "itemInventory.SKU",
|
|
1986
|
-
required: true,
|
|
1987
|
-
resolver: "sdk.trim"
|
|
1988
|
-
},
|
|
1989
|
-
qty: {
|
|
1990
|
-
source: "itemInventory.Quantity",
|
|
1991
|
-
required: true,
|
|
1992
|
-
resolver: "sdk.parseInt"
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
};
|
|
1996
|
-
|
|
1997
|
-
// Map each record
|
|
1998
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
1999
|
-
const mappedRecords = [];
|
|
2000
|
-
|
|
2001
|
-
for (const record of enrichedRecords) {
|
|
2002
|
-
const result = await mapper.map(record);
|
|
2003
|
-
|
|
2004
|
-
if (result.success) {
|
|
2005
|
-
mappedRecords.push(result.data);
|
|
2006
|
-
} else {
|
|
2007
|
-
console.error('Mapping failed for record:', record);
|
|
2008
|
-
console.error('Errors:', result.errors);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// Send to Fluent Commerce
|
|
2013
|
-
const client = await createClient({ config });
|
|
2014
|
-
|
|
2015
|
-
const job = await client.createJob({
|
|
2016
|
-
name: 'inventory-status-update',
|
|
2017
|
-
retailerId: process.env.FLUENT_RETAILER_ID!
|
|
2018
|
-
});
|
|
2019
|
-
|
|
2020
|
-
await client.sendBatch(job.id, {
|
|
2021
|
-
action: 'UPSERT',
|
|
2022
|
-
entityType: 'INVENTORY',
|
|
2023
|
-
entities: mappedRecords
|
|
2024
|
-
});
|
|
2025
|
-
|
|
2026
|
-
console.log(`Processed ${mappedRecords.length} inventory records`);
|
|
2027
|
-
}
|
|
2028
|
-
```
|
|
2029
|
-
|
|
2030
|
-
---
|
|
2031
|
-
|
|
2032
|
-
## Best Practices
|
|
2033
|
-
|
|
2034
|
-
### 1. Always Log Parsed Structure First
|
|
2035
|
-
|
|
2036
|
-
```typescript
|
|
2037
|
-
const parsed = await parser.parse(content);
|
|
2038
|
-
console.log('Parsed structure:', JSON.stringify(parsed, null, 2));
|
|
2039
|
-
```
|
|
2040
|
-
|
|
2041
|
-
This eliminates guesswork about object structure.
|
|
2042
|
-
|
|
2043
|
-
### 2. Normalize XML Arrays Immediately
|
|
2044
|
-
|
|
2045
|
-
```typescript
|
|
2046
|
-
// Always do this for XML
|
|
2047
|
-
const items = parsed.inventory?.item;
|
|
2048
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
2049
|
-
```
|
|
2050
|
-
|
|
2051
|
-
Prevents runtime errors when XML has only one element.
|
|
2052
|
-
|
|
2053
|
-
### 3. Use External JSON for Mapping Configs
|
|
2054
|
-
|
|
2055
|
-
```typescript
|
|
2056
|
-
// GOOD: External JSON file
|
|
2057
|
-
import mappingConfig from './config/inventory.json' with { type: 'json' };
|
|
2058
|
-
const mapper = new UniversalMapper(mappingConfig);
|
|
2059
|
-
```
|
|
2060
|
-
|
|
2061
|
-
Keeps code clean and configs maintainable.
|
|
2062
|
-
|
|
2063
|
-
### 4. Transform Before Mapping
|
|
2064
|
-
|
|
2065
|
-
```typescript
|
|
2066
|
-
// Extract parent data
|
|
2067
|
-
const facilityId = parsed.warehouse.id;
|
|
2068
|
-
|
|
2069
|
-
// Transform records to include parent data
|
|
2070
|
-
const enrichedRecords = items.map(item => ({
|
|
2071
|
-
...item,
|
|
2072
|
-
facilityId: facilityId
|
|
2073
|
-
}));
|
|
2074
|
-
|
|
2075
|
-
// Now mapping can reference facilityId
|
|
2076
|
-
```
|
|
2077
|
-
|
|
2078
|
-
Don't try to access parent data during mapping - transform first.
|
|
2079
|
-
|
|
2080
|
-
### 5. Test Mapping on One Record First
|
|
2081
|
-
|
|
2082
|
-
```typescript
|
|
2083
|
-
// Test with first record
|
|
2084
|
-
const testRecord = records[0];
|
|
2085
|
-
const result = await mapper.map(testRecord);
|
|
2086
|
-
|
|
2087
|
-
if (!result.success) {
|
|
2088
|
-
console.error('Mapping errors:', result.errors);
|
|
2089
|
-
// Fix mapping config before processing all records
|
|
2090
|
-
}
|
|
2091
|
-
```
|
|
2092
|
-
|
|
2093
|
-
Catch mapping errors early.
|
|
2094
|
-
|
|
2095
|
-
### 6. Use Descriptive Property Names in Transformations
|
|
2096
|
-
|
|
2097
|
-
```typescript
|
|
2098
|
-
// GOOD: Clear structure
|
|
2099
|
-
const enrichedRecords = items.map(item => ({
|
|
2100
|
-
itemInventory: {
|
|
2101
|
-
FacilityId: facilityId,
|
|
2102
|
-
...item
|
|
2103
|
-
}
|
|
2104
|
-
}));
|
|
2105
|
-
|
|
2106
|
-
// BAD: Unclear structure
|
|
2107
|
-
const enrichedRecords = items.map(item => ({
|
|
2108
|
-
...item,
|
|
2109
|
-
f: facilityId // What is 'f'?
|
|
2110
|
-
}));
|
|
2111
|
-
```
|
|
2112
|
-
|
|
2113
|
-
Makes mapping configs easier to understand.
|
|
2114
|
-
|
|
2115
|
-
### 7. Document Your Transformation Logic
|
|
2116
|
-
|
|
2117
|
-
```typescript
|
|
2118
|
-
// Transform XML structure to include parent FacilityId in each item
|
|
2119
|
-
// Original: { ItemInventory: { FacilityId, Item: [...] } }
|
|
2120
|
-
// Result: [{ itemInventory: { FacilityId, SKU, Qty } }]
|
|
2121
|
-
const enrichedRecords = normalizedItems.map(item => ({
|
|
2122
|
-
itemInventory: {
|
|
2123
|
-
FacilityId: facilityId,
|
|
2124
|
-
...item
|
|
2125
|
-
}
|
|
2126
|
-
}));
|
|
2127
|
-
```
|
|
2128
|
-
|
|
2129
|
-
Future you (or your team) will thank you.
|
|
2130
|
-
|
|
2131
|
-
### 8. Parse First, Then Decide on Transformation
|
|
2132
|
-
|
|
2133
|
-
1. **Parse the file format**
|
|
2134
|
-
- Use the appropriate parser service
|
|
2135
|
-
- Log the parsed structure
|
|
2136
|
-
|
|
2137
|
-
2. **Inspect the parsed structure**
|
|
2138
|
-
- Understand the data hierarchy
|
|
2139
|
-
- Identify shared parent data
|
|
2140
|
-
- Check for array normalization needs
|
|
2141
|
-
|
|
2142
|
-
3. **Decide if transformation is needed**
|
|
2143
|
-
- Can you map directly?
|
|
2144
|
-
- Do you need to enrich child records with parent data?
|
|
2145
|
-
- Is the structure compatible with your mapping config?
|
|
2146
|
-
|
|
2147
|
-
### 9. Keep Transformation Minimal
|
|
2148
|
-
|
|
2149
|
-
- Only transform when necessary
|
|
2150
|
-
- Use resolvers for simple transformations when possible
|
|
2151
|
-
- Prefer code transformation for complex restructuring
|
|
2152
|
-
|
|
2153
|
-
### 10. Handle Edge Cases
|
|
2154
|
-
|
|
2155
|
-
- Single vs multiple elements (XML)
|
|
2156
|
-
- Missing fields
|
|
2157
|
-
- Null/undefined values
|
|
2158
|
-
- Empty arrays
|
|
2159
|
-
|
|
2160
|
-
---
|
|
2161
|
-
|
|
2162
|
-
## Common Mistakes to Avoid
|
|
2163
|
-
|
|
2164
|
-
### Mistake 1: Using File Format Syntax in Mapping Config
|
|
2165
|
-
|
|
2166
|
-
**WRONG:**
|
|
2167
|
-
```typescript
|
|
2168
|
-
// Trying to use XPath in mapping config
|
|
2169
|
-
const mappingConfig = {
|
|
2170
|
-
fields: {
|
|
2171
|
-
skuRef: { source: "//product/sku" } // This is XPath, not JavaScript
|
|
2172
|
-
}
|
|
2173
|
-
};
|
|
2174
|
-
```
|
|
2175
|
-
|
|
2176
|
-
**CORRECT:**
|
|
2177
|
-
```typescript
|
|
2178
|
-
// Use JavaScript object path
|
|
2179
|
-
const mappingConfig = {
|
|
2180
|
-
fields: {
|
|
2181
|
-
skuRef: { source: "product.sku" } // JavaScript property access
|
|
2182
|
-
}
|
|
2183
|
-
};
|
|
2184
|
-
```
|
|
2185
|
-
|
|
2186
|
-
### Mistake 2: Mapping Before Transforming
|
|
2187
|
-
|
|
2188
|
-
**WRONG:**
|
|
2189
|
-
```typescript
|
|
2190
|
-
// Trying to access parent data in mapping config
|
|
2191
|
-
const mappingConfig = {
|
|
2192
|
-
fields: {
|
|
2193
|
-
// This won't work - FacilityId is at parent level
|
|
2194
|
-
locationRef: { source: "FacilityId" } // Item doesn't have FacilityId
|
|
2195
|
-
}
|
|
2196
|
-
};
|
|
2197
|
-
|
|
2198
|
-
for (const item of items) {
|
|
2199
|
-
await mapper.map(item); // Fails - item doesn't have FacilityId
|
|
2200
|
-
}
|
|
2201
|
-
```
|
|
2202
|
-
|
|
2203
|
-
**CORRECT:**
|
|
2204
|
-
```typescript
|
|
2205
|
-
// Transform FIRST to add parent data
|
|
2206
|
-
const enrichedRecords = items.map(item => ({
|
|
2207
|
-
...item,
|
|
2208
|
-
FacilityId: facilityId // Add parent data
|
|
2209
|
-
}));
|
|
2210
|
-
|
|
2211
|
-
// NOW mapping can access FacilityId
|
|
2212
|
-
const mappingConfig = {
|
|
2213
|
-
fields: {
|
|
2214
|
-
locationRef: { source: "FacilityId" } // Now it exists!
|
|
2215
|
-
}
|
|
2216
|
-
};
|
|
2217
|
-
```
|
|
2218
|
-
|
|
2219
|
-
### Mistake 3: Not Normalizing XML Arrays
|
|
2220
|
-
|
|
2221
|
-
**WRONG:**
|
|
2222
|
-
```typescript
|
|
2223
|
-
const items = parsed.products.product;
|
|
2224
|
-
|
|
2225
|
-
for (const item of items) { // Breaks if only one <product>!
|
|
2226
|
-
await mapper.map(item);
|
|
2227
|
-
}
|
|
2228
|
-
```
|
|
2229
|
-
|
|
2230
|
-
**CORRECT:**
|
|
2231
|
-
```typescript
|
|
2232
|
-
const items = parsed.products?.product;
|
|
2233
|
-
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
2234
|
-
|
|
2235
|
-
for (const record of records) { // Always works
|
|
2236
|
-
await mapper.map(record);
|
|
2237
|
-
}
|
|
2238
|
-
```
|
|
2239
|
-
|
|
2240
|
-
### Mistake 4: Forgetting @ Prefix for XML Attributes
|
|
2241
|
-
|
|
2242
|
-
**WRONG:**
|
|
2243
|
-
```typescript
|
|
2244
|
-
// XML: <item locationRef="LOC-A" skuRef="SKU-001">
|
|
2245
|
-
const mappingConfig = {
|
|
2246
|
-
fields: {
|
|
2247
|
-
locationRef: { source: "locationRef" }, // Missing @
|
|
2248
|
-
skuRef: { source: "skuRef" } // Missing @
|
|
2249
|
-
}
|
|
2250
|
-
};
|
|
2251
|
-
```
|
|
2252
|
-
|
|
2253
|
-
**CORRECT:**
|
|
2254
|
-
```typescript
|
|
2255
|
-
// Parsed object has: { "@locationRef": "LOC-A", "@skuRef": "SKU-001" }
|
|
2256
|
-
const mappingConfig = {
|
|
2257
|
-
fields: {
|
|
2258
|
-
locationRef: { source: "@locationRef" }, // @ prefix required
|
|
2259
|
-
skuRef: { source: "@skuRef" }
|
|
2260
|
-
}
|
|
2261
|
-
};
|
|
2262
|
-
```
|
|
2263
|
-
|
|
2264
|
-
### Mistake 5: Mapping Nested Source to Flat Target Without Extraction
|
|
2265
|
-
|
|
2266
|
-
**WRONG:**
|
|
2267
|
-
```typescript
|
|
2268
|
-
// Source: { warehouse: { inventory: { items: [...] } } }
|
|
2269
|
-
const items = parsed.warehouse.inventory.items;
|
|
2270
|
-
|
|
2271
|
-
// Mapping without extraction
|
|
2272
|
-
const mappingConfig = {
|
|
2273
|
-
fields: {
|
|
2274
|
-
skuRef: { source: "warehouse.inventory.items.sku" } // Path too long
|
|
2275
|
-
}
|
|
2276
|
-
};
|
|
2277
|
-
```
|
|
2278
|
-
|
|
2279
|
-
**CORRECT:**
|
|
2280
|
-
```typescript
|
|
2281
|
-
// Extract the array first
|
|
2282
|
-
const items = parsed.warehouse.inventory.items;
|
|
2283
|
-
|
|
2284
|
-
// Now map each item directly
|
|
2285
|
-
const mappingConfig = {
|
|
2286
|
-
fields: {
|
|
2287
|
-
skuRef: { source: "sku" } // Clean path
|
|
2288
|
-
}
|
|
2289
|
-
};
|
|
2290
|
-
|
|
2291
|
-
for (const item of items) {
|
|
2292
|
-
await mapper.map(item);
|
|
2293
|
-
}
|
|
2294
|
-
```
|
|
2295
|
-
|
|
2296
|
-
### Mistake 6: Assuming Parsed Data Matches File Structure
|
|
2297
|
-
|
|
2298
|
-
**WRONG:**
|
|
2299
|
-
```typescript
|
|
2300
|
-
// CSV has columns: sku_id, quantity, location
|
|
2301
|
-
// Assuming mapping uses column positions
|
|
2302
|
-
const mappingConfig = {
|
|
2303
|
-
fields: {
|
|
2304
|
-
skuRef: { source: "columns[0]" } // WRONG! Not how it works
|
|
2305
|
-
}
|
|
2306
|
-
};
|
|
2307
|
-
```
|
|
2308
|
-
|
|
2309
|
-
**CORRECT:**
|
|
2310
|
-
```typescript
|
|
2311
|
-
// CSV parser creates: { sku_id: "...", quantity: "...", location: "..." }
|
|
2312
|
-
const mappingConfig = {
|
|
2313
|
-
fields: {
|
|
2314
|
-
skuRef: { source: "sku_id" } // Use property name
|
|
2315
|
-
}
|
|
2316
|
-
};
|
|
2317
|
-
```
|
|
2318
|
-
|
|
2319
|
-
---
|
|
2320
|
-
|
|
2321
|
-
## Related Documentation
|
|
2322
|
-
|
|
2323
|
-
- [Mapping Foundations](../auto-pagination/modules/auto-pagination-01-foundations.md) - Core mapping concepts
|
|
2324
|
-
- [Universal Mapper Guide](mapping-readme.md) - Complete mapping patterns
|
|
2325
|
-
- [SDK Resolvers](./resolvers/mapping-resolvers-readme.md) - Built-in field transformations
|
|
2326
|
-
- [XML Parser Guide](../parsers/modules/02-core-guides-parsers-04-xml-parser.md) - XML parsing details
|
|
2327
|
-
- [CSV Parser Guide](../parsers/modules/02-core-guides-parsers-02-csv-parser.md) - CSV parsing details
|
|
2328
|
-
- [JSON Parser Guide](../parsers/modules/02-core-guides-parsers-03-json-parser.md) - JSON parsing details
|
|
2329
|
-
|
|
2330
|
-
---
|
|
2331
|
-
|
|
2332
|
-
## FAQ
|
|
2333
|
-
|
|
2334
|
-
**Q: Do I need to know XPath/JSONPath/SQL to write mapping configs?**
|
|
2335
|
-
|
|
2336
|
-
A: No! You only need to know JavaScript object property access. Use dot notation for nested properties and `@` prefix for XML attributes.
|
|
2337
|
-
|
|
2338
|
-
**Q: Why does my XML mapping fail when there's only one element?**
|
|
2339
|
-
|
|
2340
|
-
A: XML parsers return an object for a single element and an array for multiple elements. Always normalize with: `Array.isArray(items) ? items : items ? [items] : []`
|
|
2341
|
-
|
|
2342
|
-
**Q: Can I use the same mapping config for CSV, JSON, and XML?**
|
|
2343
|
-
|
|
2344
|
-
A: Only if they produce the same JavaScript object structure after parsing. Usually, you need different configs because each format has different property names.
|
|
2345
|
-
|
|
2346
|
-
**Q: How do I map parent data to child records?**
|
|
2347
|
-
|
|
2348
|
-
A: Transform the data BEFORE mapping. Extract parent data, then wrap each child record with the parent data. See the [InventoryStatus example](#real-world-example-inventorystatus).
|
|
2349
|
-
|
|
2350
|
-
**Q: What's the difference between `"source": "sku"` and `"source": "@sku"`?**
|
|
2351
|
-
|
|
2352
|
-
A: `"sku"` is a child element in XML (or object property). `"@sku"` is an XML attribute. The XML parser adds the `@` prefix to attributes in the JavaScript object.
|
|
2353
|
-
|
|
2354
|
-
**Q: Can I access nested properties with bracket notation?**
|
|
2355
|
-
|
|
2356
|
-
A: Use dot notation for paths: `"source": "product.details.sku"`. The mapper handles the property access internally.
|
|
2357
|
-
|
|
2358
|
-
**Q: How do I debug mapping errors?**
|
|
2359
|
-
|
|
2360
|
-
A: Log the exact object you pass to `mapper.map()` with `JSON.stringify(object, null, 2)`. Compare property names in the log to your mapping config `source` paths.
|
|
2361
|
-
|
|
2362
|
-
---
|
|
2363
|
-
|
|
2364
|
-
**Last Updated:** 2025-11-03
|
|
2365
|
-
|
|
2366
|
-
**Related Guides:**
|
|
2367
|
-
- [Universal Mapping Guide](mapping-readme.md)
|
|
2368
|
-
- [SDK Resolvers](./resolvers/mapping-resolvers-readme.md)
|
|
2369
|
-
- [Parsers Guide](../parsers/parsers-readme.md)
|
|
1
|
+
---
|
|
2
|
+
title: Understanding Mapping - JavaScript Object Paths
|
|
3
|
+
description: Comprehensive guide to how mapping configurations work with parsed data structures
|
|
4
|
+
category: mapping
|
|
5
|
+
priority: high
|
|
6
|
+
related:
|
|
7
|
+
- mapping/modules/01-foundations.md
|
|
8
|
+
- mapping/modules/06-helpers-resolvers.md
|
|
9
|
+
- parsers/modules/04-xml-parser.md
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Understanding Mapping: JavaScript Object Paths
|
|
13
|
+
|
|
14
|
+
**Critical Concept:** Mapping paths reference **JavaScript object structure**, not source file format.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
1. [Core Concept: Mapping AFTER Parsing](#core-concept-mapping-after-parsing)
|
|
21
|
+
2. [The ETL Pattern](#the-etl-pattern)
|
|
22
|
+
3. [Format Examples](#format-examples)
|
|
23
|
+
- [CSV Examples](#csv-examples)
|
|
24
|
+
- [JSON Examples](#json-examples)
|
|
25
|
+
- [XML Examples](#xml-examples)
|
|
26
|
+
- [Complex XML with Attributes](#complex-xml-with-attributes)
|
|
27
|
+
- [Complex JSON with Deep Nesting](#complex-json-with-deep-nesting)
|
|
28
|
+
- [XML with Shared Parent Data](#xml-with-shared-parent-data)
|
|
29
|
+
4. [Common Transformation Patterns](#common-transformation-patterns)
|
|
30
|
+
5. [Visual Flow Diagrams](#visual-flow-diagrams)
|
|
31
|
+
6. [Debugging Guide](#debugging-guide)
|
|
32
|
+
7. [Real-World Example: InventoryStatus](#real-world-example-inventorystatus)
|
|
33
|
+
8. [Best Practices](#best-practices)
|
|
34
|
+
9. [Common Mistakes to Avoid](#common-mistakes-to-avoid)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Core Concept: Mapping AFTER Parsing
|
|
39
|
+
|
|
40
|
+
### The Key Principle
|
|
41
|
+
|
|
42
|
+
**Mapping configurations map JAVASCRIPT OBJECTS, not file formats.**
|
|
43
|
+
|
|
44
|
+
When you write a mapping configuration like this:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"fields": {
|
|
49
|
+
"skuRef": { "source": "product.sku" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The path `"product.sku"` refers to **JavaScript object property access**, not:
|
|
55
|
+
- NOT CSV column names directly
|
|
56
|
+
- NOT XPath expressions
|
|
57
|
+
- NOT JSONPath queries
|
|
58
|
+
- NOT file format structure
|
|
59
|
+
|
|
60
|
+
### Why This Matters
|
|
61
|
+
|
|
62
|
+
Understanding this principle prevents confusion when working with different data sources:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// This is what the mapper receives - ALWAYS a JavaScript object
|
|
66
|
+
const jsObject = {
|
|
67
|
+
product: {
|
|
68
|
+
sku: "SKU-001",
|
|
69
|
+
name: "Widget"
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Your mapping config uses JavaScript paths
|
|
74
|
+
const mappingConfig = {
|
|
75
|
+
fields: {
|
|
76
|
+
skuRef: { source: "product.sku" } // JavaScript object path
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The mapper **NEVER** sees the original CSV, XML, or JSON file. It only sees the JavaScript object that results from parsing and transformation.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## The ETL Pattern
|
|
86
|
+
|
|
87
|
+
All SDK data flows follow this standard Extract-Transform-Load pattern:
|
|
88
|
+
|
|
89
|
+
```mermaid
|
|
90
|
+
flowchart TD
|
|
91
|
+
A[Source File<br/>CSV/JSON/XML] --> B[Step 1: Parse<br/>→ JavaScript Objects]
|
|
92
|
+
B --> C[Step 2: Extract/Normalize<br/>→ Array of Records]
|
|
93
|
+
C --> D[Step 3: Map Each Record<br/>→ Fluent Format]
|
|
94
|
+
D --> E[Step 4: Load<br/>→ Batch API/GraphQL]
|
|
95
|
+
|
|
96
|
+
style A fill:#e1f5ff
|
|
97
|
+
style B fill:#fff4e1
|
|
98
|
+
style C fill:#fff4e1
|
|
99
|
+
style D fill:#e8f5e9
|
|
100
|
+
style E fill:#f3e5f5
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Text Version:**
|
|
104
|
+
```
|
|
105
|
+
Source File (CSV/JSON/XML)
|
|
106
|
+
↓
|
|
107
|
+
[Step 1] Parse → JavaScript Objects
|
|
108
|
+
↓
|
|
109
|
+
[Step 2] Extract/Normalize → Array of Records
|
|
110
|
+
↓
|
|
111
|
+
[Step 3] Map Each Record → Fluent Format
|
|
112
|
+
↓
|
|
113
|
+
[Step 4] Load → Batch API/GraphQL
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Detailed Breakdown:**
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
120
|
+
│ ETL WORKFLOW PATTERN │
|
|
121
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
122
|
+
|
|
123
|
+
Step 1: SOURCE FILE
|
|
124
|
+
┌────────────────────┐
|
|
125
|
+
│ inventory.csv │ File format: CSV, JSON, XML, Parquet
|
|
126
|
+
│ inventory.json │ Location: S3, SFTP, Local
|
|
127
|
+
│ inventory.xml │
|
|
128
|
+
└────────────────────┘
|
|
129
|
+
│
|
|
130
|
+
│ Read from data source
|
|
131
|
+
↓
|
|
132
|
+
Step 2: PARSE → JavaScript Objects
|
|
133
|
+
┌────────────────────┐
|
|
134
|
+
│ CSVParserService │ Converts file format to JavaScript
|
|
135
|
+
│ XMLParserService │ Result: Array of objects or nested structure
|
|
136
|
+
│ JSONParserService │
|
|
137
|
+
│ ParquetParser │
|
|
138
|
+
└────────────────────┘
|
|
139
|
+
│
|
|
140
|
+
│ Parser output: JavaScript objects
|
|
141
|
+
↓
|
|
142
|
+
Step 3: EXTRACT/NORMALIZE → Array of Records
|
|
143
|
+
┌────────────────────┐
|
|
144
|
+
│ Extract items │ Navigate to the data you need
|
|
145
|
+
│ Normalize arrays │ Flatten nested structures
|
|
146
|
+
│ Enrich with │ Add shared context data
|
|
147
|
+
│ parent data │
|
|
148
|
+
└────────────────────┘
|
|
149
|
+
│
|
|
150
|
+
│ What gets passed to mapper
|
|
151
|
+
↓
|
|
152
|
+
Step 4: MAP EACH RECORD → Fluent Format
|
|
153
|
+
┌────────────────────┐
|
|
154
|
+
│ UniversalMapper │ Maps JavaScript object paths to Fluent fields
|
|
155
|
+
│ or │ Uses mapping configuration
|
|
156
|
+
│ GraphQLMutation │ Applies resolvers for transformations
|
|
157
|
+
│ Mapper │
|
|
158
|
+
└────────────────────┘
|
|
159
|
+
│
|
|
160
|
+
│ Fluent-formatted data
|
|
161
|
+
↓
|
|
162
|
+
Step 5: LOAD → Fluent Commerce
|
|
163
|
+
┌────────────────────┐
|
|
164
|
+
│ Batch API │ Send data to Fluent
|
|
165
|
+
│ Event API │ create/update entities
|
|
166
|
+
│ GraphQL Mutations │
|
|
167
|
+
└────────────────────┘
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Key Insight:** Mapping (Step 4) operates on JavaScript objects from Step 3, not on the original file from Step 1. **Mapping configuration matches Step 3** - the structure you pass to `mapper.map()`, which is the result of Step 2.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Format Examples
|
|
175
|
+
|
|
176
|
+
### CSV Examples
|
|
177
|
+
|
|
178
|
+
#### Simple CSV: No Transformation Needed
|
|
179
|
+
|
|
180
|
+
**Original CSV File:**
|
|
181
|
+
```csv
|
|
182
|
+
sku_id,quantity,location_code,status
|
|
183
|
+
SKU-001,100,LOC-A,AVAILABLE
|
|
184
|
+
SKU-002,50,LOC-B,RESERVED
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Step 1: Parse**
|
|
188
|
+
```typescript
|
|
189
|
+
const csvParser = new CSVParserService();
|
|
190
|
+
const records = await csvParser.parse(csvContent);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Step 2: Parsed JavaScript Object (CSVParserService output)**
|
|
194
|
+
```javascript
|
|
195
|
+
[
|
|
196
|
+
{
|
|
197
|
+
sku_id: "SKU-001",
|
|
198
|
+
quantity: "100", // Note: strings by default
|
|
199
|
+
location_code: "LOC-A",
|
|
200
|
+
status: "AVAILABLE"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
sku_id: "SKU-002",
|
|
204
|
+
quantity: "50",
|
|
205
|
+
location_code: "LOC-B",
|
|
206
|
+
status: "RESERVED"
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Step 3: Extraction (No transformation needed)**
|
|
212
|
+
```javascript
|
|
213
|
+
// CSV parser already gives us an array of flat objects
|
|
214
|
+
const records = parsedCsv; // Use directly
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Step 4: Mapping Configuration**
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"fields": {
|
|
221
|
+
"skuRef": {
|
|
222
|
+
"source": "sku_id",
|
|
223
|
+
"required": true
|
|
224
|
+
},
|
|
225
|
+
"qty": {
|
|
226
|
+
"source": "quantity",
|
|
227
|
+
"resolver": "sdk.parseInt"
|
|
228
|
+
},
|
|
229
|
+
"locationRef": {
|
|
230
|
+
"source": "location_code"
|
|
231
|
+
},
|
|
232
|
+
"status": {
|
|
233
|
+
"source": "status",
|
|
234
|
+
"resolver": "sdk.uppercase"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**What gets passed to mapper.map():**
|
|
241
|
+
```javascript
|
|
242
|
+
// For each record in the array:
|
|
243
|
+
await mapper.map({
|
|
244
|
+
sku_id: "SKU-001",
|
|
245
|
+
quantity: "100",
|
|
246
|
+
location_code: "LOC-A",
|
|
247
|
+
status: "AVAILABLE"
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Key Points:**
|
|
252
|
+
- CSV columns become JavaScript object keys
|
|
253
|
+
- Column names in file match `source` paths in mapping config
|
|
254
|
+
- No dot notation needed (flat structure)
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
#### Complex CSV: Requires Transformation
|
|
259
|
+
|
|
260
|
+
**CSV File:**
|
|
261
|
+
```csv
|
|
262
|
+
ref,price_default_currency,price_default_value,price_special_currency,price_special_value
|
|
263
|
+
PROD-001,USD,50.00,USD,35.00
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Step 1: Parse**
|
|
267
|
+
```typescript
|
|
268
|
+
const csvParser = new CSVParserService();
|
|
269
|
+
const records = await csvParser.parse(csvContent);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Parsed JavaScript Object:**
|
|
273
|
+
```javascript
|
|
274
|
+
[
|
|
275
|
+
{
|
|
276
|
+
ref: "PROD-001",
|
|
277
|
+
price_default_currency: "USD",
|
|
278
|
+
price_default_value: "50.00",
|
|
279
|
+
price_special_currency: "USD",
|
|
280
|
+
price_special_value: "35.00"
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Step 2: Extract/Normalize**
|
|
286
|
+
```typescript
|
|
287
|
+
// CSV is flat, but we need nested structure for prices
|
|
288
|
+
// Two options:
|
|
289
|
+
|
|
290
|
+
// Option A: Transform in code before mapping
|
|
291
|
+
const records = parsed.map(row => ({
|
|
292
|
+
ref: row.ref,
|
|
293
|
+
price: [
|
|
294
|
+
{ type: "DEFAULT", currency: row.price_default_currency, value: row.price_default_value },
|
|
295
|
+
{ type: "SPECIAL", currency: row.price_special_currency, value: row.price_special_value }
|
|
296
|
+
]
|
|
297
|
+
}));
|
|
298
|
+
// Result: [{ ref: "PROD-001", price: [{ type: "DEFAULT", ... }, { type: "SPECIAL", ... }] }]
|
|
299
|
+
|
|
300
|
+
// Option B: Use custom resolver in mapping (see Step 3)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Step 3: Map**
|
|
304
|
+
|
|
305
|
+
**Option A - After Code Transformation:**
|
|
306
|
+
```typescript
|
|
307
|
+
const mapper = new UniversalMapper({
|
|
308
|
+
fields: {
|
|
309
|
+
ref: { source: "ref" },
|
|
310
|
+
price: {
|
|
311
|
+
source: "price", // ← Reads from transformed record.price
|
|
312
|
+
isArray: true,
|
|
313
|
+
fields: {
|
|
314
|
+
type: { source: "type" },
|
|
315
|
+
currency: { source: "currency" },
|
|
316
|
+
value: { source: "value", resolver: "sdk.parseFloat" }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
for (const record of records) {
|
|
323
|
+
const result = await mapper.map(record); // ← Pass transformed structure
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Option B - Using Custom Resolver:**
|
|
328
|
+
```typescript
|
|
329
|
+
const mapper = new UniversalMapper({
|
|
330
|
+
fields: {
|
|
331
|
+
ref: { source: "ref" },
|
|
332
|
+
price: {
|
|
333
|
+
resolver: "custom.buildPriceArray", // ← Custom resolver builds nested structure
|
|
334
|
+
// No source - resolver receives full record
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}, {
|
|
338
|
+
customResolvers: {
|
|
339
|
+
'custom.buildPriceArray': (value, sourceData) => {
|
|
340
|
+
// sourceData = full CSV row
|
|
341
|
+
return [
|
|
342
|
+
{
|
|
343
|
+
type: "DEFAULT",
|
|
344
|
+
currency: sourceData.price_default_currency,
|
|
345
|
+
value: parseFloat(sourceData.price_default_value)
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
type: "SPECIAL",
|
|
349
|
+
currency: sourceData.price_special_currency,
|
|
350
|
+
value: parseFloat(sourceData.price_special_value)
|
|
351
|
+
}
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
for (const record of records) {
|
|
358
|
+
const result = await mapper.map(record); // ← Pass flat CSV row
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Mapping Matches:**
|
|
363
|
+
- Option A: Transformed structure (`record.price[]`)
|
|
364
|
+
- Option B: Flat CSV row (`record.price_default_currency` via resolver)
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
### JSON Examples
|
|
369
|
+
|
|
370
|
+
#### Simple JSON: Array Extraction Needed
|
|
371
|
+
|
|
372
|
+
**Original JSON File:**
|
|
373
|
+
```json
|
|
374
|
+
{
|
|
375
|
+
"timestamp": "2025-01-15T10:00:00Z",
|
|
376
|
+
"warehouse": "WH-01",
|
|
377
|
+
"inventory": [
|
|
378
|
+
{
|
|
379
|
+
"product_code": "SKU-001",
|
|
380
|
+
"stock_level": 100,
|
|
381
|
+
"location": "A1"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"product_code": "SKU-002",
|
|
385
|
+
"stock_level": 50,
|
|
386
|
+
"location": "B2"
|
|
387
|
+
}
|
|
388
|
+
]
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Step 1: Parse**
|
|
393
|
+
```typescript
|
|
394
|
+
const jsonParser = new JSONParserService();
|
|
395
|
+
const parsed = await jsonParser.parse(jsonContent);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Step 2: Parsed JavaScript Object (JSONParserService output)**
|
|
399
|
+
```javascript
|
|
400
|
+
{
|
|
401
|
+
timestamp: "2025-01-15T10:00:00Z",
|
|
402
|
+
warehouse: "WH-01",
|
|
403
|
+
inventory: [
|
|
404
|
+
{
|
|
405
|
+
product_code: "SKU-001",
|
|
406
|
+
stock_level: 100,
|
|
407
|
+
location: "A1"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
product_code: "SKU-002",
|
|
411
|
+
stock_level: 50,
|
|
412
|
+
location: "B2"
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Step 3: Extraction**
|
|
419
|
+
```javascript
|
|
420
|
+
// Extract the array we want to process
|
|
421
|
+
const parsed = await jsonParser.parse(jsonContent);
|
|
422
|
+
const records = parsed.inventory; // Extract nested array
|
|
423
|
+
|
|
424
|
+
// Now we have an array of inventory items
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Step 4: Mapping Configuration**
|
|
428
|
+
```json
|
|
429
|
+
{
|
|
430
|
+
"fields": {
|
|
431
|
+
"skuRef": {
|
|
432
|
+
"source": "product_code",
|
|
433
|
+
"required": true
|
|
434
|
+
},
|
|
435
|
+
"qty": {
|
|
436
|
+
"source": "stock_level",
|
|
437
|
+
"resolver": "sdk.parseInt"
|
|
438
|
+
},
|
|
439
|
+
"locationRef": {
|
|
440
|
+
"source": "location"
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**What gets passed to mapper.map():**
|
|
447
|
+
```javascript
|
|
448
|
+
// For each item in the inventory array:
|
|
449
|
+
await mapper.map({
|
|
450
|
+
product_code: "SKU-001",
|
|
451
|
+
stock_level: 100,
|
|
452
|
+
location: "A1"
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**Key Points:**
|
|
457
|
+
- JSON parsing preserves nested structure
|
|
458
|
+
- You extract the array you need (`parsed.inventory`)
|
|
459
|
+
- Mapping config references the **extracted object** structure, not the full JSON
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
#### Complex JSON: Nested Structure with Transformation Options
|
|
464
|
+
|
|
465
|
+
**JSON File:**
|
|
466
|
+
```json
|
|
467
|
+
{
|
|
468
|
+
"inventory": {
|
|
469
|
+
"warehouse": "WH-001",
|
|
470
|
+
"items": [
|
|
471
|
+
{
|
|
472
|
+
"product": {
|
|
473
|
+
"sku": "SKU-001",
|
|
474
|
+
"details": {
|
|
475
|
+
"name": "Product 1"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"stock": {
|
|
479
|
+
"quantity": 100,
|
|
480
|
+
"status": "available"
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
"product": {
|
|
485
|
+
"sku": "SKU-002",
|
|
486
|
+
"details": {
|
|
487
|
+
"name": "Product 2"
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
"stock": {
|
|
491
|
+
"quantity": 50,
|
|
492
|
+
"status": "reserved"
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Step 1: Parse**
|
|
501
|
+
```typescript
|
|
502
|
+
const jsonParser = new JSONParserService();
|
|
503
|
+
const parsed = await jsonParser.parse(jsonContent);
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Parsed JavaScript Object:**
|
|
507
|
+
```javascript
|
|
508
|
+
{
|
|
509
|
+
inventory: {
|
|
510
|
+
warehouse: "WH-001",
|
|
511
|
+
items: [
|
|
512
|
+
{
|
|
513
|
+
product: { sku: "SKU-001", details: { name: "Product 1" } },
|
|
514
|
+
stock: { quantity: 100, status: "available" }
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
product: { sku: "SKU-002", details: { name: "Product 2" } },
|
|
518
|
+
stock: { quantity: 50, status: "reserved" }
|
|
519
|
+
}
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Step 2: Extract/Normalize**
|
|
526
|
+
```typescript
|
|
527
|
+
// Extract items array, and add warehouse from parent
|
|
528
|
+
const warehouse = parsed.inventory.warehouse;
|
|
529
|
+
const items = parsed.inventory.items;
|
|
530
|
+
|
|
531
|
+
// Option A: Transform to add warehouse to each item
|
|
532
|
+
const records = items.map(item => ({
|
|
533
|
+
warehouse: warehouse, // ← Add shared data
|
|
534
|
+
product: item.product,
|
|
535
|
+
stock: item.stock
|
|
536
|
+
}));
|
|
537
|
+
// Result: [{ warehouse: "WH-001", product: {...}, stock: {...} }, ...]
|
|
538
|
+
|
|
539
|
+
// Option B: Pass full structure and use dot notation in mapping
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Step 3: Map**
|
|
543
|
+
|
|
544
|
+
**Option A - After Transformation:**
|
|
545
|
+
```typescript
|
|
546
|
+
const mapper = new UniversalMapper({
|
|
547
|
+
fields: {
|
|
548
|
+
locationRef: { source: "warehouse" }, // ← From transformed record
|
|
549
|
+
productRef: { source: "product.sku" }, // ← From transformed record
|
|
550
|
+
quantity: { source: "stock.quantity" }, // ← From transformed record
|
|
551
|
+
status: { source: "stock.status" } // ← From transformed record
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
for (const record of records) {
|
|
556
|
+
const result = await mapper.map(record); // ← Pass transformed structure
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Option B - Direct Mapping (No Transformation):**
|
|
561
|
+
```typescript
|
|
562
|
+
// Map each item directly, accessing parent via root context
|
|
563
|
+
const mapper = new UniversalMapper({
|
|
564
|
+
fields: {
|
|
565
|
+
items: {
|
|
566
|
+
source: "inventory.items", // ← Extract items array
|
|
567
|
+
isArray: true, // ← Iterate over items
|
|
568
|
+
fields: {
|
|
569
|
+
locationRef: {
|
|
570
|
+
// Access parent warehouse from root context
|
|
571
|
+
// Note: You may need a custom resolver to access parent data
|
|
572
|
+
resolver: "custom.getWarehouse" // Custom resolver accesses root
|
|
573
|
+
},
|
|
574
|
+
productRef: { source: "product.sku" }, // ← From current item
|
|
575
|
+
quantity: { source: "stock.quantity" }, // ← From current item
|
|
576
|
+
status: { source: "stock.status" } // ← From current item
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const result = await mapper.map(parsed); // ← Pass full parsed structure
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**Mapping Matches:**
|
|
586
|
+
- Option A: Transformed structure (`record.warehouse`, `record.product.sku`)
|
|
587
|
+
- Option B: Full parsed structure (`inventory.items[]`, requires custom resolver for parent access)
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
#### Simple XML: Array Normalization Required
|
|
592
|
+
|
|
593
|
+
**Original XML File:**
|
|
594
|
+
```xml
|
|
595
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
596
|
+
<products>
|
|
597
|
+
<product>
|
|
598
|
+
<sku>SKU-001</sku>
|
|
599
|
+
<name>Product 1</name>
|
|
600
|
+
<quantity>100</quantity>
|
|
601
|
+
<location>LOC-A</location>
|
|
602
|
+
</product>
|
|
603
|
+
<product>
|
|
604
|
+
<sku>SKU-002</sku>
|
|
605
|
+
<name>Product 2</name>
|
|
606
|
+
<quantity>50</quantity>
|
|
607
|
+
<location>LOC-B</location>
|
|
608
|
+
</product>
|
|
609
|
+
</products>
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**Step 1: Parse**
|
|
613
|
+
```typescript
|
|
614
|
+
const xmlParser = new XMLParserService();
|
|
615
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**Step 2: Parsed JavaScript Object (XMLParserService output)**
|
|
619
|
+
```javascript
|
|
620
|
+
{
|
|
621
|
+
products: {
|
|
622
|
+
product: [ // Multiple <product> elements → array
|
|
623
|
+
{
|
|
624
|
+
sku: "SKU-001",
|
|
625
|
+
name: "Product 1",
|
|
626
|
+
quantity: 100, // Auto-parsed as number
|
|
627
|
+
location: "LOC-A"
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
sku: "SKU-002",
|
|
631
|
+
name: "Product 2",
|
|
632
|
+
quantity: 50,
|
|
633
|
+
location: "LOC-B"
|
|
634
|
+
}
|
|
635
|
+
]
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**CRITICAL: XML Array Normalization**
|
|
641
|
+
|
|
642
|
+
XML parsing has a gotcha: single vs multiple elements
|
|
643
|
+
```javascript
|
|
644
|
+
// Multiple <product> elements
|
|
645
|
+
parsed.products.product // → array []
|
|
646
|
+
|
|
647
|
+
// Single <product> element (gotcha!)
|
|
648
|
+
parsed.products.product // → object {} (NOT array!)
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
**Step 3: Extraction with Normalization**
|
|
652
|
+
```javascript
|
|
653
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
654
|
+
|
|
655
|
+
// ALWAYS normalize XML arrays
|
|
656
|
+
const items = parsed.products?.product;
|
|
657
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
658
|
+
|
|
659
|
+
// Now records is ALWAYS an array
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Step 4: Mapping Configuration**
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"fields": {
|
|
666
|
+
"skuRef": {
|
|
667
|
+
"source": "sku",
|
|
668
|
+
"required": true
|
|
669
|
+
},
|
|
670
|
+
"name": {
|
|
671
|
+
"source": "name"
|
|
672
|
+
},
|
|
673
|
+
"qty": {
|
|
674
|
+
"source": "quantity",
|
|
675
|
+
"resolver": "sdk.parseInt"
|
|
676
|
+
},
|
|
677
|
+
"locationRef": {
|
|
678
|
+
"source": "location"
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**What gets passed to mapper.map():**
|
|
685
|
+
```javascript
|
|
686
|
+
// For each normalized item:
|
|
687
|
+
await mapper.map({
|
|
688
|
+
sku: "SKU-001",
|
|
689
|
+
name: "Product 1",
|
|
690
|
+
quantity: 100,
|
|
691
|
+
location: "LOC-A"
|
|
692
|
+
});
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Key Points:**
|
|
696
|
+
- XML element names become JavaScript object keys
|
|
697
|
+
- Multiple elements create arrays
|
|
698
|
+
- Single element creates object (must normalize!)
|
|
699
|
+
- Always use: `Array.isArray(items) ? items : items ? [items] : []`
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
### Complex XML with Attributes
|
|
704
|
+
|
|
705
|
+
#### XML Attributes Use @ Prefix
|
|
706
|
+
|
|
707
|
+
**Original XML File:**
|
|
708
|
+
```xml
|
|
709
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
710
|
+
<inventory>
|
|
711
|
+
<item locationRef="LOC-A" skuRef="SKU-001">
|
|
712
|
+
<qty>100</qty>
|
|
713
|
+
<status>AVAILABLE</status>
|
|
714
|
+
<expectedOn>2025-01-20</expectedOn>
|
|
715
|
+
</item>
|
|
716
|
+
<item locationRef="LOC-B" skuRef="SKU-002">
|
|
717
|
+
<qty>50</qty>
|
|
718
|
+
<status>RESERVED</status>
|
|
719
|
+
</item>
|
|
720
|
+
</inventory>
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
**Step 2: Parsed JavaScript Object (XMLParserService output)**
|
|
724
|
+
```javascript
|
|
725
|
+
{
|
|
726
|
+
inventory: {
|
|
727
|
+
item: [
|
|
728
|
+
{
|
|
729
|
+
"@locationRef": "LOC-A", // Attributes use @ prefix
|
|
730
|
+
"@skuRef": "SKU-001",
|
|
731
|
+
qty: 100,
|
|
732
|
+
status: "AVAILABLE",
|
|
733
|
+
expectedOn: "2025-01-20"
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
"@locationRef": "LOC-B",
|
|
737
|
+
"@skuRef": "SKU-002",
|
|
738
|
+
qty: 50,
|
|
739
|
+
status: "RESERVED"
|
|
740
|
+
}
|
|
741
|
+
]
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
**Step 3: Extraction with Normalization**
|
|
747
|
+
```javascript
|
|
748
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
749
|
+
|
|
750
|
+
// Normalize array
|
|
751
|
+
const items = parsed.inventory?.item;
|
|
752
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Step 4: Mapping Configuration**
|
|
756
|
+
```json
|
|
757
|
+
{
|
|
758
|
+
"fields": {
|
|
759
|
+
"locationRef": {
|
|
760
|
+
"source": "@locationRef",
|
|
761
|
+
"required": true,
|
|
762
|
+
"resolver": "sdk.trim"
|
|
763
|
+
},
|
|
764
|
+
"skuRef": {
|
|
765
|
+
"source": "@skuRef",
|
|
766
|
+
"required": true,
|
|
767
|
+
"resolver": "sdk.trim"
|
|
768
|
+
},
|
|
769
|
+
"qty": {
|
|
770
|
+
"source": "qty",
|
|
771
|
+
"required": true,
|
|
772
|
+
"resolver": "sdk.parseInt"
|
|
773
|
+
},
|
|
774
|
+
"status": {
|
|
775
|
+
"source": "status",
|
|
776
|
+
"required": true,
|
|
777
|
+
"resolver": "sdk.uppercase"
|
|
778
|
+
},
|
|
779
|
+
"expectedOn": {
|
|
780
|
+
"source": "expectedOn",
|
|
781
|
+
"resolver": "sdk.formatDate"
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
**What gets passed to mapper.map():**
|
|
788
|
+
```javascript
|
|
789
|
+
// For each normalized item:
|
|
790
|
+
await mapper.map({
|
|
791
|
+
"@locationRef": "LOC-A", // @ prefix in JavaScript object
|
|
792
|
+
"@skuRef": "SKU-001",
|
|
793
|
+
qty: 100,
|
|
794
|
+
status: "AVAILABLE",
|
|
795
|
+
expectedOn: "2025-01-20"
|
|
796
|
+
});
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
**Key Points:**
|
|
800
|
+
- XML attributes use `@` prefix in parsed JavaScript object
|
|
801
|
+
- Mapping config uses `@` prefix to reference attributes
|
|
802
|
+
- This is a JavaScript object key, not XPath syntax
|
|
803
|
+
- Elements without `@` are child elements
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
#### Complex XML: Orders with Attributes and Transformation Options
|
|
808
|
+
|
|
809
|
+
**XML File:**
|
|
810
|
+
```xml
|
|
811
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
812
|
+
<Orders>
|
|
813
|
+
<Order id="ORD-001" orderDate="2025-01-22">
|
|
814
|
+
<Customer>
|
|
815
|
+
<Name>John Doe</Name>
|
|
816
|
+
<Email>john@example.com</Email>
|
|
817
|
+
</Customer>
|
|
818
|
+
<Items>
|
|
819
|
+
<Item sku="PROD-001" qty="2">
|
|
820
|
+
<Price>29.99</Price>
|
|
821
|
+
</Item>
|
|
822
|
+
<Item sku="PROD-002" qty="1">
|
|
823
|
+
<Price>49.99</Price>
|
|
824
|
+
</Item>
|
|
825
|
+
</Items>
|
|
826
|
+
</Order>
|
|
827
|
+
<Order id="ORD-002" orderDate="2025-01-23">
|
|
828
|
+
<Customer>
|
|
829
|
+
<Name>Jane Smith</Name>
|
|
830
|
+
<Email>jane@example.com</Email>
|
|
831
|
+
</Customer>
|
|
832
|
+
<Items>
|
|
833
|
+
<Item sku="PROD-003" qty="3">
|
|
834
|
+
<Price>19.99</Price>
|
|
835
|
+
</Item>
|
|
836
|
+
</Items>
|
|
837
|
+
</Order>
|
|
838
|
+
</Orders>
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**Step 1: Parse (with attributes)**
|
|
842
|
+
```typescript
|
|
843
|
+
const xmlParser = new XMLParserService();
|
|
844
|
+
const parsed = await xmlParser.parse(xmlContent, { includeAttributes: true });
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Parsed JavaScript Object:**
|
|
848
|
+
```javascript
|
|
849
|
+
{
|
|
850
|
+
Orders: {
|
|
851
|
+
Order: [
|
|
852
|
+
{
|
|
853
|
+
"@id": "ORD-001", // ← Attributes prefixed with @
|
|
854
|
+
"@orderDate": "2025-01-22",
|
|
855
|
+
Customer: {
|
|
856
|
+
Name: "John Doe",
|
|
857
|
+
Email: "john@example.com"
|
|
858
|
+
},
|
|
859
|
+
Items: {
|
|
860
|
+
Item: [
|
|
861
|
+
{
|
|
862
|
+
"@sku": "PROD-001", // ← Attributes prefixed with @
|
|
863
|
+
"@qty": "2",
|
|
864
|
+
Price: "29.99"
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
"@sku": "PROD-002",
|
|
868
|
+
"@qty": "1",
|
|
869
|
+
Price: "49.99"
|
|
870
|
+
}
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"@id": "ORD-002",
|
|
876
|
+
"@orderDate": "2025-01-23",
|
|
877
|
+
Customer: { ... },
|
|
878
|
+
Items: { ... }
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
**Step 2: Extract/Normalize**
|
|
886
|
+
```typescript
|
|
887
|
+
const orders = parsed.Orders.Order;
|
|
888
|
+
const records = Array.isArray(orders) ? orders : orders ? [orders] : [];
|
|
889
|
+
// Result: [{ "@id": "ORD-001", "@orderDate": "...", Customer: {...}, Items: {...} }, ...]
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
**Step 3: Map**
|
|
893
|
+
|
|
894
|
+
**Option A: Map Each Order, Then Items Separately**
|
|
895
|
+
```typescript
|
|
896
|
+
const orderMapper = new UniversalMapper({
|
|
897
|
+
fields: {
|
|
898
|
+
orderRef: { source: "@id" }, // ← XML attribute
|
|
899
|
+
orderDate: { source: "@orderDate", resolver: "sdk.parseDate" },
|
|
900
|
+
customerName: { source: "Customer.Name" },
|
|
901
|
+
customerEmail: { source: "Customer.Email" },
|
|
902
|
+
items: {
|
|
903
|
+
source: "Items.Item", // ← Extract items array
|
|
904
|
+
isArray: true, // ← Iterate over items
|
|
905
|
+
fields: {
|
|
906
|
+
productRef: { source: "@sku" }, // ← Attribute from item
|
|
907
|
+
quantity: { source: "@qty", resolver: "sdk.parseInt" },
|
|
908
|
+
price: { source: "Price", resolver: "sdk.parseFloat" }
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
for (const order of records) {
|
|
915
|
+
const result = await orderMapper.map({ Order: order }); // ← Wrap for context
|
|
916
|
+
// Mapping: Order.@id, Order.Customer.Name, Order.Items.Item[].@sku
|
|
917
|
+
}
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
**Option B: Transform First, Then Map**
|
|
921
|
+
```typescript
|
|
922
|
+
// Extract items and flatten with order data
|
|
923
|
+
const allItems = [];
|
|
924
|
+
for (const order of records) {
|
|
925
|
+
const orderId = order["@id"];
|
|
926
|
+
const orderDate = order["@orderDate"];
|
|
927
|
+
const items = Array.isArray(order.Items.Item)
|
|
928
|
+
? order.Items.Item
|
|
929
|
+
: order.Items.Item
|
|
930
|
+
? [order.Items.Item]
|
|
931
|
+
: [];
|
|
932
|
+
|
|
933
|
+
for (const item of items) {
|
|
934
|
+
allItems.push({
|
|
935
|
+
orderId: orderId, // ← Add from parent
|
|
936
|
+
orderDate: orderDate, // ← Add from parent
|
|
937
|
+
sku: item["@sku"], // ← From item attribute
|
|
938
|
+
qty: item["@qty"], // ← From item attribute
|
|
939
|
+
price: item.Price // ← From item element
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Now map flattened structure
|
|
945
|
+
const itemMapper = new UniversalMapper({
|
|
946
|
+
fields: {
|
|
947
|
+
orderRef: { source: "orderId" }, // ← From transformed structure
|
|
948
|
+
orderDate: { source: "orderDate", resolver: "sdk.parseDate" },
|
|
949
|
+
productRef: { source: "sku" }, // ← From transformed structure
|
|
950
|
+
quantity: { source: "qty", resolver: "sdk.parseInt" },
|
|
951
|
+
price: { source: "price", resolver: "sdk.parseFloat" }
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
for (const item of allItems) {
|
|
956
|
+
const result = await itemMapper.map(item); // ← Pass transformed structure
|
|
957
|
+
}
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
**Mapping Matches:**
|
|
961
|
+
- Option A: Parsed XML structure (`Order.@id`, `Order.Items.Item[].@sku`)
|
|
962
|
+
- Option B: Transformed structure (`item.orderId`, `item.sku`)
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
### Complex JSON with Deep Nesting
|
|
967
|
+
|
|
968
|
+
#### Nested Path Navigation
|
|
969
|
+
|
|
970
|
+
**Original JSON File:**
|
|
971
|
+
```json
|
|
972
|
+
{
|
|
973
|
+
"warehouse": {
|
|
974
|
+
"id": "WH-01",
|
|
975
|
+
"location": {
|
|
976
|
+
"code": "LOC-A",
|
|
977
|
+
"name": "Warehouse A"
|
|
978
|
+
},
|
|
979
|
+
"inventory": {
|
|
980
|
+
"items": [
|
|
981
|
+
{
|
|
982
|
+
"product": {
|
|
983
|
+
"sku": "SKU-001",
|
|
984
|
+
"details": {
|
|
985
|
+
"name": "Widget",
|
|
986
|
+
"category": "Electronics"
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
"stock": {
|
|
990
|
+
"available": 100,
|
|
991
|
+
"reserved": 20
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
]
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**Step 2: Parsed JavaScript Object (JSONParserService output)**
|
|
1001
|
+
```javascript
|
|
1002
|
+
{
|
|
1003
|
+
warehouse: {
|
|
1004
|
+
id: "WH-01",
|
|
1005
|
+
location: {
|
|
1006
|
+
code: "LOC-A",
|
|
1007
|
+
name: "Warehouse A"
|
|
1008
|
+
},
|
|
1009
|
+
inventory: {
|
|
1010
|
+
items: [
|
|
1011
|
+
{
|
|
1012
|
+
product: {
|
|
1013
|
+
sku: "SKU-001",
|
|
1014
|
+
details: {
|
|
1015
|
+
name: "Widget",
|
|
1016
|
+
category: "Electronics"
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
stock: {
|
|
1020
|
+
available: 100,
|
|
1021
|
+
reserved: 20
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
]
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
**Step 3: Extraction**
|
|
1031
|
+
```javascript
|
|
1032
|
+
const parsed = await jsonParser.parse(jsonContent);
|
|
1033
|
+
|
|
1034
|
+
// Extract the nested array
|
|
1035
|
+
const records = parsed.warehouse.inventory.items;
|
|
1036
|
+
|
|
1037
|
+
// For shared context, store parent data
|
|
1038
|
+
const warehouseCode = parsed.warehouse.location.code;
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
**Step 4: Mapping Configuration**
|
|
1042
|
+
```json
|
|
1043
|
+
{
|
|
1044
|
+
"fields": {
|
|
1045
|
+
"skuRef": {
|
|
1046
|
+
"source": "product.sku",
|
|
1047
|
+
"required": true
|
|
1048
|
+
},
|
|
1049
|
+
"name": {
|
|
1050
|
+
"source": "product.details.name"
|
|
1051
|
+
},
|
|
1052
|
+
"qty": {
|
|
1053
|
+
"source": "stock.available",
|
|
1054
|
+
"resolver": "sdk.parseInt"
|
|
1055
|
+
},
|
|
1056
|
+
"locationRef": {
|
|
1057
|
+
"value": "LOC-A"
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
**What gets passed to mapper.map():**
|
|
1064
|
+
```javascript
|
|
1065
|
+
// For each item in the items array:
|
|
1066
|
+
await mapper.map({
|
|
1067
|
+
product: {
|
|
1068
|
+
sku: "SKU-001",
|
|
1069
|
+
details: {
|
|
1070
|
+
name: "Widget",
|
|
1071
|
+
category: "Electronics"
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
stock: {
|
|
1075
|
+
available: 100,
|
|
1076
|
+
reserved: 20
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
**Key Points:**
|
|
1082
|
+
- Dot notation navigates nested JavaScript objects
|
|
1083
|
+
- `"product.sku"` becomes `obj.product.sku` in JavaScript
|
|
1084
|
+
- `"product.details.name"` becomes `obj.product.details.name`
|
|
1085
|
+
- You can reference any depth of nesting
|
|
1086
|
+
|
|
1087
|
+
---
|
|
1088
|
+
|
|
1089
|
+
### XML with Shared Parent Data
|
|
1090
|
+
|
|
1091
|
+
#### Real-World Pattern: Enriching Child Records
|
|
1092
|
+
|
|
1093
|
+
This is a common pattern where parent-level data needs to be added to each child record.
|
|
1094
|
+
|
|
1095
|
+
**Original XML File:**
|
|
1096
|
+
```xml
|
|
1097
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1098
|
+
<InventoryStatus>
|
|
1099
|
+
<ItemInventory>
|
|
1100
|
+
<FacilityId>WAREHOUSE-01</FacilityId>
|
|
1101
|
+
<Item>
|
|
1102
|
+
<SKU>SKU-001</SKU>
|
|
1103
|
+
<Quantity>100</Quantity>
|
|
1104
|
+
<Status>AVAILABLE</Status>
|
|
1105
|
+
</Item>
|
|
1106
|
+
<Item>
|
|
1107
|
+
<SKU>SKU-002</SKU>
|
|
1108
|
+
<Quantity>50</Quantity>
|
|
1109
|
+
<Status>RESERVED</Status>
|
|
1110
|
+
</Item>
|
|
1111
|
+
</ItemInventory>
|
|
1112
|
+
</InventoryStatus>
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
**Step 2: Parsed JavaScript Object (XMLParserService output)**
|
|
1116
|
+
```javascript
|
|
1117
|
+
{
|
|
1118
|
+
InventoryStatus: {
|
|
1119
|
+
ItemInventory: {
|
|
1120
|
+
FacilityId: "WAREHOUSE-01",
|
|
1121
|
+
Item: [ // Multiple Items
|
|
1122
|
+
{
|
|
1123
|
+
SKU: "SKU-001",
|
|
1124
|
+
Quantity: 100,
|
|
1125
|
+
Status: "AVAILABLE"
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
SKU: "SKU-002",
|
|
1129
|
+
Quantity: 50,
|
|
1130
|
+
Status: "RESERVED"
|
|
1131
|
+
}
|
|
1132
|
+
]
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
**Step 3: Transformation - Enrich Each Item**
|
|
1139
|
+
```javascript
|
|
1140
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1141
|
+
|
|
1142
|
+
// Extract parent data
|
|
1143
|
+
const facilityId = parsed.InventoryStatus.ItemInventory.FacilityId;
|
|
1144
|
+
|
|
1145
|
+
// Extract items
|
|
1146
|
+
const items = parsed.InventoryStatus.ItemInventory.Item;
|
|
1147
|
+
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1148
|
+
|
|
1149
|
+
// TRANSFORM: Wrap each item with facility context
|
|
1150
|
+
const enrichedRecords = normalizedItems.map(item => ({
|
|
1151
|
+
itemInventory: {
|
|
1152
|
+
FacilityId: facilityId, // Add parent data to each item
|
|
1153
|
+
...item // Spread item properties
|
|
1154
|
+
}
|
|
1155
|
+
}));
|
|
1156
|
+
|
|
1157
|
+
// Now each record has the FacilityId embedded
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
**Result of Transformation:**
|
|
1161
|
+
```javascript
|
|
1162
|
+
[
|
|
1163
|
+
{
|
|
1164
|
+
itemInventory: {
|
|
1165
|
+
FacilityId: "WAREHOUSE-01", // Parent data added
|
|
1166
|
+
SKU: "SKU-001",
|
|
1167
|
+
Quantity: 100,
|
|
1168
|
+
Status: "AVAILABLE"
|
|
1169
|
+
}
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
itemInventory: {
|
|
1173
|
+
FacilityId: "WAREHOUSE-01", // Parent data added
|
|
1174
|
+
SKU: "SKU-002",
|
|
1175
|
+
Quantity: 50,
|
|
1176
|
+
Status: "RESERVED"
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
]
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**Step 4: Mapping Configuration**
|
|
1183
|
+
```json
|
|
1184
|
+
{
|
|
1185
|
+
"fields": {
|
|
1186
|
+
"locationRef": {
|
|
1187
|
+
"source": "itemInventory.FacilityId",
|
|
1188
|
+
"required": true
|
|
1189
|
+
},
|
|
1190
|
+
"skuRef": {
|
|
1191
|
+
"source": "itemInventory.SKU",
|
|
1192
|
+
"required": true
|
|
1193
|
+
},
|
|
1194
|
+
"qty": {
|
|
1195
|
+
"source": "itemInventory.Quantity",
|
|
1196
|
+
"resolver": "sdk.parseInt"
|
|
1197
|
+
},
|
|
1198
|
+
"status": {
|
|
1199
|
+
"source": "itemInventory.Status",
|
|
1200
|
+
"resolver": "sdk.uppercase"
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
**What gets passed to mapper.map():**
|
|
1207
|
+
```javascript
|
|
1208
|
+
// For each enriched record:
|
|
1209
|
+
await mapper.map({
|
|
1210
|
+
itemInventory: {
|
|
1211
|
+
FacilityId: "WAREHOUSE-01",
|
|
1212
|
+
SKU: "SKU-001",
|
|
1213
|
+
Quantity: 100,
|
|
1214
|
+
Status: "AVAILABLE"
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
**Why This Works:**
|
|
1220
|
+
- You transformed the data BEFORE mapping
|
|
1221
|
+
- The mapping config references the TRANSFORMED structure
|
|
1222
|
+
- Path `"itemInventory.FacilityId"` exists in the JavaScript object
|
|
1223
|
+
- This is NOT XPath - it's JavaScript property access
|
|
1224
|
+
|
|
1225
|
+
**Why Transformation Needed:**
|
|
1226
|
+
- XML has `FacilityId` in parent `ItemInventory` (shared by all Items)
|
|
1227
|
+
- Each `Item` needs its own `FacilityId` to create complete records
|
|
1228
|
+
- Mapping can't easily access parent data when iterating arrays
|
|
1229
|
+
- Solution: Transform to combine parent data with each child before mapping
|
|
1230
|
+
|
|
1231
|
+
**Complete Example Code:**
|
|
1232
|
+
```typescript
|
|
1233
|
+
import { XMLParserService, UniversalMapper } from '@fluentcommerce/fc-connect-sdk';
|
|
1234
|
+
|
|
1235
|
+
const xmlParser = new XMLParserService();
|
|
1236
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1237
|
+
|
|
1238
|
+
// Step 1: Extract parent data
|
|
1239
|
+
const facilityId = parsed.InventoryStatus.ItemInventory.FacilityId;
|
|
1240
|
+
|
|
1241
|
+
// Step 2: Extract and normalize items
|
|
1242
|
+
const items = parsed.InventoryStatus.ItemInventory.Item;
|
|
1243
|
+
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1244
|
+
|
|
1245
|
+
// Step 3: Transform - wrap each item
|
|
1246
|
+
const enrichedRecords = normalizedItems.map(item => ({
|
|
1247
|
+
itemInventory: {
|
|
1248
|
+
FacilityId: facilityId,
|
|
1249
|
+
...item
|
|
1250
|
+
}
|
|
1251
|
+
}));
|
|
1252
|
+
|
|
1253
|
+
// Step 4: Map each enriched record
|
|
1254
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
1255
|
+
|
|
1256
|
+
for (const record of enrichedRecords) {
|
|
1257
|
+
const result = await mapper.map(record);
|
|
1258
|
+
|
|
1259
|
+
if (result.success) {
|
|
1260
|
+
console.log('Mapped:', result.data);
|
|
1261
|
+
} else {
|
|
1262
|
+
console.error('Mapping failed:', result.errors);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
---
|
|
1268
|
+
|
|
1269
|
+
## Common Transformation Patterns
|
|
1270
|
+
|
|
1271
|
+
### Pattern 1: No Transformation (Flat CSV)
|
|
1272
|
+
|
|
1273
|
+
**Use Case:** CSV with flat structure
|
|
1274
|
+
|
|
1275
|
+
```javascript
|
|
1276
|
+
// Parsed CSV is already array of flat objects
|
|
1277
|
+
const records = parsedCsv;
|
|
1278
|
+
|
|
1279
|
+
// Map directly
|
|
1280
|
+
for (const record of records) {
|
|
1281
|
+
await mapper.map(record);
|
|
1282
|
+
}
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
### Pattern 2: Array Extraction (Nested JSON)
|
|
1286
|
+
|
|
1287
|
+
**Use Case:** JSON with nested data array
|
|
1288
|
+
|
|
1289
|
+
```javascript
|
|
1290
|
+
// Extract the array you need
|
|
1291
|
+
const parsed = await jsonParser.parse(jsonContent);
|
|
1292
|
+
const records = parsed.data.inventory.items;
|
|
1293
|
+
|
|
1294
|
+
// Map each item
|
|
1295
|
+
for (const record of records) {
|
|
1296
|
+
await mapper.map(record);
|
|
1297
|
+
}
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
### Pattern 3: Array Normalization (XML)
|
|
1301
|
+
|
|
1302
|
+
**Use Case:** XML with repeating elements
|
|
1303
|
+
|
|
1304
|
+
```javascript
|
|
1305
|
+
// Always normalize XML arrays
|
|
1306
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1307
|
+
const items = parsed.products?.product;
|
|
1308
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1309
|
+
|
|
1310
|
+
// Now safe to iterate
|
|
1311
|
+
for (const record of records) {
|
|
1312
|
+
await mapper.map(record);
|
|
1313
|
+
}
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
### Pattern 4: Parent Data Enrichment (XML with Context)
|
|
1317
|
+
|
|
1318
|
+
**Use Case:** Child elements need parent data
|
|
1319
|
+
|
|
1320
|
+
```javascript
|
|
1321
|
+
// Extract parent data
|
|
1322
|
+
const facilityId = parsed.warehouse.facility.id;
|
|
1323
|
+
|
|
1324
|
+
// Extract items
|
|
1325
|
+
const items = parsed.warehouse.facility.inventory;
|
|
1326
|
+
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1327
|
+
|
|
1328
|
+
// Enrich each item
|
|
1329
|
+
const enrichedRecords = normalizedItems.map(item => ({
|
|
1330
|
+
...item,
|
|
1331
|
+
facilityId: facilityId // Add parent data
|
|
1332
|
+
}));
|
|
1333
|
+
|
|
1334
|
+
// Map enriched records
|
|
1335
|
+
for (const record of enrichedRecords) {
|
|
1336
|
+
await mapper.map(record);
|
|
1337
|
+
}
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
### Pattern 5: Attribute Handling (XML Attributes)
|
|
1341
|
+
|
|
1342
|
+
**Use Case:** XML with attributes and elements
|
|
1343
|
+
|
|
1344
|
+
```javascript
|
|
1345
|
+
// XML Parser adds @ prefix for attributes
|
|
1346
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1347
|
+
const items = parsed.catalog?.product;
|
|
1348
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1349
|
+
|
|
1350
|
+
// Mapping config uses @ prefix
|
|
1351
|
+
const mappingConfig = {
|
|
1352
|
+
fields: {
|
|
1353
|
+
id: { source: "@id" }, // Attribute
|
|
1354
|
+
name: { source: "name" }, // Element
|
|
1355
|
+
price: { source: "@price" } // Attribute
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
## Key Takeaways
|
|
1363
|
+
|
|
1364
|
+
### 1. Mapping Uses Parsed JavaScript Objects
|
|
1365
|
+
|
|
1366
|
+
✅ **Mapping paths match the JavaScript object structure you pass to `mapper.map()`**
|
|
1367
|
+
|
|
1368
|
+
```typescript
|
|
1369
|
+
// What you pass:
|
|
1370
|
+
await mapper.map({ itemInventory: { FacilityId: "...", Item: {...} } });
|
|
1371
|
+
|
|
1372
|
+
// Mapping paths must match:
|
|
1373
|
+
{ source: "itemInventory.FacilityId" } // ✅ Matches
|
|
1374
|
+
{ source: "InventoryStatus.ItemInventory.FacilityId" } // ❌ Doesn't match
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
### 2. When Transformation is Needed
|
|
1378
|
+
|
|
1379
|
+
**Transform when:**
|
|
1380
|
+
- ✅ Parent data needs to be combined with child records (like XML with shared parent data)
|
|
1381
|
+
- ✅ Flat structure needs to become nested (CSV → nested JSON)
|
|
1382
|
+
- ✅ Multiple data sources need to be combined
|
|
1383
|
+
- ✅ Complex business logic is easier in code than mapping
|
|
1384
|
+
|
|
1385
|
+
**Don't transform when:**
|
|
1386
|
+
- ✅ Structure already matches target format
|
|
1387
|
+
- ✅ Simple field renaming is sufficient
|
|
1388
|
+
- ✅ Resolvers can handle the transformation
|
|
1389
|
+
|
|
1390
|
+
### 3. XML Attributes
|
|
1391
|
+
|
|
1392
|
+
**XML attributes are prefixed with `@` in parsed objects:**
|
|
1393
|
+
```xml
|
|
1394
|
+
<Order id="ORD-001">
|
|
1395
|
+
```
|
|
1396
|
+
```javascript
|
|
1397
|
+
{ Order: { "@id": "ORD-001" } }
|
|
1398
|
+
```
|
|
1399
|
+
```json
|
|
1400
|
+
{ "source": "Order@id" } // ← No dot before @
|
|
1401
|
+
```
|
|
1402
|
+
|
|
1403
|
+
### 4. Array Normalization
|
|
1404
|
+
|
|
1405
|
+
**Always normalize XML arrays (single element → object, multiple → array):**
|
|
1406
|
+
```typescript
|
|
1407
|
+
const items = parsed.products?.product;
|
|
1408
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
### 5. Mapping Pattern Summary
|
|
1412
|
+
|
|
1413
|
+
| Format | Parse Result | Extract/Normalize | Mapping Matches |
|
|
1414
|
+
|--------|-------------|-------------------|-----------------|
|
|
1415
|
+
| **CSV** | Array of flat objects | Already normalized | Flat record structure |
|
|
1416
|
+
| **JSON** | Nested objects | Extract array, optionally transform | Passed structure |
|
|
1417
|
+
| **XML** | Nested objects with `@` attributes | Extract array, normalize, optionally transform | Passed structure |
|
|
1418
|
+
|
|
1419
|
+
---
|
|
1420
|
+
|
|
1421
|
+
## Visual Flow Diagrams
|
|
1422
|
+
|
|
1423
|
+
### Data Flow: CSV to Fluent
|
|
1424
|
+
|
|
1425
|
+
```
|
|
1426
|
+
┌───────────────────────────────────────────────────────────────────┐
|
|
1427
|
+
│ CSV FILE │
|
|
1428
|
+
├───────────────────────────────────────────────────────────────────┤
|
|
1429
|
+
│ sku_id,quantity,location_code │
|
|
1430
|
+
│ SKU-001,100,LOC-A │
|
|
1431
|
+
│ SKU-002,50,LOC-B │
|
|
1432
|
+
└───────────────────────────────────────────────────────────────────┘
|
|
1433
|
+
│
|
|
1434
|
+
│ CSVParserService.parse()
|
|
1435
|
+
↓
|
|
1436
|
+
┌───────────────────────────────────────────────────────────────────┐
|
|
1437
|
+
│ PARSED JAVASCRIPT ARRAY │
|
|
1438
|
+
├───────────────────────────────────────────────────────────────────┤
|
|
1439
|
+
│ [ │
|
|
1440
|
+
│ { sku_id: "SKU-001", quantity: "100", location_code: "LOC-A" }, │
|
|
1441
|
+
│ { sku_id: "SKU-002", quantity: "50", location_code: "LOC-B" } │
|
|
1442
|
+
│ ] │
|
|
1443
|
+
└───────────────────────────────────────────────────────────────────┘
|
|
1444
|
+
│
|
|
1445
|
+
│ No transformation needed (flat)
|
|
1446
|
+
↓
|
|
1447
|
+
┌───────────────────────────────────────────────────────────────────┐
|
|
1448
|
+
│ WHAT MAPPER RECEIVES │
|
|
1449
|
+
├───────────────────────────────────────────────────────────────────┤
|
|
1450
|
+
│ { │
|
|
1451
|
+
│ sku_id: "SKU-001", ← mapping: "skuRef" from "sku_id" │
|
|
1452
|
+
│ quantity: "100", ← mapping: "qty" from "quantity" │
|
|
1453
|
+
│ location_code: "LOC-A" ← mapping: "locationRef" from ... │
|
|
1454
|
+
│ } │
|
|
1455
|
+
└───────────────────────────────────────────────────────────────────┘
|
|
1456
|
+
│
|
|
1457
|
+
│ UniversalMapper.map()
|
|
1458
|
+
↓
|
|
1459
|
+
┌───────────────────────────────────────────────────────────────────┐
|
|
1460
|
+
│ FLUENT FORMAT │
|
|
1461
|
+
├───────────────────────────────────────────────────────────────────┤
|
|
1462
|
+
│ { │
|
|
1463
|
+
│ skuRef: "SKU-001", │
|
|
1464
|
+
│ qty: 100, ← Converted to number by sdk.parseInt │
|
|
1465
|
+
│ locationRef: "LOC-A" │
|
|
1466
|
+
│ } │
|
|
1467
|
+
└───────────────────────────────────────────────────────────────────┘
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
### Data Flow: XML with Attributes to Fluent
|
|
1471
|
+
|
|
1472
|
+
```
|
|
1473
|
+
┌────────────────────────────────────────────────────────────────────┐
|
|
1474
|
+
│ XML FILE │
|
|
1475
|
+
├────────────────────────────────────────────────────────────────────┤
|
|
1476
|
+
│ <inventory> │
|
|
1477
|
+
│ <item locationRef="LOC-A" skuRef="SKU-001"> │
|
|
1478
|
+
│ <qty>100</qty> │
|
|
1479
|
+
│ <status>AVAILABLE</status> │
|
|
1480
|
+
│ </item> │
|
|
1481
|
+
│ </inventory> │
|
|
1482
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
1483
|
+
│
|
|
1484
|
+
│ XMLParserService.parse()
|
|
1485
|
+
↓
|
|
1486
|
+
┌────────────────────────────────────────────────────────────────────┐
|
|
1487
|
+
│ PARSED JAVASCRIPT OBJECT │
|
|
1488
|
+
├────────────────────────────────────────────────────────────────────┤
|
|
1489
|
+
│ { │
|
|
1490
|
+
│ inventory: { │
|
|
1491
|
+
│ item: { │
|
|
1492
|
+
│ "@locationRef": "LOC-A", ← @ prefix for attributes │
|
|
1493
|
+
│ "@skuRef": "SKU-001", │
|
|
1494
|
+
│ qty: 100, ← Elements without @ │
|
|
1495
|
+
│ status: "AVAILABLE" │
|
|
1496
|
+
│ } │
|
|
1497
|
+
│ } │
|
|
1498
|
+
│ } │
|
|
1499
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
1500
|
+
│
|
|
1501
|
+
│ Extract: parsed.inventory.item
|
|
1502
|
+
│ Normalize: single element, wrap in array
|
|
1503
|
+
↓
|
|
1504
|
+
┌────────────────────────────────────────────────────────────────────┐
|
|
1505
|
+
│ WHAT MAPPER RECEIVES │
|
|
1506
|
+
├────────────────────────────────────────────────────────────────────┤
|
|
1507
|
+
│ { │
|
|
1508
|
+
│ "@locationRef": "LOC-A", ← mapping: source "@locationRef" │
|
|
1509
|
+
│ "@skuRef": "SKU-001", ← mapping: source "@skuRef" │
|
|
1510
|
+
│ qty: 100, ← mapping: source "qty" │
|
|
1511
|
+
│ status: "AVAILABLE" ← mapping: source "status" │
|
|
1512
|
+
│ } │
|
|
1513
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
1514
|
+
│
|
|
1515
|
+
│ UniversalMapper.map()
|
|
1516
|
+
↓
|
|
1517
|
+
┌────────────────────────────────────────────────────────────────────┐
|
|
1518
|
+
│ FLUENT FORMAT │
|
|
1519
|
+
├────────────────────────────────────────────────────────────────────┤
|
|
1520
|
+
│ { │
|
|
1521
|
+
│ locationRef: "LOC-A", │
|
|
1522
|
+
│ skuRef: "SKU-001", │
|
|
1523
|
+
│ qty: 100, │
|
|
1524
|
+
│ status: "AVAILABLE" │
|
|
1525
|
+
│ } │
|
|
1526
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
### Data Flow: XML with Parent Data Enrichment
|
|
1530
|
+
|
|
1531
|
+
```
|
|
1532
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
1533
|
+
│ XML FILE │
|
|
1534
|
+
├──────────────────────────────────────────────────────────────────────┤
|
|
1535
|
+
│ <InventoryStatus> │
|
|
1536
|
+
│ <ItemInventory> │
|
|
1537
|
+
│ <FacilityId>WH-01</FacilityId> ← Parent data │
|
|
1538
|
+
│ <Item><SKU>SKU-001</SKU><Qty>100</Qty></Item> ← Children │
|
|
1539
|
+
│ <Item><SKU>SKU-002</SKU><Qty>50</Qty></Item> │
|
|
1540
|
+
│ </ItemInventory> │
|
|
1541
|
+
│ </InventoryStatus> │
|
|
1542
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
1543
|
+
│
|
|
1544
|
+
│ XMLParserService.parse()
|
|
1545
|
+
↓
|
|
1546
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
1547
|
+
│ PARSED JAVASCRIPT OBJECT │
|
|
1548
|
+
├──────────────────────────────────────────────────────────────────────┤
|
|
1549
|
+
│ { │
|
|
1550
|
+
│ InventoryStatus: { │
|
|
1551
|
+
│ ItemInventory: { │
|
|
1552
|
+
│ FacilityId: "WH-01", ← Parent data │
|
|
1553
|
+
│ Item: [ ← Array of children │
|
|
1554
|
+
│ { SKU: "SKU-001", Qty: 100 }, │
|
|
1555
|
+
│ { SKU: "SKU-002", Qty: 50 } │
|
|
1556
|
+
│ ] │
|
|
1557
|
+
│ } │
|
|
1558
|
+
│ } │
|
|
1559
|
+
│ } │
|
|
1560
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
1561
|
+
│
|
|
1562
|
+
│ TRANSFORMATION:
|
|
1563
|
+
│ 1. Extract facilityId
|
|
1564
|
+
│ 2. Normalize Item array
|
|
1565
|
+
│ 3. Wrap each item with facilityId
|
|
1566
|
+
↓
|
|
1567
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
1568
|
+
│ TRANSFORMED RECORDS │
|
|
1569
|
+
├──────────────────────────────────────────────────────────────────────┤
|
|
1570
|
+
│ [ │
|
|
1571
|
+
│ { │
|
|
1572
|
+
│ itemInventory: { │
|
|
1573
|
+
│ FacilityId: "WH-01", ← Parent data added to each item │
|
|
1574
|
+
│ SKU: "SKU-001", │
|
|
1575
|
+
│ Qty: 100 │
|
|
1576
|
+
│ } │
|
|
1577
|
+
│ }, │
|
|
1578
|
+
│ { │
|
|
1579
|
+
│ itemInventory: { │
|
|
1580
|
+
│ FacilityId: "WH-01", ← Same parent data │
|
|
1581
|
+
│ SKU: "SKU-002", │
|
|
1582
|
+
│ Qty: 50 │
|
|
1583
|
+
│ } │
|
|
1584
|
+
│ } │
|
|
1585
|
+
│ ] │
|
|
1586
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
1587
|
+
│
|
|
1588
|
+
│ For each record...
|
|
1589
|
+
↓
|
|
1590
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
1591
|
+
│ WHAT MAPPER RECEIVES │
|
|
1592
|
+
├──────────────────────────────────────────────────────────────────────┤
|
|
1593
|
+
│ { │
|
|
1594
|
+
│ itemInventory: { │
|
|
1595
|
+
│ FacilityId: "WH-01", ← source: "itemInventory.FacilityId" │
|
|
1596
|
+
│ SKU: "SKU-001", ← source: "itemInventory.SKU" │
|
|
1597
|
+
│ Qty: 100 ← source: "itemInventory.Qty" │
|
|
1598
|
+
│ } │
|
|
1599
|
+
│ } │
|
|
1600
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
1601
|
+
│
|
|
1602
|
+
│ UniversalMapper.map()
|
|
1603
|
+
↓
|
|
1604
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
1605
|
+
│ FLUENT FORMAT │
|
|
1606
|
+
├──────────────────────────────────────────────────────────────────────┤
|
|
1607
|
+
│ { │
|
|
1608
|
+
│ locationRef: "WH-01", │
|
|
1609
|
+
│ skuRef: "SKU-001", │
|
|
1610
|
+
│ qty: 100 │
|
|
1611
|
+
│ } │
|
|
1612
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
1613
|
+
```
|
|
1614
|
+
|
|
1615
|
+
---
|
|
1616
|
+
|
|
1617
|
+
## Debugging Guide
|
|
1618
|
+
|
|
1619
|
+
### How to Figure Out What Paths to Use
|
|
1620
|
+
|
|
1621
|
+
#### Step 1: Log the Parsed Structure
|
|
1622
|
+
|
|
1623
|
+
```typescript
|
|
1624
|
+
import { XMLParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
1625
|
+
|
|
1626
|
+
const xmlParser = new XMLParserService();
|
|
1627
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1628
|
+
|
|
1629
|
+
// Log the ENTIRE parsed structure
|
|
1630
|
+
console.log('Parsed structure:', JSON.stringify(parsed, null, 2));
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
This shows you the exact JavaScript object structure.
|
|
1634
|
+
|
|
1635
|
+
#### Step 2: Identify What You Need
|
|
1636
|
+
|
|
1637
|
+
Look at the output and find:
|
|
1638
|
+
- Where is the array of items you need to process?
|
|
1639
|
+
- What are the property names in the JavaScript object?
|
|
1640
|
+
- Are there any `@` prefixes (XML attributes)?
|
|
1641
|
+
|
|
1642
|
+
#### Step 3: Test Your Extraction
|
|
1643
|
+
|
|
1644
|
+
```typescript
|
|
1645
|
+
// Try to extract the array
|
|
1646
|
+
const items = parsed.inventory?.item;
|
|
1647
|
+
console.log('Extracted items:', items);
|
|
1648
|
+
|
|
1649
|
+
// Check if it's an array
|
|
1650
|
+
console.log('Is array?', Array.isArray(items));
|
|
1651
|
+
|
|
1652
|
+
// Normalize if needed
|
|
1653
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1654
|
+
console.log('Normalized records:', records);
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
#### Step 4: Check a Single Record
|
|
1658
|
+
|
|
1659
|
+
```typescript
|
|
1660
|
+
// Look at one record
|
|
1661
|
+
const firstRecord = records[0];
|
|
1662
|
+
console.log('First record:', JSON.stringify(firstRecord, null, 2));
|
|
1663
|
+
```
|
|
1664
|
+
|
|
1665
|
+
This shows you exactly what structure the mapper will receive.
|
|
1666
|
+
|
|
1667
|
+
#### Step 5: Match Mapping Config to Structure
|
|
1668
|
+
|
|
1669
|
+
```typescript
|
|
1670
|
+
// Your mapping config paths must match the JavaScript object
|
|
1671
|
+
const mappingConfig = {
|
|
1672
|
+
fields: {
|
|
1673
|
+
// If you see: { "sku": "SKU-001" }
|
|
1674
|
+
skuRef: { source: "sku" },
|
|
1675
|
+
|
|
1676
|
+
// If you see: { "@sku": "SKU-001" }
|
|
1677
|
+
skuRef: { source: "@sku" },
|
|
1678
|
+
|
|
1679
|
+
// If you see: { "product": { "sku": "SKU-001" } }
|
|
1680
|
+
skuRef: { source: "product.sku" }
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
```
|
|
1684
|
+
|
|
1685
|
+
#### Step 6: Test Mapping on One Record
|
|
1686
|
+
|
|
1687
|
+
```typescript
|
|
1688
|
+
import { UniversalMapper } from '@fluentcommerce/fc-connect-sdk';
|
|
1689
|
+
|
|
1690
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
1691
|
+
const result = await mapper.map(firstRecord);
|
|
1692
|
+
|
|
1693
|
+
if (result.success) {
|
|
1694
|
+
console.log('Mapping succeeded:', result.data);
|
|
1695
|
+
} else {
|
|
1696
|
+
console.error('Mapping failed:', result.errors);
|
|
1697
|
+
|
|
1698
|
+
// Check each error
|
|
1699
|
+
result.errors.forEach(error => {
|
|
1700
|
+
console.error('Field:', error.field);
|
|
1701
|
+
console.error('Message:', error.message);
|
|
1702
|
+
console.error('Path:', error.path);
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
### Common Debugging Mistakes
|
|
1708
|
+
|
|
1709
|
+
#### Mistake 1: Using File Structure Instead of Object Structure
|
|
1710
|
+
|
|
1711
|
+
**WRONG:**
|
|
1712
|
+
```typescript
|
|
1713
|
+
// CSV file has column "sku_id"
|
|
1714
|
+
const mappingConfig = {
|
|
1715
|
+
fields: {
|
|
1716
|
+
skuRef: { source: "columns.sku_id" } // WRONG!
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
**CORRECT:**
|
|
1722
|
+
```typescript
|
|
1723
|
+
// Parsed CSV gives you { sku_id: "SKU-001" }
|
|
1724
|
+
const mappingConfig = {
|
|
1725
|
+
fields: {
|
|
1726
|
+
skuRef: { source: "sku_id" } // CORRECT
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
```
|
|
1730
|
+
|
|
1731
|
+
#### Mistake 2: Using XPath for XML
|
|
1732
|
+
|
|
1733
|
+
**WRONG:**
|
|
1734
|
+
```typescript
|
|
1735
|
+
// XML has <product><sku>SKU-001</sku></product>
|
|
1736
|
+
const mappingConfig = {
|
|
1737
|
+
fields: {
|
|
1738
|
+
skuRef: { source: "//product/sku" } // WRONG! Not XPath
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
```
|
|
1742
|
+
|
|
1743
|
+
**CORRECT:**
|
|
1744
|
+
```typescript
|
|
1745
|
+
// Parsed XML gives you { product: { sku: "SKU-001" } }
|
|
1746
|
+
const mappingConfig = {
|
|
1747
|
+
fields: {
|
|
1748
|
+
skuRef: { source: "product.sku" } // CORRECT: JavaScript path
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
#### Mistake 3: Forgetting to Normalize XML Arrays
|
|
1754
|
+
|
|
1755
|
+
**WRONG:**
|
|
1756
|
+
```typescript
|
|
1757
|
+
const items = parsed.products.product;
|
|
1758
|
+
|
|
1759
|
+
// If there's only one <product>, this breaks!
|
|
1760
|
+
for (const item of items) { // ERROR: items is not iterable
|
|
1761
|
+
await mapper.map(item);
|
|
1762
|
+
}
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
**CORRECT:**
|
|
1766
|
+
```typescript
|
|
1767
|
+
const items = parsed.products?.product;
|
|
1768
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
1769
|
+
|
|
1770
|
+
// Now always works
|
|
1771
|
+
for (const record of records) {
|
|
1772
|
+
await mapper.map(record);
|
|
1773
|
+
}
|
|
1774
|
+
```
|
|
1775
|
+
|
|
1776
|
+
#### Mistake 4: Missing @ Prefix for XML Attributes
|
|
1777
|
+
|
|
1778
|
+
**WRONG:**
|
|
1779
|
+
```typescript
|
|
1780
|
+
// XML: <item locationRef="LOC-A">
|
|
1781
|
+
const mappingConfig = {
|
|
1782
|
+
fields: {
|
|
1783
|
+
locationRef: { source: "locationRef" } // WRONG: Missing @
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
```
|
|
1787
|
+
|
|
1788
|
+
**CORRECT:**
|
|
1789
|
+
```typescript
|
|
1790
|
+
// Parsed: { "@locationRef": "LOC-A" }
|
|
1791
|
+
const mappingConfig = {
|
|
1792
|
+
fields: {
|
|
1793
|
+
locationRef: { source: "@locationRef" } // CORRECT
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
### Debugging Checklist
|
|
1799
|
+
|
|
1800
|
+
When your mapping fails, check:
|
|
1801
|
+
|
|
1802
|
+
- [ ] Did you log the parsed structure with `JSON.stringify(parsed, null, 2)`?
|
|
1803
|
+
- [ ] Did you check what gets passed to `mapper.map()` at line X?
|
|
1804
|
+
- [ ] Are you using JavaScript object paths, not file format paths?
|
|
1805
|
+
- [ ] Did you normalize XML arrays? (`Array.isArray() ? : []:[]`)
|
|
1806
|
+
- [ ] Are you using `@` prefix for XML attributes?
|
|
1807
|
+
- [ ] Did you check for typos in property names?
|
|
1808
|
+
- [ ] Did you verify the property actually exists in the object?
|
|
1809
|
+
- [ ] Are you mapping nested properties with dot notation correctly?
|
|
1810
|
+
|
|
1811
|
+
---
|
|
1812
|
+
|
|
1813
|
+
## Real-World Example: InventoryStatus
|
|
1814
|
+
|
|
1815
|
+
This is a real user case that demonstrates the XML parent data enrichment pattern.
|
|
1816
|
+
|
|
1817
|
+
### The Problem
|
|
1818
|
+
|
|
1819
|
+
User has XML with a parent-level `FacilityId` that needs to be added to each child `Item`:
|
|
1820
|
+
|
|
1821
|
+
```xml
|
|
1822
|
+
<InventoryStatus>
|
|
1823
|
+
<ItemInventory>
|
|
1824
|
+
<FacilityId>WAREHOUSE-01</FacilityId>
|
|
1825
|
+
<Item>
|
|
1826
|
+
<SKU>SKU-001</SKU>
|
|
1827
|
+
<Quantity>100</Quantity>
|
|
1828
|
+
</Item>
|
|
1829
|
+
<Item>
|
|
1830
|
+
<SKU>SKU-002</SKU>
|
|
1831
|
+
<Quantity>50</Quantity>
|
|
1832
|
+
</Item>
|
|
1833
|
+
</ItemInventory>
|
|
1834
|
+
</InventoryStatus>
|
|
1835
|
+
```
|
|
1836
|
+
|
|
1837
|
+
**Challenge:** Each `Item` needs the `FacilityId` for the Fluent `locationRef` field, but it's at the parent `ItemInventory` level.
|
|
1838
|
+
|
|
1839
|
+
### The Solution
|
|
1840
|
+
|
|
1841
|
+
**Step 1: Parse XML**
|
|
1842
|
+
```typescript
|
|
1843
|
+
import { XMLParserService } from '@fluentcommerce/fc-connect-sdk';
|
|
1844
|
+
|
|
1845
|
+
const xmlParser = new XMLParserService();
|
|
1846
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1847
|
+
|
|
1848
|
+
// Result:
|
|
1849
|
+
// {
|
|
1850
|
+
// InventoryStatus: {
|
|
1851
|
+
// ItemInventory: {
|
|
1852
|
+
// FacilityId: "WAREHOUSE-01",
|
|
1853
|
+
// Item: [
|
|
1854
|
+
// { SKU: "SKU-001", Quantity: 100 },
|
|
1855
|
+
// { SKU: "SKU-002", Quantity: 50 }
|
|
1856
|
+
// ]
|
|
1857
|
+
// }
|
|
1858
|
+
// }
|
|
1859
|
+
// }
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
**Step 2: Extract Parent and Child Data**
|
|
1863
|
+
```typescript
|
|
1864
|
+
const itemInventory = parsed.InventoryStatus.ItemInventory;
|
|
1865
|
+
const facilityId = itemInventory.FacilityId;
|
|
1866
|
+
|
|
1867
|
+
// Normalize array (handle single vs multiple Items)
|
|
1868
|
+
const items = itemInventory.Item;
|
|
1869
|
+
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1870
|
+
```
|
|
1871
|
+
|
|
1872
|
+
**Step 3: Transform - Wrap Each Item with Parent Context**
|
|
1873
|
+
```typescript
|
|
1874
|
+
const enrichedRecords = normalizedItems.map(item => ({
|
|
1875
|
+
itemInventory: {
|
|
1876
|
+
FacilityId: facilityId, // Parent data
|
|
1877
|
+
...item // Child data
|
|
1878
|
+
}
|
|
1879
|
+
}));
|
|
1880
|
+
|
|
1881
|
+
// Result:
|
|
1882
|
+
// [
|
|
1883
|
+
// {
|
|
1884
|
+
// itemInventory: {
|
|
1885
|
+
// FacilityId: "WAREHOUSE-01",
|
|
1886
|
+
// SKU: "SKU-001",
|
|
1887
|
+
// Quantity: 100
|
|
1888
|
+
// }
|
|
1889
|
+
// },
|
|
1890
|
+
// ...
|
|
1891
|
+
// ]
|
|
1892
|
+
```
|
|
1893
|
+
|
|
1894
|
+
**Step 4: Mapping Configuration**
|
|
1895
|
+
```json
|
|
1896
|
+
{
|
|
1897
|
+
"fields": {
|
|
1898
|
+
"locationRef": {
|
|
1899
|
+
"source": "itemInventory.FacilityId",
|
|
1900
|
+
"required": true,
|
|
1901
|
+
"resolver": "sdk.trim"
|
|
1902
|
+
},
|
|
1903
|
+
"skuRef": {
|
|
1904
|
+
"source": "itemInventory.SKU",
|
|
1905
|
+
"required": true,
|
|
1906
|
+
"resolver": "sdk.trim"
|
|
1907
|
+
},
|
|
1908
|
+
"qty": {
|
|
1909
|
+
"source": "itemInventory.Quantity",
|
|
1910
|
+
"required": true,
|
|
1911
|
+
"resolver": "sdk.parseInt"
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
**Step 5: Map Each Record**
|
|
1918
|
+
```typescript
|
|
1919
|
+
import { UniversalMapper } from '@fluentcommerce/fc-connect-sdk';
|
|
1920
|
+
|
|
1921
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
1922
|
+
|
|
1923
|
+
for (const record of enrichedRecords) {
|
|
1924
|
+
const result = await mapper.map(record);
|
|
1925
|
+
|
|
1926
|
+
if (result.success) {
|
|
1927
|
+
console.log('Mapped record:', result.data);
|
|
1928
|
+
// {
|
|
1929
|
+
// locationRef: "WAREHOUSE-01",
|
|
1930
|
+
// skuRef: "SKU-001",
|
|
1931
|
+
// qty: 100
|
|
1932
|
+
// }
|
|
1933
|
+
} else {
|
|
1934
|
+
console.error('Mapping failed:', result.errors);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
```
|
|
1938
|
+
|
|
1939
|
+
### Why This Works
|
|
1940
|
+
|
|
1941
|
+
1. **Parsing creates JavaScript object** - XML → `{ InventoryStatus: { ItemInventory: { ... } } }`
|
|
1942
|
+
2. **Transformation wraps each item** - Added `FacilityId` to each item's structure
|
|
1943
|
+
3. **Mapping uses transformed structure** - Paths like `itemInventory.FacilityId` exist in the JavaScript object
|
|
1944
|
+
4. **Not XPath, not XML structure** - Pure JavaScript object property access
|
|
1945
|
+
|
|
1946
|
+
### Complete Working Example
|
|
1947
|
+
|
|
1948
|
+
```typescript
|
|
1949
|
+
import {
|
|
1950
|
+
XMLParserService,
|
|
1951
|
+
UniversalMapper,
|
|
1952
|
+
createClient
|
|
1953
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
1954
|
+
|
|
1955
|
+
async function processInventoryStatus(xmlContent: string) {
|
|
1956
|
+
// Parse XML
|
|
1957
|
+
const xmlParser = new XMLParserService();
|
|
1958
|
+
const parsed = await xmlParser.parse(xmlContent);
|
|
1959
|
+
|
|
1960
|
+
// Extract parent data
|
|
1961
|
+
const itemInventory = parsed.InventoryStatus.ItemInventory;
|
|
1962
|
+
const facilityId = itemInventory.FacilityId;
|
|
1963
|
+
|
|
1964
|
+
// Extract and normalize items
|
|
1965
|
+
const items = itemInventory.Item;
|
|
1966
|
+
const normalizedItems = Array.isArray(items) ? items : items ? [items] : [];
|
|
1967
|
+
|
|
1968
|
+
// Transform: wrap each item with parent context
|
|
1969
|
+
const enrichedRecords = normalizedItems.map(item => ({
|
|
1970
|
+
itemInventory: {
|
|
1971
|
+
FacilityId: facilityId,
|
|
1972
|
+
...item
|
|
1973
|
+
}
|
|
1974
|
+
}));
|
|
1975
|
+
|
|
1976
|
+
// Mapping configuration
|
|
1977
|
+
const mappingConfig = {
|
|
1978
|
+
fields: {
|
|
1979
|
+
locationRef: {
|
|
1980
|
+
source: "itemInventory.FacilityId",
|
|
1981
|
+
required: true,
|
|
1982
|
+
resolver: "sdk.trim"
|
|
1983
|
+
},
|
|
1984
|
+
skuRef: {
|
|
1985
|
+
source: "itemInventory.SKU",
|
|
1986
|
+
required: true,
|
|
1987
|
+
resolver: "sdk.trim"
|
|
1988
|
+
},
|
|
1989
|
+
qty: {
|
|
1990
|
+
source: "itemInventory.Quantity",
|
|
1991
|
+
required: true,
|
|
1992
|
+
resolver: "sdk.parseInt"
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
|
|
1997
|
+
// Map each record
|
|
1998
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
1999
|
+
const mappedRecords = [];
|
|
2000
|
+
|
|
2001
|
+
for (const record of enrichedRecords) {
|
|
2002
|
+
const result = await mapper.map(record);
|
|
2003
|
+
|
|
2004
|
+
if (result.success) {
|
|
2005
|
+
mappedRecords.push(result.data);
|
|
2006
|
+
} else {
|
|
2007
|
+
console.error('Mapping failed for record:', record);
|
|
2008
|
+
console.error('Errors:', result.errors);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// Send to Fluent Commerce
|
|
2013
|
+
const client = await createClient({ config });
|
|
2014
|
+
|
|
2015
|
+
const job = await client.createJob({
|
|
2016
|
+
name: 'inventory-status-update',
|
|
2017
|
+
retailerId: process.env.FLUENT_RETAILER_ID!
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
await client.sendBatch(job.id, {
|
|
2021
|
+
action: 'UPSERT',
|
|
2022
|
+
entityType: 'INVENTORY',
|
|
2023
|
+
entities: mappedRecords
|
|
2024
|
+
});
|
|
2025
|
+
|
|
2026
|
+
console.log(`Processed ${mappedRecords.length} inventory records`);
|
|
2027
|
+
}
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
---
|
|
2031
|
+
|
|
2032
|
+
## Best Practices
|
|
2033
|
+
|
|
2034
|
+
### 1. Always Log Parsed Structure First
|
|
2035
|
+
|
|
2036
|
+
```typescript
|
|
2037
|
+
const parsed = await parser.parse(content);
|
|
2038
|
+
console.log('Parsed structure:', JSON.stringify(parsed, null, 2));
|
|
2039
|
+
```
|
|
2040
|
+
|
|
2041
|
+
This eliminates guesswork about object structure.
|
|
2042
|
+
|
|
2043
|
+
### 2. Normalize XML Arrays Immediately
|
|
2044
|
+
|
|
2045
|
+
```typescript
|
|
2046
|
+
// Always do this for XML
|
|
2047
|
+
const items = parsed.inventory?.item;
|
|
2048
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
2049
|
+
```
|
|
2050
|
+
|
|
2051
|
+
Prevents runtime errors when XML has only one element.
|
|
2052
|
+
|
|
2053
|
+
### 3. Use External JSON for Mapping Configs
|
|
2054
|
+
|
|
2055
|
+
```typescript
|
|
2056
|
+
// GOOD: External JSON file
|
|
2057
|
+
import mappingConfig from './config/inventory.json' with { type: 'json' };
|
|
2058
|
+
const mapper = new UniversalMapper(mappingConfig);
|
|
2059
|
+
```
|
|
2060
|
+
|
|
2061
|
+
Keeps code clean and configs maintainable.
|
|
2062
|
+
|
|
2063
|
+
### 4. Transform Before Mapping
|
|
2064
|
+
|
|
2065
|
+
```typescript
|
|
2066
|
+
// Extract parent data
|
|
2067
|
+
const facilityId = parsed.warehouse.id;
|
|
2068
|
+
|
|
2069
|
+
// Transform records to include parent data
|
|
2070
|
+
const enrichedRecords = items.map(item => ({
|
|
2071
|
+
...item,
|
|
2072
|
+
facilityId: facilityId
|
|
2073
|
+
}));
|
|
2074
|
+
|
|
2075
|
+
// Now mapping can reference facilityId
|
|
2076
|
+
```
|
|
2077
|
+
|
|
2078
|
+
Don't try to access parent data during mapping - transform first.
|
|
2079
|
+
|
|
2080
|
+
### 5. Test Mapping on One Record First
|
|
2081
|
+
|
|
2082
|
+
```typescript
|
|
2083
|
+
// Test with first record
|
|
2084
|
+
const testRecord = records[0];
|
|
2085
|
+
const result = await mapper.map(testRecord);
|
|
2086
|
+
|
|
2087
|
+
if (!result.success) {
|
|
2088
|
+
console.error('Mapping errors:', result.errors);
|
|
2089
|
+
// Fix mapping config before processing all records
|
|
2090
|
+
}
|
|
2091
|
+
```
|
|
2092
|
+
|
|
2093
|
+
Catch mapping errors early.
|
|
2094
|
+
|
|
2095
|
+
### 6. Use Descriptive Property Names in Transformations
|
|
2096
|
+
|
|
2097
|
+
```typescript
|
|
2098
|
+
// GOOD: Clear structure
|
|
2099
|
+
const enrichedRecords = items.map(item => ({
|
|
2100
|
+
itemInventory: {
|
|
2101
|
+
FacilityId: facilityId,
|
|
2102
|
+
...item
|
|
2103
|
+
}
|
|
2104
|
+
}));
|
|
2105
|
+
|
|
2106
|
+
// BAD: Unclear structure
|
|
2107
|
+
const enrichedRecords = items.map(item => ({
|
|
2108
|
+
...item,
|
|
2109
|
+
f: facilityId // What is 'f'?
|
|
2110
|
+
}));
|
|
2111
|
+
```
|
|
2112
|
+
|
|
2113
|
+
Makes mapping configs easier to understand.
|
|
2114
|
+
|
|
2115
|
+
### 7. Document Your Transformation Logic
|
|
2116
|
+
|
|
2117
|
+
```typescript
|
|
2118
|
+
// Transform XML structure to include parent FacilityId in each item
|
|
2119
|
+
// Original: { ItemInventory: { FacilityId, Item: [...] } }
|
|
2120
|
+
// Result: [{ itemInventory: { FacilityId, SKU, Qty } }]
|
|
2121
|
+
const enrichedRecords = normalizedItems.map(item => ({
|
|
2122
|
+
itemInventory: {
|
|
2123
|
+
FacilityId: facilityId,
|
|
2124
|
+
...item
|
|
2125
|
+
}
|
|
2126
|
+
}));
|
|
2127
|
+
```
|
|
2128
|
+
|
|
2129
|
+
Future you (or your team) will thank you.
|
|
2130
|
+
|
|
2131
|
+
### 8. Parse First, Then Decide on Transformation
|
|
2132
|
+
|
|
2133
|
+
1. **Parse the file format**
|
|
2134
|
+
- Use the appropriate parser service
|
|
2135
|
+
- Log the parsed structure
|
|
2136
|
+
|
|
2137
|
+
2. **Inspect the parsed structure**
|
|
2138
|
+
- Understand the data hierarchy
|
|
2139
|
+
- Identify shared parent data
|
|
2140
|
+
- Check for array normalization needs
|
|
2141
|
+
|
|
2142
|
+
3. **Decide if transformation is needed**
|
|
2143
|
+
- Can you map directly?
|
|
2144
|
+
- Do you need to enrich child records with parent data?
|
|
2145
|
+
- Is the structure compatible with your mapping config?
|
|
2146
|
+
|
|
2147
|
+
### 9. Keep Transformation Minimal
|
|
2148
|
+
|
|
2149
|
+
- Only transform when necessary
|
|
2150
|
+
- Use resolvers for simple transformations when possible
|
|
2151
|
+
- Prefer code transformation for complex restructuring
|
|
2152
|
+
|
|
2153
|
+
### 10. Handle Edge Cases
|
|
2154
|
+
|
|
2155
|
+
- Single vs multiple elements (XML)
|
|
2156
|
+
- Missing fields
|
|
2157
|
+
- Null/undefined values
|
|
2158
|
+
- Empty arrays
|
|
2159
|
+
|
|
2160
|
+
---
|
|
2161
|
+
|
|
2162
|
+
## Common Mistakes to Avoid
|
|
2163
|
+
|
|
2164
|
+
### Mistake 1: Using File Format Syntax in Mapping Config
|
|
2165
|
+
|
|
2166
|
+
**WRONG:**
|
|
2167
|
+
```typescript
|
|
2168
|
+
// Trying to use XPath in mapping config
|
|
2169
|
+
const mappingConfig = {
|
|
2170
|
+
fields: {
|
|
2171
|
+
skuRef: { source: "//product/sku" } // This is XPath, not JavaScript
|
|
2172
|
+
}
|
|
2173
|
+
};
|
|
2174
|
+
```
|
|
2175
|
+
|
|
2176
|
+
**CORRECT:**
|
|
2177
|
+
```typescript
|
|
2178
|
+
// Use JavaScript object path
|
|
2179
|
+
const mappingConfig = {
|
|
2180
|
+
fields: {
|
|
2181
|
+
skuRef: { source: "product.sku" } // JavaScript property access
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
```
|
|
2185
|
+
|
|
2186
|
+
### Mistake 2: Mapping Before Transforming
|
|
2187
|
+
|
|
2188
|
+
**WRONG:**
|
|
2189
|
+
```typescript
|
|
2190
|
+
// Trying to access parent data in mapping config
|
|
2191
|
+
const mappingConfig = {
|
|
2192
|
+
fields: {
|
|
2193
|
+
// This won't work - FacilityId is at parent level
|
|
2194
|
+
locationRef: { source: "FacilityId" } // Item doesn't have FacilityId
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
|
|
2198
|
+
for (const item of items) {
|
|
2199
|
+
await mapper.map(item); // Fails - item doesn't have FacilityId
|
|
2200
|
+
}
|
|
2201
|
+
```
|
|
2202
|
+
|
|
2203
|
+
**CORRECT:**
|
|
2204
|
+
```typescript
|
|
2205
|
+
// Transform FIRST to add parent data
|
|
2206
|
+
const enrichedRecords = items.map(item => ({
|
|
2207
|
+
...item,
|
|
2208
|
+
FacilityId: facilityId // Add parent data
|
|
2209
|
+
}));
|
|
2210
|
+
|
|
2211
|
+
// NOW mapping can access FacilityId
|
|
2212
|
+
const mappingConfig = {
|
|
2213
|
+
fields: {
|
|
2214
|
+
locationRef: { source: "FacilityId" } // Now it exists!
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
```
|
|
2218
|
+
|
|
2219
|
+
### Mistake 3: Not Normalizing XML Arrays
|
|
2220
|
+
|
|
2221
|
+
**WRONG:**
|
|
2222
|
+
```typescript
|
|
2223
|
+
const items = parsed.products.product;
|
|
2224
|
+
|
|
2225
|
+
for (const item of items) { // Breaks if only one <product>!
|
|
2226
|
+
await mapper.map(item);
|
|
2227
|
+
}
|
|
2228
|
+
```
|
|
2229
|
+
|
|
2230
|
+
**CORRECT:**
|
|
2231
|
+
```typescript
|
|
2232
|
+
const items = parsed.products?.product;
|
|
2233
|
+
const records = Array.isArray(items) ? items : items ? [items] : [];
|
|
2234
|
+
|
|
2235
|
+
for (const record of records) { // Always works
|
|
2236
|
+
await mapper.map(record);
|
|
2237
|
+
}
|
|
2238
|
+
```
|
|
2239
|
+
|
|
2240
|
+
### Mistake 4: Forgetting @ Prefix for XML Attributes
|
|
2241
|
+
|
|
2242
|
+
**WRONG:**
|
|
2243
|
+
```typescript
|
|
2244
|
+
// XML: <item locationRef="LOC-A" skuRef="SKU-001">
|
|
2245
|
+
const mappingConfig = {
|
|
2246
|
+
fields: {
|
|
2247
|
+
locationRef: { source: "locationRef" }, // Missing @
|
|
2248
|
+
skuRef: { source: "skuRef" } // Missing @
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
```
|
|
2252
|
+
|
|
2253
|
+
**CORRECT:**
|
|
2254
|
+
```typescript
|
|
2255
|
+
// Parsed object has: { "@locationRef": "LOC-A", "@skuRef": "SKU-001" }
|
|
2256
|
+
const mappingConfig = {
|
|
2257
|
+
fields: {
|
|
2258
|
+
locationRef: { source: "@locationRef" }, // @ prefix required
|
|
2259
|
+
skuRef: { source: "@skuRef" }
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
```
|
|
2263
|
+
|
|
2264
|
+
### Mistake 5: Mapping Nested Source to Flat Target Without Extraction
|
|
2265
|
+
|
|
2266
|
+
**WRONG:**
|
|
2267
|
+
```typescript
|
|
2268
|
+
// Source: { warehouse: { inventory: { items: [...] } } }
|
|
2269
|
+
const items = parsed.warehouse.inventory.items;
|
|
2270
|
+
|
|
2271
|
+
// Mapping without extraction
|
|
2272
|
+
const mappingConfig = {
|
|
2273
|
+
fields: {
|
|
2274
|
+
skuRef: { source: "warehouse.inventory.items.sku" } // Path too long
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
```
|
|
2278
|
+
|
|
2279
|
+
**CORRECT:**
|
|
2280
|
+
```typescript
|
|
2281
|
+
// Extract the array first
|
|
2282
|
+
const items = parsed.warehouse.inventory.items;
|
|
2283
|
+
|
|
2284
|
+
// Now map each item directly
|
|
2285
|
+
const mappingConfig = {
|
|
2286
|
+
fields: {
|
|
2287
|
+
skuRef: { source: "sku" } // Clean path
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
for (const item of items) {
|
|
2292
|
+
await mapper.map(item);
|
|
2293
|
+
}
|
|
2294
|
+
```
|
|
2295
|
+
|
|
2296
|
+
### Mistake 6: Assuming Parsed Data Matches File Structure
|
|
2297
|
+
|
|
2298
|
+
**WRONG:**
|
|
2299
|
+
```typescript
|
|
2300
|
+
// CSV has columns: sku_id, quantity, location
|
|
2301
|
+
// Assuming mapping uses column positions
|
|
2302
|
+
const mappingConfig = {
|
|
2303
|
+
fields: {
|
|
2304
|
+
skuRef: { source: "columns[0]" } // WRONG! Not how it works
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
```
|
|
2308
|
+
|
|
2309
|
+
**CORRECT:**
|
|
2310
|
+
```typescript
|
|
2311
|
+
// CSV parser creates: { sku_id: "...", quantity: "...", location: "..." }
|
|
2312
|
+
const mappingConfig = {
|
|
2313
|
+
fields: {
|
|
2314
|
+
skuRef: { source: "sku_id" } // Use property name
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
```
|
|
2318
|
+
|
|
2319
|
+
---
|
|
2320
|
+
|
|
2321
|
+
## Related Documentation
|
|
2322
|
+
|
|
2323
|
+
- [Mapping Foundations](../auto-pagination/modules/auto-pagination-01-foundations.md) - Core mapping concepts
|
|
2324
|
+
- [Universal Mapper Guide](mapping-readme.md) - Complete mapping patterns
|
|
2325
|
+
- [SDK Resolvers](./resolvers/mapping-resolvers-readme.md) - Built-in field transformations
|
|
2326
|
+
- [XML Parser Guide](../parsers/modules/02-core-guides-parsers-04-xml-parser.md) - XML parsing details
|
|
2327
|
+
- [CSV Parser Guide](../parsers/modules/02-core-guides-parsers-02-csv-parser.md) - CSV parsing details
|
|
2328
|
+
- [JSON Parser Guide](../parsers/modules/02-core-guides-parsers-03-json-parser.md) - JSON parsing details
|
|
2329
|
+
|
|
2330
|
+
---
|
|
2331
|
+
|
|
2332
|
+
## FAQ
|
|
2333
|
+
|
|
2334
|
+
**Q: Do I need to know XPath/JSONPath/SQL to write mapping configs?**
|
|
2335
|
+
|
|
2336
|
+
A: No! You only need to know JavaScript object property access. Use dot notation for nested properties and `@` prefix for XML attributes.
|
|
2337
|
+
|
|
2338
|
+
**Q: Why does my XML mapping fail when there's only one element?**
|
|
2339
|
+
|
|
2340
|
+
A: XML parsers return an object for a single element and an array for multiple elements. Always normalize with: `Array.isArray(items) ? items : items ? [items] : []`
|
|
2341
|
+
|
|
2342
|
+
**Q: Can I use the same mapping config for CSV, JSON, and XML?**
|
|
2343
|
+
|
|
2344
|
+
A: Only if they produce the same JavaScript object structure after parsing. Usually, you need different configs because each format has different property names.
|
|
2345
|
+
|
|
2346
|
+
**Q: How do I map parent data to child records?**
|
|
2347
|
+
|
|
2348
|
+
A: Transform the data BEFORE mapping. Extract parent data, then wrap each child record with the parent data. See the [InventoryStatus example](#real-world-example-inventorystatus).
|
|
2349
|
+
|
|
2350
|
+
**Q: What's the difference between `"source": "sku"` and `"source": "@sku"`?**
|
|
2351
|
+
|
|
2352
|
+
A: `"sku"` is a child element in XML (or object property). `"@sku"` is an XML attribute. The XML parser adds the `@` prefix to attributes in the JavaScript object.
|
|
2353
|
+
|
|
2354
|
+
**Q: Can I access nested properties with bracket notation?**
|
|
2355
|
+
|
|
2356
|
+
A: Use dot notation for paths: `"source": "product.details.sku"`. The mapper handles the property access internally.
|
|
2357
|
+
|
|
2358
|
+
**Q: How do I debug mapping errors?**
|
|
2359
|
+
|
|
2360
|
+
A: Log the exact object you pass to `mapper.map()` with `JSON.stringify(object, null, 2)`. Compare property names in the log to your mapping config `source` paths.
|
|
2361
|
+
|
|
2362
|
+
---
|
|
2363
|
+
|
|
2364
|
+
**Last Updated:** 2025-11-03
|
|
2365
|
+
|
|
2366
|
+
**Related Guides:**
|
|
2367
|
+
- [Universal Mapping Guide](mapping-readme.md)
|
|
2368
|
+
- [SDK Resolvers](./resolvers/mapping-resolvers-readme.md)
|
|
2369
|
+
- [Parsers Guide](../parsers/parsers-readme.md)
|