#
tokens: 49714/50000 8/825 files (page 29/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 29 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .DS_Store
├── .github
│   ├── dependabot.yml
│   ├── instructions
│   │   ├── mcp-node-tests.instructions.md
│   │   └── mcp-yml-tests.instructions.md
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── documentation.yml
│   │   ├── feature_request.yml
│   │   └── question.yml
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bug_fix.md
│   │   ├── documentation.md
│   │   └── new_tool.md
│   ├── pull_request_template.md
│   └── workflows
│       ├── ci.yml
│       ├── deploy-pages.yml
│       ├── publish.yml
│       └── update-docs.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── aegis.config.docs-only.json
├── aegis.config.json
├── aegis.config.with-dw.json
├── AGENTS.md
├── ai-instructions
│   ├── claude-desktop
│   │   └── claude_custom_instructions.md
│   ├── cursor
│   │   └── .cursor
│   │       └── rules
│   │           ├── debugging-workflows.mdc
│   │           ├── hooks-development.mdc
│   │           ├── isml-templates.mdc
│   │           ├── job-framework.mdc
│   │           ├── performance-optimization.mdc
│   │           ├── scapi-endpoints.mdc
│   │           ├── security-patterns.mdc
│   │           ├── sfcc-development.mdc
│   │           ├── sfra-controllers.mdc
│   │           ├── sfra-models.mdc
│   │           ├── system-objects.mdc
│   │           └── testing-patterns.mdc
│   └── github-copilot
│       └── copilot-instructions.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docs
│   ├── best-practices
│   │   ├── cartridge_creation.md
│   │   ├── isml_templates.md
│   │   ├── job_framework.md
│   │   ├── localserviceregistry.md
│   │   ├── ocapi_hooks.md
│   │   ├── performance.md
│   │   ├── scapi_custom_endpoint.md
│   │   ├── scapi_hooks.md
│   │   ├── security.md
│   │   ├── sfra_client_side_js.md
│   │   ├── sfra_controllers.md
│   │   ├── sfra_models.md
│   │   └── sfra_scss.md
│   ├── dw_campaign
│   │   ├── ABTest.md
│   │   ├── ABTestMgr.md
│   │   ├── ABTestSegment.md
│   │   ├── AmountDiscount.md
│   │   ├── ApproachingDiscount.md
│   │   ├── BonusChoiceDiscount.md
│   │   ├── BonusDiscount.md
│   │   ├── Campaign.md
│   │   ├── CampaignMgr.md
│   │   ├── CampaignStatusCodes.md
│   │   ├── Coupon.md
│   │   ├── CouponMgr.md
│   │   ├── CouponRedemption.md
│   │   ├── CouponStatusCodes.md
│   │   ├── Discount.md
│   │   ├── DiscountPlan.md
│   │   ├── FixedPriceDiscount.md
│   │   ├── FixedPriceShippingDiscount.md
│   │   ├── FreeDiscount.md
│   │   ├── FreeShippingDiscount.md
│   │   ├── PercentageDiscount.md
│   │   ├── PercentageOptionDiscount.md
│   │   ├── PriceBookPriceDiscount.md
│   │   ├── Promotion.md
│   │   ├── PromotionMgr.md
│   │   ├── PromotionPlan.md
│   │   ├── SlotContent.md
│   │   ├── SourceCodeGroup.md
│   │   ├── SourceCodeInfo.md
│   │   ├── SourceCodeStatusCodes.md
│   │   └── TotalFixedPriceDiscount.md
│   ├── dw_catalog
│   │   ├── Catalog.md
│   │   ├── CatalogMgr.md
│   │   ├── Category.md
│   │   ├── CategoryAssignment.md
│   │   ├── CategoryLink.md
│   │   ├── PriceBook.md
│   │   ├── PriceBookMgr.md
│   │   ├── Product.md
│   │   ├── ProductActiveData.md
│   │   ├── ProductAttributeModel.md
│   │   ├── ProductAvailabilityLevels.md
│   │   ├── ProductAvailabilityModel.md
│   │   ├── ProductInventoryList.md
│   │   ├── ProductInventoryMgr.md
│   │   ├── ProductInventoryRecord.md
│   │   ├── ProductLink.md
│   │   ├── ProductMgr.md
│   │   ├── ProductOption.md
│   │   ├── ProductOptionModel.md
│   │   ├── ProductOptionValue.md
│   │   ├── ProductPriceInfo.md
│   │   ├── ProductPriceModel.md
│   │   ├── ProductPriceTable.md
│   │   ├── ProductSearchHit.md
│   │   ├── ProductSearchModel.md
│   │   ├── ProductSearchRefinementDefinition.md
│   │   ├── ProductSearchRefinements.md
│   │   ├── ProductSearchRefinementValue.md
│   │   ├── ProductVariationAttribute.md
│   │   ├── ProductVariationAttributeValue.md
│   │   ├── ProductVariationModel.md
│   │   ├── Recommendation.md
│   │   ├── SearchModel.md
│   │   ├── SearchRefinementDefinition.md
│   │   ├── SearchRefinements.md
│   │   ├── SearchRefinementValue.md
│   │   ├── SortingOption.md
│   │   ├── SortingRule.md
│   │   ├── Store.md
│   │   ├── StoreGroup.md
│   │   ├── StoreInventoryFilter.md
│   │   ├── StoreInventoryFilterValue.md
│   │   ├── StoreMgr.md
│   │   ├── Variant.md
│   │   └── VariationGroup.md
│   ├── dw_content
│   │   ├── Content.md
│   │   ├── ContentMgr.md
│   │   ├── ContentSearchModel.md
│   │   ├── ContentSearchRefinementDefinition.md
│   │   ├── ContentSearchRefinements.md
│   │   ├── ContentSearchRefinementValue.md
│   │   ├── Folder.md
│   │   ├── Library.md
│   │   ├── MarkupText.md
│   │   └── MediaFile.md
│   ├── dw_crypto
│   │   ├── CertificateRef.md
│   │   ├── CertificateUtils.md
│   │   ├── Cipher.md
│   │   ├── Encoding.md
│   │   ├── JWE.md
│   │   ├── JWEHeader.md
│   │   ├── JWS.md
│   │   ├── JWSHeader.md
│   │   ├── KeyRef.md
│   │   ├── Mac.md
│   │   ├── MessageDigest.md
│   │   ├── SecureRandom.md
│   │   ├── Signature.md
│   │   ├── WeakCipher.md
│   │   ├── WeakMac.md
│   │   ├── WeakMessageDigest.md
│   │   ├── WeakSignature.md
│   │   └── X509Certificate.md
│   ├── dw_customer
│   │   ├── AddressBook.md
│   │   ├── AgentUserMgr.md
│   │   ├── AgentUserStatusCodes.md
│   │   ├── AuthenticationStatus.md
│   │   ├── Credentials.md
│   │   ├── Customer.md
│   │   ├── CustomerActiveData.md
│   │   ├── CustomerAddress.md
│   │   ├── CustomerCDPData.md
│   │   ├── CustomerContextMgr.md
│   │   ├── CustomerGroup.md
│   │   ├── CustomerList.md
│   │   ├── CustomerMgr.md
│   │   ├── CustomerPasswordConstraints.md
│   │   ├── CustomerPaymentInstrument.md
│   │   ├── CustomerStatusCodes.md
│   │   ├── EncryptedObject.md
│   │   ├── ExternalProfile.md
│   │   ├── OrderHistory.md
│   │   ├── ProductList.md
│   │   ├── ProductListItem.md
│   │   ├── ProductListItemPurchase.md
│   │   ├── ProductListMgr.md
│   │   ├── ProductListRegistrant.md
│   │   ├── Profile.md
│   │   └── Wallet.md
│   ├── dw_extensions.applepay
│   │   ├── ApplePayHookResult.md
│   │   └── ApplePayHooks.md
│   ├── dw_extensions.facebook
│   │   ├── FacebookFeedHooks.md
│   │   └── FacebookProduct.md
│   ├── dw_extensions.paymentrequest
│   │   ├── PaymentRequestHookResult.md
│   │   └── PaymentRequestHooks.md
│   ├── dw_extensions.payments
│   │   ├── SalesforceBancontactPaymentDetails.md
│   │   ├── SalesforceCardPaymentDetails.md
│   │   ├── SalesforceEpsPaymentDetails.md
│   │   ├── SalesforceIdealPaymentDetails.md
│   │   ├── SalesforceKlarnaPaymentDetails.md
│   │   ├── SalesforcePaymentDetails.md
│   │   ├── SalesforcePaymentIntent.md
│   │   ├── SalesforcePaymentMethod.md
│   │   ├── SalesforcePaymentRequest.md
│   │   ├── SalesforcePaymentsHooks.md
│   │   ├── SalesforcePaymentsMgr.md
│   │   ├── SalesforcePaymentsSiteConfiguration.md
│   │   ├── SalesforcePayPalOrder.md
│   │   ├── SalesforcePayPalOrderAddress.md
│   │   ├── SalesforcePayPalOrderPayer.md
│   │   ├── SalesforcePayPalPaymentDetails.md
│   │   ├── SalesforceSepaDebitPaymentDetails.md
│   │   └── SalesforceVenmoPaymentDetails.md
│   ├── dw_extensions.pinterest
│   │   ├── PinterestAvailability.md
│   │   ├── PinterestFeedHooks.md
│   │   ├── PinterestOrder.md
│   │   ├── PinterestOrderHooks.md
│   │   └── PinterestProduct.md
│   ├── dw_io
│   │   ├── CSVStreamReader.md
│   │   ├── CSVStreamWriter.md
│   │   ├── File.md
│   │   ├── FileReader.md
│   │   ├── FileWriter.md
│   │   ├── InputStream.md
│   │   ├── OutputStream.md
│   │   ├── PrintWriter.md
│   │   ├── RandomAccessFileReader.md
│   │   ├── Reader.md
│   │   ├── StringWriter.md
│   │   ├── Writer.md
│   │   ├── XMLIndentingStreamWriter.md
│   │   ├── XMLStreamConstants.md
│   │   ├── XMLStreamReader.md
│   │   └── XMLStreamWriter.md
│   ├── dw_job
│   │   ├── JobExecution.md
│   │   └── JobStepExecution.md
│   ├── dw_net
│   │   ├── FTPClient.md
│   │   ├── FTPFileInfo.md
│   │   ├── HTTPClient.md
│   │   ├── HTTPRequestPart.md
│   │   ├── Mail.md
│   │   ├── SFTPClient.md
│   │   ├── SFTPFileInfo.md
│   │   ├── WebDAVClient.md
│   │   └── WebDAVFileInfo.md
│   ├── dw_object
│   │   ├── ActiveData.md
│   │   ├── CustomAttributes.md
│   │   ├── CustomObject.md
│   │   ├── CustomObjectMgr.md
│   │   ├── Extensible.md
│   │   ├── ExtensibleObject.md
│   │   ├── Note.md
│   │   ├── ObjectAttributeDefinition.md
│   │   ├── ObjectAttributeGroup.md
│   │   ├── ObjectAttributeValueDefinition.md
│   │   ├── ObjectTypeDefinition.md
│   │   ├── PersistentObject.md
│   │   ├── SimpleExtensible.md
│   │   └── SystemObjectMgr.md
│   ├── dw_order
│   │   ├── AbstractItem.md
│   │   ├── AbstractItemCtnr.md
│   │   ├── Appeasement.md
│   │   ├── AppeasementItem.md
│   │   ├── Basket.md
│   │   ├── BasketMgr.md
│   │   ├── BonusDiscountLineItem.md
│   │   ├── CouponLineItem.md
│   │   ├── CreateAgentBasketLimitExceededException.md
│   │   ├── CreateBasketFromOrderException.md
│   │   ├── CreateCouponLineItemException.md
│   │   ├── CreateOrderException.md
│   │   ├── CreateTemporaryBasketLimitExceededException.md
│   │   ├── GiftCertificate.md
│   │   ├── GiftCertificateLineItem.md
│   │   ├── GiftCertificateMgr.md
│   │   ├── GiftCertificateStatusCodes.md
│   │   ├── Invoice.md
│   │   ├── InvoiceItem.md
│   │   ├── LineItem.md
│   │   ├── LineItemCtnr.md
│   │   ├── Order.md
│   │   ├── OrderAddress.md
│   │   ├── OrderItem.md
│   │   ├── OrderMgr.md
│   │   ├── OrderPaymentInstrument.md
│   │   ├── OrderProcessStatusCodes.md
│   │   ├── PaymentCard.md
│   │   ├── PaymentInstrument.md
│   │   ├── PaymentMethod.md
│   │   ├── PaymentMgr.md
│   │   ├── PaymentProcessor.md
│   │   ├── PaymentStatusCodes.md
│   │   ├── PaymentTransaction.md
│   │   ├── PriceAdjustment.md
│   │   ├── PriceAdjustmentLimitTypes.md
│   │   ├── ProductLineItem.md
│   │   ├── ProductShippingCost.md
│   │   ├── ProductShippingLineItem.md
│   │   ├── ProductShippingModel.md
│   │   ├── Return.md
│   │   ├── ReturnCase.md
│   │   ├── ReturnCaseItem.md
│   │   ├── ReturnItem.md
│   │   ├── Shipment.md
│   │   ├── ShipmentShippingCost.md
│   │   ├── ShipmentShippingModel.md
│   │   ├── ShippingLineItem.md
│   │   ├── ShippingLocation.md
│   │   ├── ShippingMethod.md
│   │   ├── ShippingMgr.md
│   │   ├── ShippingOrder.md
│   │   ├── ShippingOrderItem.md
│   │   ├── SumItem.md
│   │   ├── TaxGroup.md
│   │   ├── TaxItem.md
│   │   ├── TaxMgr.md
│   │   ├── TrackingInfo.md
│   │   └── TrackingRef.md
│   ├── dw_order.hooks
│   │   ├── CalculateHooks.md
│   │   ├── OrderHooks.md
│   │   ├── PaymentHooks.md
│   │   ├── ReturnHooks.md
│   │   └── ShippingOrderHooks.md
│   ├── dw_rpc
│   │   ├── SOAPUtil.md
│   │   ├── Stub.md
│   │   └── WebReference.md
│   ├── dw_suggest
│   │   ├── BrandSuggestions.md
│   │   ├── CategorySuggestions.md
│   │   ├── ContentSuggestions.md
│   │   ├── CustomSuggestions.md
│   │   ├── ProductSuggestions.md
│   │   ├── SearchPhraseSuggestions.md
│   │   ├── SuggestedCategory.md
│   │   ├── SuggestedContent.md
│   │   ├── SuggestedPhrase.md
│   │   ├── SuggestedProduct.md
│   │   ├── SuggestedTerm.md
│   │   ├── SuggestedTerms.md
│   │   ├── Suggestions.md
│   │   └── SuggestModel.md
│   ├── dw_svc
│   │   ├── FTPService.md
│   │   ├── FTPServiceDefinition.md
│   │   ├── HTTPFormService.md
│   │   ├── HTTPFormServiceDefinition.md
│   │   ├── HTTPService.md
│   │   ├── HTTPServiceDefinition.md
│   │   ├── LocalServiceRegistry.md
│   │   ├── Result.md
│   │   ├── Service.md
│   │   ├── ServiceCallback.md
│   │   ├── ServiceConfig.md
│   │   ├── ServiceCredential.md
│   │   ├── ServiceDefinition.md
│   │   ├── ServiceProfile.md
│   │   ├── ServiceRegistry.md
│   │   ├── SOAPService.md
│   │   └── SOAPServiceDefinition.md
│   ├── dw_system
│   │   ├── AgentUserStatusCodes.md
│   │   ├── Cache.md
│   │   ├── CacheMgr.md
│   │   ├── HookMgr.md
│   │   ├── InternalObject.md
│   │   ├── JobProcessMonitor.md
│   │   ├── Log.md
│   │   ├── Logger.md
│   │   ├── LogNDC.md
│   │   ├── OrganizationPreferences.md
│   │   ├── Pipeline.md
│   │   ├── PipelineDictionary.md
│   │   ├── RemoteInclude.md
│   │   ├── Request.md
│   │   ├── RequestHooks.md
│   │   ├── Response.md
│   │   ├── RESTErrorResponse.md
│   │   ├── RESTResponseMgr.md
│   │   ├── RESTSuccessResponse.md
│   │   ├── SearchStatus.md
│   │   ├── Session.md
│   │   ├── Site.md
│   │   ├── SitePreferences.md
│   │   ├── Status.md
│   │   ├── StatusItem.md
│   │   ├── System.md
│   │   └── Transaction.md
│   ├── dw_util
│   │   ├── ArrayList.md
│   │   ├── Assert.md
│   │   ├── BigInteger.md
│   │   ├── Bytes.md
│   │   ├── Calendar.md
│   │   ├── Collection.md
│   │   ├── Currency.md
│   │   ├── DateUtils.md
│   │   ├── Decimal.md
│   │   ├── FilteringCollection.md
│   │   ├── Geolocation.md
│   │   ├── HashMap.md
│   │   ├── HashSet.md
│   │   ├── Iterator.md
│   │   ├── LinkedHashMap.md
│   │   ├── LinkedHashSet.md
│   │   ├── List.md
│   │   ├── Locale.md
│   │   ├── Map.md
│   │   ├── MapEntry.md
│   │   ├── MappingKey.md
│   │   ├── MappingMgr.md
│   │   ├── PropertyComparator.md
│   │   ├── SecureEncoder.md
│   │   ├── SecureFilter.md
│   │   ├── SeekableIterator.md
│   │   ├── Set.md
│   │   ├── SortedMap.md
│   │   ├── SortedSet.md
│   │   ├── StringUtils.md
│   │   ├── Template.md
│   │   └── UUIDUtils.md
│   ├── dw_value
│   │   ├── EnumValue.md
│   │   ├── MimeEncodedText.md
│   │   ├── Money.md
│   │   └── Quantity.md
│   ├── dw_web
│   │   ├── ClickStream.md
│   │   ├── ClickStreamEntry.md
│   │   ├── Cookie.md
│   │   ├── Cookies.md
│   │   ├── CSRFProtection.md
│   │   ├── Form.md
│   │   ├── FormAction.md
│   │   ├── FormElement.md
│   │   ├── FormElementValidationResult.md
│   │   ├── FormField.md
│   │   ├── FormFieldOption.md
│   │   ├── FormFieldOptions.md
│   │   ├── FormGroup.md
│   │   ├── FormList.md
│   │   ├── FormListItem.md
│   │   ├── Forms.md
│   │   ├── HttpParameter.md
│   │   ├── HttpParameterMap.md
│   │   ├── LoopIterator.md
│   │   ├── PageMetaData.md
│   │   ├── PageMetaTag.md
│   │   ├── PagingModel.md
│   │   ├── Resource.md
│   │   ├── URL.md
│   │   ├── URLAction.md
│   │   ├── URLParameter.md
│   │   ├── URLRedirect.md
│   │   ├── URLRedirectMgr.md
│   │   └── URLUtils.md
│   ├── sfra
│   │   ├── account.md
│   │   ├── address.md
│   │   ├── billing.md
│   │   ├── cart.md
│   │   ├── categories.md
│   │   ├── content.md
│   │   ├── locale.md
│   │   ├── order.md
│   │   ├── payment.md
│   │   ├── price-default.md
│   │   ├── price-range.md
│   │   ├── price-tiered.md
│   │   ├── product-bundle.md
│   │   ├── product-full.md
│   │   ├── product-line-items.md
│   │   ├── product-search.md
│   │   ├── product-tile.md
│   │   ├── querystring.md
│   │   ├── render.md
│   │   ├── request.md
│   │   ├── response.md
│   │   ├── server.md
│   │   ├── shipping.md
│   │   ├── store.md
│   │   ├── stores.md
│   │   └── totals.md
│   └── TopLevel
│       ├── APIException.md
│       ├── arguments.md
│       ├── Array.md
│       ├── ArrayBuffer.md
│       ├── BigInt.md
│       ├── Boolean.md
│       ├── ConversionError.md
│       ├── DataView.md
│       ├── Date.md
│       ├── Error.md
│       ├── ES6Iterator.md
│       ├── EvalError.md
│       ├── Fault.md
│       ├── Float32Array.md
│       ├── Float64Array.md
│       ├── Function.md
│       ├── Generator.md
│       ├── global.md
│       ├── Int16Array.md
│       ├── Int32Array.md
│       ├── Int8Array.md
│       ├── InternalError.md
│       ├── IOError.md
│       ├── Iterable.md
│       ├── Iterator.md
│       ├── JSON.md
│       ├── Map.md
│       ├── Math.md
│       ├── Module.md
│       ├── Namespace.md
│       ├── Number.md
│       ├── Object.md
│       ├── QName.md
│       ├── RangeError.md
│       ├── ReferenceError.md
│       ├── RegExp.md
│       ├── Set.md
│       ├── StopIteration.md
│       ├── String.md
│       ├── Symbol.md
│       ├── SyntaxError.md
│       ├── SystemError.md
│       ├── TypeError.md
│       ├── Uint16Array.md
│       ├── Uint32Array.md
│       ├── Uint8Array.md
│       ├── Uint8ClampedArray.md
│       ├── URIError.md
│       ├── WeakMap.md
│       ├── WeakSet.md
│       ├── XML.md
│       ├── XMLList.md
│       └── XMLStreamError.md
├── docs-site
│   ├── .gitignore
│   ├── App.tsx
│   ├── components
│   │   ├── Badge.tsx
│   │   ├── BreadcrumbSchema.tsx
│   │   ├── CodeBlock.tsx
│   │   ├── Collapsible.tsx
│   │   ├── ConfigBuilder.tsx
│   │   ├── ConfigHero.tsx
│   │   ├── ConfigModeTabs.tsx
│   │   ├── icons.tsx
│   │   ├── Layout.tsx
│   │   ├── LightCodeContainer.tsx
│   │   ├── NewcomerCTA.tsx
│   │   ├── NextStepsStrip.tsx
│   │   ├── OnThisPage.tsx
│   │   ├── Search.tsx
│   │   ├── SEO.tsx
│   │   ├── Sidebar.tsx
│   │   ├── StructuredData.tsx
│   │   ├── ToolCard.tsx
│   │   ├── ToolFilters.tsx
│   │   ├── Typography.tsx
│   │   └── VersionBadge.tsx
│   ├── constants.tsx
│   ├── index.html
│   ├── main.tsx
│   ├── metadata.json
│   ├── package-lock.json
│   ├── package.json
│   ├── pages
│   │   ├── AIInterfacesPage.tsx
│   │   ├── ConfigurationPage.tsx
│   │   ├── DevelopmentPage.tsx
│   │   ├── ExamplesPage.tsx
│   │   ├── FeaturesPage.tsx
│   │   ├── HomePage.tsx
│   │   ├── SecurityPage.tsx
│   │   ├── ToolsPage.tsx
│   │   └── TroubleshootingPage.tsx
│   ├── postcss.config.js
│   ├── public
│   │   ├── .well-known
│   │   │   └── security.txt
│   │   ├── 404.html
│   │   ├── android-chrome-192x192.png
│   │   ├── android-chrome-512x512.png
│   │   ├── apple-touch-icon.png
│   │   ├── explain-product-pricing-methods-no-mcp.png
│   │   ├── explain-product-pricing-methods.png
│   │   ├── favicon-16x16.png
│   │   ├── favicon-32x32.png
│   │   ├── favicon.ico
│   │   ├── llms.txt
│   │   ├── robots.txt
│   │   ├── site.webmanifest
│   │   └── sitemap.xml
│   ├── README.md
│   ├── scripts
│   │   ├── generate-search-index.js
│   │   ├── generate-sitemap.js
│   │   └── search-dev.js
│   ├── src
│   │   └── styles
│   │       ├── input.css
│   │       └── prism-theme.css
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   ├── types.ts
│   ├── utils
│   │   ├── search.ts
│   │   └── toolsData.ts
│   └── vite.config.ts
├── eslint.config.js
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── convert-docs.js
├── SECURITY.md
├── server.json
├── src
│   ├── clients
│   │   ├── base
│   │   │   ├── http-client.ts
│   │   │   ├── oauth-token.ts
│   │   │   └── ocapi-auth-client.ts
│   │   ├── best-practices-client.ts
│   │   ├── cartridge-generation-client.ts
│   │   ├── docs
│   │   │   ├── class-content-parser.ts
│   │   │   ├── class-name-resolver.ts
│   │   │   ├── documentation-scanner.ts
│   │   │   ├── index.ts
│   │   │   └── referenced-types-extractor.ts
│   │   ├── docs-client.ts
│   │   ├── log-client.ts
│   │   ├── logs
│   │   │   ├── index.ts
│   │   │   ├── log-analyzer.ts
│   │   │   ├── log-client.ts
│   │   │   ├── log-constants.ts
│   │   │   ├── log-file-discovery.ts
│   │   │   ├── log-file-reader.ts
│   │   │   ├── log-formatter.ts
│   │   │   ├── log-processor.ts
│   │   │   ├── log-types.ts
│   │   │   └── webdav-client-manager.ts
│   │   ├── ocapi
│   │   │   ├── code-versions-client.ts
│   │   │   ├── site-preferences-client.ts
│   │   │   └── system-objects-client.ts
│   │   ├── ocapi-client.ts
│   │   └── sfra-client.ts
│   ├── config
│   │   ├── configuration-factory.ts
│   │   └── dw-json-loader.ts
│   ├── core
│   │   ├── handlers
│   │   │   ├── abstract-log-tool-handler.ts
│   │   │   ├── base-handler.ts
│   │   │   ├── best-practices-handler.ts
│   │   │   ├── cartridge-handler.ts
│   │   │   ├── client-factory.ts
│   │   │   ├── code-version-handler.ts
│   │   │   ├── docs-handler.ts
│   │   │   ├── job-log-handler.ts
│   │   │   ├── job-log-tool-config.ts
│   │   │   ├── log-handler.ts
│   │   │   ├── log-tool-config.ts
│   │   │   ├── sfra-handler.ts
│   │   │   ├── system-object-handler.ts
│   │   │   └── validation-helpers.ts
│   │   ├── server.ts
│   │   └── tool-definitions.ts
│   ├── index.ts
│   ├── main.ts
│   ├── services
│   │   ├── file-system-service.ts
│   │   ├── index.ts
│   │   └── path-service.ts
│   ├── tool-configs
│   │   ├── best-practices-tool-config.ts
│   │   ├── cartridge-tool-config.ts
│   │   ├── code-version-tool-config.ts
│   │   ├── docs-tool-config.ts
│   │   ├── job-log-tool-config.ts
│   │   ├── log-tool-config.ts
│   │   ├── sfra-tool-config.ts
│   │   └── system-object-tool-config.ts
│   ├── types
│   │   └── types.ts
│   └── utils
│       ├── cache.ts
│       ├── job-log-tool-config.ts
│       ├── job-log-utils.ts
│       ├── log-cache.ts
│       ├── log-tool-config.ts
│       ├── log-tool-constants.ts
│       ├── log-tool-utils.ts
│       ├── logger.ts
│       ├── ocapi-url-builder.ts
│       ├── path-resolver.ts
│       ├── query-builder.ts
│       ├── utils.ts
│       └── validator.ts
├── tests
│   ├── __mocks__
│   │   ├── docs-client.ts
│   │   ├── src
│   │   │   └── clients
│   │   │       └── base
│   │   │           └── http-client.js
│   │   └── webdav.js
│   ├── base-handler.test.ts
│   ├── base-http-client.test.ts
│   ├── best-practices-handler.test.ts
│   ├── cache.test.ts
│   ├── cartridge-handler.test.ts
│   ├── class-content-parser.test.ts
│   ├── class-name-resolver.test.ts
│   ├── client-factory.test.ts
│   ├── code-version-handler.test.ts
│   ├── code-versions-client.test.ts
│   ├── config.test.ts
│   ├── configuration-factory.test.ts
│   ├── docs-handler.test.ts
│   ├── documentation-scanner.test.ts
│   ├── file-system-service.test.ts
│   ├── job-log-handler.test.ts
│   ├── job-log-utils.test.ts
│   ├── log-client.test.ts
│   ├── log-handler.test.ts
│   ├── log-processor.test.ts
│   ├── logger.test.ts
│   ├── mcp
│   │   ├── AGENTS.md
│   │   ├── node
│   │   │   ├── activate-code-version-advanced.full-mode.programmatic.test.js
│   │   │   ├── code-versions.full-mode.programmatic.test.js
│   │   │   ├── generate-cartridge-structure.docs-only.programmatic.test.js
│   │   │   ├── get-available-best-practice-guides.docs-only.programmatic.test.js
│   │   │   ├── get-available-sfra-documents.programmatic.test.js
│   │   │   ├── get-best-practice-guide.docs-only.programmatic.test.js
│   │   │   ├── get-hook-reference.docs-only.programmatic.test.js
│   │   │   ├── get-job-execution-summary.full-mode.programmatic.test.js
│   │   │   ├── get-job-log-entries.full-mode.programmatic.test.js
│   │   │   ├── get-latest-debug.full-mode.programmatic.test.js
│   │   │   ├── get-latest-error.full-mode.programmatic.test.js
│   │   │   ├── get-latest-info.full-mode.programmatic.test.js
│   │   │   ├── get-latest-job-log-files.full-mode.programmatic.test.js
│   │   │   ├── get-latest-warn.full-mode.programmatic.test.js
│   │   │   ├── get-log-file-contents.full-mode.programmatic.test.js
│   │   │   ├── get-sfcc-class-documentation.docs-only.programmatic.test.js
│   │   │   ├── get-sfcc-class-info.docs-only.programmatic.test.js
│   │   │   ├── get-sfra-categories.docs-only.programmatic.test.js
│   │   │   ├── get-sfra-document.programmatic.test.js
│   │   │   ├── get-sfra-documents-by-category.docs-only.programmatic.test.js
│   │   │   ├── get-system-object-definition.full-mode.programmatic.test.js
│   │   │   ├── get-system-object-definitions.docs-only.programmatic.test.js
│   │   │   ├── get-system-object-definitions.full-mode.programmatic.test.js
│   │   │   ├── list-log-files.full-mode.programmatic.test.js
│   │   │   ├── list-sfcc-classes.docs-only.programmatic.test.js
│   │   │   ├── search-best-practices.docs-only.programmatic.test.js
│   │   │   ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js
│   │   │   ├── search-job-logs-by-name.full-mode.programmatic.test.js
│   │   │   ├── search-job-logs.full-mode.programmatic.test.js
│   │   │   ├── search-logs.full-mode.programmatic.test.js
│   │   │   ├── search-sfcc-classes.docs-only.programmatic.test.js
│   │   │   ├── search-sfcc-methods.docs-only.programmatic.test.js
│   │   │   ├── search-sfra-documentation.docs-only.programmatic.test.js
│   │   │   ├── search-site-preferences.full-mode.programmatic.test.js
│   │   │   ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js
│   │   │   ├── search-system-object-attribute-groups.full-mode.programmatic.test.js
│   │   │   ├── summarize-logs.full-mode.programmatic.test.js
│   │   │   ├── tools.docs-only.programmatic.test.js
│   │   │   └── tools.full-mode.programmatic.test.js
│   │   ├── README.md
│   │   ├── test-fixtures
│   │   │   └── dw.json
│   │   └── yaml
│   │       ├── activate-code-version.docs-only.test.mcp.yml
│   │       ├── activate-code-version.full-mode.test.mcp.yml
│   │       ├── get_latest_error.test.mcp.yml
│   │       ├── get-available-best-practice-guides.docs-only.test.mcp.yml
│   │       ├── get-available-best-practice-guides.full-mode.test.mcp.yml
│   │       ├── get-available-sfra-documents.docs-only.test.mcp.yml
│   │       ├── get-available-sfra-documents.full-mode.test.mcp.yml
│   │       ├── get-best-practice-guide.docs-only.test.mcp.yml
│   │       ├── get-best-practice-guide.full-mode.test.mcp.yml
│   │       ├── get-code-versions.docs-only.test.mcp.yml
│   │       ├── get-code-versions.full-mode.test.mcp.yml
│   │       ├── get-hook-reference.docs-only.test.mcp.yml
│   │       ├── get-hook-reference.full-mode.test.mcp.yml
│   │       ├── get-job-execution-summary.full-mode.test.mcp.yml
│   │       ├── get-job-log-entries.full-mode.test.mcp.yml
│   │       ├── get-latest-debug.full-mode.test.mcp.yml
│   │       ├── get-latest-error.full-mode.test.mcp.yml
│   │       ├── get-latest-info.full-mode.test.mcp.yml
│   │       ├── get-latest-job-log-files.full-mode.test.mcp.yml
│   │       ├── get-latest-warn.full-mode.test.mcp.yml
│   │       ├── get-log-file-contents.full-mode.test.mcp.yml
│   │       ├── get-sfcc-class-documentation.docs-only.test.mcp.yml
│   │       ├── get-sfcc-class-documentation.full-mode.test.mcp.yml
│   │       ├── get-sfcc-class-info.docs-only.test.mcp.yml
│   │       ├── get-sfcc-class-info.full-mode.test.mcp.yml
│   │       ├── get-sfra-categories.docs-only.test.mcp.yml
│   │       ├── get-sfra-categories.full-mode.test.mcp.yml
│   │       ├── get-sfra-document.docs-only.test.mcp.yml
│   │       ├── get-sfra-document.full-mode.test.mcp.yml
│   │       ├── get-sfra-documents-by-category.docs-only.test.mcp.yml
│   │       ├── get-sfra-documents-by-category.full-mode.test.mcp.yml
│   │       ├── get-system-object-definition.docs-only.test.mcp.yml
│   │       ├── get-system-object-definition.full-mode.test.mcp.yml
│   │       ├── get-system-object-definitions.docs-only.test.mcp.yml
│   │       ├── get-system-object-definitions.full-mode.test.mcp.yml
│   │       ├── list-log-files.full-mode.test.mcp.yml
│   │       ├── list-sfcc-classes.docs-only.test.mcp.yml
│   │       ├── list-sfcc-classes.full-mode.test.mcp.yml
│   │       ├── search-best-practices.docs-only.test.mcp.yml
│   │       ├── search-best-practices.full-mode.test.mcp.yml
│   │       ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml
│   │       ├── search-custom-object-attribute-definitions.test.mcp.yml
│   │       ├── search-job-logs-by-name.full-mode.test.mcp.yml
│   │       ├── search-job-logs.full-mode.test.mcp.yml
│   │       ├── search-logs.full-mode.test.mcp.yml
│   │       ├── search-sfcc-classes.docs-only.test.mcp.yml
│   │       ├── search-sfcc-classes.full-mode.test.mcp.yml
│   │       ├── search-sfcc-methods.docs-only.test.mcp.yml
│   │       ├── search-sfcc-methods.full-mode.test.mcp.yml
│   │       ├── search-sfra-documentation.docs-only.test.mcp.yml
│   │       ├── search-sfra-documentation.full-mode.test.mcp.yml
│   │       ├── search-site-preferences.docs-only.test.mcp.yml
│   │       ├── search-site-preferences.full-mode.test.mcp.yml
│   │       ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml
│   │       ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml
│   │       ├── search-system-object-attribute-groups.docs-only.test.mcp.yml
│   │       ├── search-system-object-attribute-groups.full-mode.test.mcp.yml
│   │       ├── summarize-logs.full-mode.test.mcp.yml
│   │       ├── tools.docs-only.test.mcp.yml
│   │       └── tools.full-mode.test.mcp.yml
│   ├── oauth-token.test.ts
│   ├── ocapi-auth-client.test.ts
│   ├── ocapi-client.test.ts
│   ├── path-service.test.ts
│   ├── query-builder.test.ts
│   ├── referenced-types-extractor.test.ts
│   ├── servers
│   │   ├── sfcc-mock-server
│   │   │   ├── mock-data
│   │   │   │   └── ocapi
│   │   │   │       ├── code-versions.json
│   │   │   │       ├── custom-object-attributes-customapi.json
│   │   │   │       ├── custom-object-attributes-globalsettings.json
│   │   │   │       ├── custom-object-attributes-versionhistory.json
│   │   │   │       ├── site-preferences-ccv.json
│   │   │   │       ├── site-preferences-fastforward.json
│   │   │   │       ├── site-preferences-sfra.json
│   │   │   │       ├── site-preferences-storefront.json
│   │   │   │       ├── site-preferences-system.json
│   │   │   │       ├── system-object-attribute-groups-campaign.json
│   │   │   │       ├── system-object-attribute-groups-category.json
│   │   │   │       ├── system-object-attribute-groups-order.json
│   │   │   │       ├── system-object-attribute-groups-product.json
│   │   │   │       ├── system-object-attribute-groups-sitepreferences.json
│   │   │   │       ├── system-object-attributes-customeraddress.json
│   │   │   │       ├── system-object-attributes-product-expanded.json
│   │   │   │       ├── system-object-attributes-product.json
│   │   │   │       ├── system-object-definition-category.json
│   │   │   │       ├── system-object-definition-customer.json
│   │   │   │       ├── system-object-definition-customeraddress.json
│   │   │   │       ├── system-object-definition-order.json
│   │   │   │       ├── system-object-definition-product.json
│   │   │   │       ├── system-object-definitions-old.json
│   │   │   │       └── system-object-definitions.json
│   │   │   ├── package-lock.json
│   │   │   ├── package.json
│   │   │   ├── README.md
│   │   │   ├── scripts
│   │   │   │   └── setup-logs.js
│   │   │   ├── server.js
│   │   │   └── src
│   │   │       ├── app.js
│   │   │       ├── config
│   │   │       │   └── server-config.js
│   │   │       ├── middleware
│   │   │       │   ├── auth.js
│   │   │       │   ├── cors.js
│   │   │       │   └── logging.js
│   │   │       ├── routes
│   │   │       │   ├── ocapi
│   │   │       │   │   ├── code-versions-handler.js
│   │   │       │   │   ├── oauth-handler.js
│   │   │       │   │   ├── ocapi-error-utils.js
│   │   │       │   │   ├── ocapi-utils.js
│   │   │       │   │   ├── site-preferences-handler.js
│   │   │       │   │   └── system-objects-handler.js
│   │   │       │   ├── ocapi.js
│   │   │       │   └── webdav.js
│   │   │       └── utils
│   │   │           ├── mock-data-loader.js
│   │   │           └── webdav-xml.js
│   │   └── sfcc-mock-server-manager.ts
│   ├── sfcc-mock-server.test.ts
│   ├── site-preferences-client.test.ts
│   ├── system-objects-client.test.ts
│   ├── utils.test.ts
│   ├── validation-helpers.test.ts
│   └── validator.test.ts
├── tsconfig.json
└── tsconfig.test.json
```

# Files

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-system-object-attribute-definitions.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | # ==================================================================================
  2 | # SFCC MCP Server - search_system_object_attribute_definitions Tool YAML Tests (Full Mode)
  3 | # Streamlined smoke testing and declarative validation for core functionality
  4 | # Complex business logic, edge cases, and workflows are covered in programmatic tests
  5 | # 
  6 | # Quick Test Commands:
  7 | # aegis "tests/mcp/yaml/search-system-object-attribute-definitions.full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose
  8 | # aegis query search_system_object_attribute_definitions '{"objectType": "Product", "searchRequest": {"query": {"match_all_query": {}}, "count": 5}}' --config "aegis.config.with-dw.json"
  9 | # ==================================================================================
 10 | 
 11 | description: "search_system_object_attribute_definitions tool smoke tests - Basic functionality validation"
 12 | 
 13 | tests:
 14 |   # ==================================================================================
 15 |   # TOOL AVAILABILITY VALIDATION
 16 |   # ==================================================================================
 17 |   - it: "should have search_system_object_attribute_definitions tool available with proper schema"
 18 |     request:
 19 |       jsonrpc: "2.0"
 20 |       id: "tool-available"
 21 |       method: "tools/list"
 22 |       params: {}
 23 |     expect:
 24 |       response:
 25 |         jsonrpc: "2.0"
 26 |         id: "tool-available"
 27 |         result:
 28 |           tools:
 29 |             match:arrayElements:
 30 |               match:partial:
 31 |                 name: "match:type:string"
 32 |                 description: "match:type:string"
 33 |           match:extractField: "tools.*.name"
 34 |           value: "match:arrayContains:search_system_object_attribute_definitions"
 35 |       stderr: "toBeEmpty"
 36 | 
 37 |   # ==================================================================================
 38 |   # CORE FUNCTIONALITY VALIDATION - match_all_query
 39 |   # ==================================================================================
 40 |   - it: "should successfully search Product attributes with match_all_query and return valid structure"
 41 |     request:
 42 |       jsonrpc: "2.0"
 43 |       id: "match-all-success"
 44 |       method: "tools/call"
 45 |       params:
 46 |         name: "search_system_object_attribute_definitions"
 47 |         arguments:
 48 |           objectType: "Product"
 49 |           searchRequest:
 50 |             query:
 51 |               match_all_query: {}
 52 |             count: 5
 53 |     expect:
 54 |       response:
 55 |         jsonrpc: "2.0"
 56 |         id: "match-all-success"
 57 |         result:
 58 |           content:
 59 |             - type: "text"
 60 |               text: "match:contains:object_attribute_definition_search_result"
 61 |           isError: false
 62 |       performance:
 63 |         maxResponseTime: "2000ms"
 64 |       stderr: "toBeEmpty"
 65 | 
 66 |   - it: "should return valid JSON structure with pagination in match_all_query response"
 67 |     request:
 68 |       jsonrpc: "2.0"
 69 |       id: "match-all-structure"
 70 |       method: "tools/call"
 71 |       params:
 72 |         name: "search_system_object_attribute_definitions"
 73 |         arguments:
 74 |           objectType: "Product"
 75 |           searchRequest:
 76 |             query:
 77 |               match_all_query: {}
 78 |             count: 3
 79 |     expect:
 80 |       response:
 81 |         jsonrpc: "2.0"
 82 |         id: "match-all-structure"
 83 |         result:
 84 |           content:
 85 |             - type: "text"
 86 |               text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition_search_result\"[\\s\\S]*"
 87 |           isError: false
 88 |       stderr: "toBeEmpty"
 89 | 
 90 |   - it: "should include query echo and pagination info in match_all_query response"
 91 |     request:
 92 |       jsonrpc: "2.0"
 93 |       id: "match-all-metadata"
 94 |       method: "tools/call"
 95 |       params:
 96 |         name: "search_system_object_attribute_definitions"
 97 |         arguments:
 98 |           objectType: "Product"
 99 |           searchRequest:
100 |             query:
101 |               match_all_query: {}
102 |             count: 5
103 |             start: 0
104 |     expect:
105 |       response:
106 |         jsonrpc: "2.0"
107 |         id: "match-all-metadata"
108 |         result:
109 |           content:
110 |             - type: "text"
111 |               text: "match:regex:[\\s\\S]*\"query\"[\\s\\S]*\"match_all_query\"[\\s\\S]*\"start\"[\\s\\S]*\"total\"[\\s\\S]*"
112 |           isError: false
113 |       stderr: "toBeEmpty"
114 | 
115 |   # ==================================================================================
116 |   # CORE FUNCTIONALITY VALIDATION - text_query
117 |   # ==================================================================================
118 |   - it: "should successfully search with text_query and return matching results"
119 |     request:
120 |       jsonrpc: "2.0"
121 |       id: "text-query-success"
122 |       method: "tools/call"
123 |       params:
124 |         name: "search_system_object_attribute_definitions"
125 |         arguments:
126 |           objectType: "Product"
127 |           searchRequest:
128 |             query:
129 |               text_query:
130 |                 fields: ["id", "display_name"]
131 |                 search_phrase: "UPC"
132 |             count: 3
133 |     expect:
134 |       response:
135 |         jsonrpc: "2.0"
136 |         id: "text-query-success"
137 |         result:
138 |           content:
139 |             - type: "text"
140 |               text: "match:contains:object_attribute_definition_search_result"
141 |           isError: false
142 |       performance:
143 |         maxResponseTime: "2000ms"
144 |       stderr: "toBeEmpty"
145 | 
146 |   - it: "should echo text_query parameters in response"
147 |     request:
148 |       jsonrpc: "2.0"
149 |       id: "text-query-echo"
150 |       method: "tools/call"
151 |       params:
152 |         name: "search_system_object_attribute_definitions"
153 |         arguments:
154 |           objectType: "Product"
155 |           searchRequest:
156 |             query:
157 |               text_query:
158 |                 fields: ["id", "display_name"]
159 |                 search_phrase: "UPC"
160 |     expect:
161 |       response:
162 |         jsonrpc: "2.0"
163 |         id: "text-query-echo"
164 |         result:
165 |           content:
166 |             - type: "text"
167 |               text: "match:regex:[\\s\\S]*\"text_query\"[\\s\\S]*\"fields\"[\\s\\S]*\"search_phrase\"[\\s\\S]*\"UPC\"[\\s\\S]*"
168 |           isError: false
169 |       stderr: "toBeEmpty"
170 | 
171 |   # ==================================================================================
172 |   # CORE FUNCTIONALITY VALIDATION - term_query
173 |   # ==================================================================================
174 |   - it: "should successfully search with term_query using exact matching"
175 |     request:
176 |       jsonrpc: "2.0"
177 |       id: "term-query-success"
178 |       method: "tools/call"
179 |       params:
180 |         name: "search_system_object_attribute_definitions"
181 |         arguments:
182 |           objectType: "Product"
183 |           searchRequest:
184 |             query:
185 |               term_query:
186 |                 fields: ["value_type"]
187 |                 operator: "is"
188 |                 values: ["string"]
189 |             count: 5
190 |     expect:
191 |       response:
192 |         jsonrpc: "2.0"
193 |         id: "term-query-success"
194 |         result:
195 |           content:
196 |             - type: "text"
197 |               text: "match:contains:object_attribute_definition_search_result"
198 |           isError: false
199 |       performance:
200 |         maxResponseTime: "2000ms"
201 |       stderr: "toBeEmpty"
202 | 
203 |   - it: "should echo term_query parameters correctly"
204 |     request:
205 |       jsonrpc: "2.0"
206 |       id: "term-query-echo"
207 |       method: "tools/call"
208 |       params:
209 |         name: "search_system_object_attribute_definitions"
210 |         arguments:
211 |           objectType: "Product"
212 |           searchRequest:
213 |             query:
214 |               term_query:
215 |                 fields: ["value_type"]
216 |                 operator: "is"
217 |                 values: ["string"]
218 |     expect:
219 |       response:
220 |         jsonrpc: "2.0"
221 |         id: "term-query-echo"
222 |         result:
223 |           content:
224 |             - type: "text"
225 |               text: "match:regex:[\\s\\S]*\"term_query\"[\\s\\S]*\"fields\"[\\s\\S]*\"operator\"[\\s\\S]*\"is\"[\\s\\S]*\"values\"[\\s\\S]*"
226 |           isError: false
227 |       stderr: "toBeEmpty"
228 | 
229 |   # ==================================================================================
230 |   # PAGINATION AND SORTING VALIDATION
231 |   # ==================================================================================
232 |   - it: "should handle pagination parameters correctly"
233 |     request:
234 |       jsonrpc: "2.0"
235 |       id: "pagination-test"
236 |       method: "tools/call"
237 |       params:
238 |         name: "search_system_object_attribute_definitions"
239 |         arguments:
240 |           objectType: "Product"
241 |           searchRequest:
242 |             query:
243 |               match_all_query: {}
244 |             start: 5
245 |             count: 3
246 |     expect:
247 |       response:
248 |         jsonrpc: "2.0"
249 |         id: "pagination-test"
250 |         result:
251 |           content:
252 |             - type: "text"
253 |               text: "match:regex:[\\s\\S]*\"start\"[\\s\\S]*5[\\s\\S]*\"count\"[\\s\\S]*3[\\s\\S]*"
254 |           isError: false
255 |       performance:
256 |         maxResponseTime: "2000ms"
257 |       stderr: "toBeEmpty"
258 | 
259 |   - it: "should handle sorting parameters correctly"
260 |     request:
261 |       jsonrpc: "2.0"
262 |       id: "sorting-test"
263 |       method: "tools/call"
264 |       params:
265 |         name: "search_system_object_attribute_definitions"
266 |         arguments:
267 |           objectType: "Product"
268 |           searchRequest:
269 |             query:
270 |               match_all_query: {}
271 |             sorts:
272 |               - field: "id"
273 |                 sort_order: "asc"
274 |             count: 5
275 |     expect:
276 |       response:
277 |         jsonrpc: "2.0"
278 |         id: "sorting-test"
279 |         result:
280 |           content:
281 |             - type: "text"
282 |               text: "match:contains:object_attribute_definition_search_result"
283 |           isError: false
284 |       performance:
285 |         maxResponseTime: "2000ms"
286 |       stderr: "toBeEmpty"
287 | 
288 |   # ==================================================================================
289 |   # DIFFERENT OBJECT TYPES VALIDATION
290 |   # ==================================================================================
291 |   - it: "should handle Customer object type correctly"
292 |     request:
293 |       jsonrpc: "2.0"
294 |       id: "customer-test"
295 |       method: "tools/call"
296 |       params:
297 |         name: "search_system_object_attribute_definitions"
298 |         arguments:
299 |           objectType: "Customer"
300 |           searchRequest:
301 |             query:
302 |               match_all_query: {}
303 |             count: 3
304 |     expect:
305 |       response:
306 |         jsonrpc: "2.0"
307 |         id: "customer-test"
308 |         result:
309 |           content:
310 |             - type: "text"
311 |               text: "match:contains:object_attribute_definition_search_result"
312 |           isError: false
313 |       performance:
314 |         maxResponseTime: "2000ms"
315 |       stderr: "toBeEmpty"
316 | 
317 |   - it: "should handle Order object type correctly"
318 |     request:
319 |       jsonrpc: "2.0"
320 |       id: "order-test"
321 |       method: "tools/call"
322 |       params:
323 |         name: "search_system_object_attribute_definitions"
324 |         arguments:
325 |           objectType: "Order"
326 |           searchRequest:
327 |             query:
328 |               match_all_query: {}
329 |             count: 3
330 |     expect:
331 |       response:
332 |         jsonrpc: "2.0"
333 |         id: "order-test"
334 |         result:
335 |           content:
336 |             - type: "text"
337 |               text: "match:contains:object_attribute_definition_search_result"
338 |           isError: false
339 |       performance:
340 |         maxResponseTime: "2000ms"
341 |       stderr: "toBeEmpty"
342 | 
343 |   # ==================================================================================
344 |   # PARAMETER VALIDATION
345 |   # ==================================================================================
346 |   - it: "should handle minimal parameters gracefully (only objectType)"
347 |     request:
348 |       jsonrpc: "2.0"
349 |       id: "minimal-params"
350 |       method: "tools/call"
351 |       params:
352 |         name: "search_system_object_attribute_definitions"
353 |         arguments:
354 |           objectType: "Product"
355 |     expect:
356 |       response:
357 |         jsonrpc: "2.0"
358 |         id: "minimal-params"
359 |         result:
360 |           content:
361 |             - type: "text"
362 |               text: "match:contains:object_attribute_definition_search_result"
363 |           isError: false
364 |       performance:
365 |         maxResponseTime: "2000ms"
366 |       stderr: "toBeEmpty"
367 | 
368 |   - it: "should handle invalid object type gracefully (returns empty results)"
369 |     request:
370 |       jsonrpc: "2.0"
371 |       id: "invalid-object-type"
372 |       method: "tools/call"
373 |       params:
374 |         name: "search_system_object_attribute_definitions"
375 |         arguments:
376 |           objectType: "NonExistentType"
377 |           searchRequest:
378 |             query:
379 |               match_all_query: {}
380 |     expect:
381 |       response:
382 |         jsonrpc: "2.0"
383 |         id: "invalid-object-type"
384 |         result:
385 |           content:
386 |             - type: "text"
387 |               text: "match:contains:\"total\": 0"
388 |           isError: false
389 |       performance:
390 |         maxResponseTime: "2000ms"
391 |       stderr: "toBeEmpty"
392 | 
393 |   # ==================================================================================
394 |   # PERFORMANCE VALIDATION
395 |   # ==================================================================================
396 |   - it: "should complete match_all_query operations within reasonable time"
397 |     request:
398 |       jsonrpc: "2.0"
399 |       id: "perf-match-all"
400 |       method: "tools/call"
401 |       params:
402 |         name: "search_system_object_attribute_definitions"
403 |         arguments:
404 |           objectType: "Product"
405 |           searchRequest:
406 |             query:
407 |               match_all_query: {}
408 |             count: 10
409 |     expect:
410 |       response:
411 |         jsonrpc: "2.0"
412 |         id: "perf-match-all"
413 |         result:
414 |           content:
415 |             - type: "text"
416 |               text: "match:contains:object_attribute_definition_search_result"
417 |           isError: false
418 |       performance:
419 |         maxResponseTime: "1500ms"
420 |       stderr: "toBeEmpty"
421 | 
422 |   - it: "should complete text_query operations within reasonable time"
423 |     request:
424 |       jsonrpc: "2.0"
425 |       id: "perf-text-query"
426 |       method: "tools/call"
427 |       params:
428 |         name: "search_system_object_attribute_definitions"
429 |         arguments:
430 |           objectType: "Product"
431 |           searchRequest:
432 |             query:
433 |               text_query:
434 |                 fields: ["id", "display_name", "description"]
435 |                 search_phrase: "brand"
436 |             count: 5
437 |     expect:
438 |       response:
439 |         jsonrpc: "2.0"
440 |         id: "perf-text-query"
441 |         result:
442 |           content:
443 |             - type: "text"
444 |               text: "match:contains:object_attribute_definition_search_result"
445 |           isError: false
446 |       performance:
447 |         maxResponseTime: "2000ms"
448 |       stderr: "toBeEmpty"
449 | 
450 |   # ==================================================================================
451 |   # RESPONSE STRUCTURE VALIDATION
452 |   # ==================================================================================
453 |   - it: "should return properly structured SFCC API response format"
454 |     request:
455 |       jsonrpc: "2.0"
456 |       id: "structure-validation"
457 |       method: "tools/call"
458 |       params:
459 |         name: "search_system_object_attribute_definitions"
460 |         arguments:
461 |           objectType: "Product"
462 |           searchRequest:
463 |             query:
464 |               match_all_query: {}
465 |             count: 2
466 |     expect:
467 |       response:
468 |         jsonrpc: "2.0"
469 |         id: "structure-validation"
470 |         result:
471 |           content:
472 |             - type: "text"
473 |               text: "match:regex:[\\s\\S]*\"_v\"[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition_search_result\"[\\s\\S]*\"hits\"[\\s\\S]*\"query\"[\\s\\S]*\"start\"[\\s\\S]*\"total\"[\\s\\S]*"
474 |           isError: false
475 |       stderr: "toBeEmpty"
476 | 
477 |   - it: "should include attribute definition details when available"
478 |     request:
479 |       jsonrpc: "2.0"
480 |       id: "attribute-details"
481 |       method: "tools/call"
482 |       params:
483 |         name: "search_system_object_attribute_definitions"
484 |         arguments:
485 |           objectType: "Product"
486 |           searchRequest:
487 |             query:
488 |               match_all_query: {}
489 |             count: 3
490 |             select: "(**)"
491 |     expect:
492 |       response:
493 |         jsonrpc: "2.0"
494 |         id: "attribute-details"
495 |         result:
496 |           content:
497 |             - type: "text"
498 |               text: "match:regex:[\\s\\S]*\"_type\"[\\s\\S]*\"object_attribute_definition\"[\\s\\S]*\"id\"[\\s\\S]*\"link\"[\\s\\S]*"
499 |           isError: false
500 |       stderr: "toBeEmpty"
```

--------------------------------------------------------------------------------
/tests/servers/sfcc-mock-server/src/routes/ocapi/ocapi-utils.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * OCAPI Utilities
  3 |  * 
  4 |  * Shared utility functions for OCAPI handlers including select parameter parsing,
  5 |  * pagination helpers, and object enhancement functions.
  6 |  */
  7 | 
  8 | class OCAPIUtils {
  9 |     /**
 10 |      * Apply select parameter to the root response structure based on SFCC select syntax
 11 |      * Examples:
 12 |      * - (**) = return full response 
 13 |      * - (start, total) = return only start and total fields
 14 |      * - (data.(**)) = return only data array with all object properties
 15 |      * - (data.(object_type)) = return only data array with specific fields
 16 |      * - (start, data.(**)) = return start field plus data array with all properties
 17 |      */
 18 |     static applyRootSelectParameter(response, select) {
 19 |         // Default behavior - return full response
 20 |         if (!select || select === '(**)') {
 21 |             return response;
 22 |         }
 23 | 
 24 |         // Parse select parameter - remove outer parentheses and split by commas
 25 |         const trimmed = select.replace(/^\(|\)$/g, '');
 26 |         const parts = this.parseSelectParts(trimmed);
 27 |         
 28 |         const result = {};
 29 |         
 30 |         // Always include metadata fields that SFCC includes
 31 |         result._v = response._v;
 32 |         result._type = response._type;
 33 |         
 34 |         for (const part of parts) {
 35 |             const trimmedPart = part.trim();
 36 |             
 37 |             if (trimmedPart === 'start') {
 38 |                 result.start = response.start;
 39 |             } else if (trimmedPart === 'total') {
 40 |                 result.total = response.total;
 41 |             } else if (trimmedPart === 'count') {
 42 |                 result.count = response.count;
 43 |             } else if (trimmedPart === 'next') {
 44 |                 result.next = response.next;
 45 |             } else if (trimmedPart === 'previous') {
 46 |                 result.previous = response.previous;
 47 |             } else if (trimmedPart === 'select') {
 48 |                 result.select = response.select;
 49 |             } else if (trimmedPart.startsWith('data.')) {
 50 |                 // Handle data.(...) patterns
 51 |                 result.data = response.data;
 52 |                 // Note: data object filtering is already applied by applySelectParameter
 53 |             } else if (trimmedPart === 'data') {
 54 |                 result.data = response.data;
 55 |             }
 56 |         }
 57 |         
 58 |         return result;
 59 |     }
 60 | 
 61 |     /**
 62 |      * Parse select parts handling nested parentheses correctly
 63 |      */
 64 |     static parseSelectParts(selectString) {
 65 |         const parts = [];
 66 |         let current = '';
 67 |         let depth = 0;
 68 |         
 69 |         for (let i = 0; i < selectString.length; i++) {
 70 |             const char = selectString[i];
 71 |             
 72 |             if (char === '(') {
 73 |                 depth++;
 74 |                 current += char;
 75 |             } else if (char === ')') {
 76 |                 depth--;
 77 |                 current += char;
 78 |             } else if (char === ',' && depth === 0) {
 79 |                 if (current.trim()) {
 80 |                     parts.push(current.trim());
 81 |                 }
 82 |                 current = '';
 83 |             } else {
 84 |                 current += char;
 85 |             }
 86 |         }
 87 |         
 88 |         if (current.trim()) {
 89 |             parts.push(current.trim());
 90 |         }
 91 |         
 92 |         return parts;
 93 |     }
 94 | 
 95 |     /**
 96 |      * Apply select parameter to modify object structure based on SFCC select syntax
 97 |      * Examples:
 98 |      * - Default (no select) = return basic format (_type, _resource_state, object_type, link)
 99 |      * - (**) = return all fields (enhanced detailed format)
100 |      * - (data.(object_type)) = return only object_type field in data objects
101 |      * - (data.(object_type,display_name)) = return specific fields in data objects
102 |      */
103 |     static applySelectParameter(obj, select) {
104 |         // Default behavior (no select) - return basic format
105 |         if (!select) {
106 |             return obj; // Keep original basic format
107 |         }
108 | 
109 |         // Full select - return enhanced detailed format
110 |         if (select === '(**)') {
111 |             return this.enhanceObjectWithDetailedFields(obj);
112 |         }
113 | 
114 |         // Specific field selection using (...) syntax (after data.() processing)
115 |         if (select.startsWith('(') && select.endsWith(')') && !select.startsWith('(data.(')) {
116 |             // Extract field list from (field1,field2)
117 |             const fieldsMatch = select.match(/\(([^)]+)\)/);
118 |             if (fieldsMatch) {
119 |                 const fields = fieldsMatch[1].split(',').map(f => f.trim());
120 |                 return this.selectSpecificFields(obj, fields);
121 |             }
122 |         }
123 | 
124 |         // Specific field selection using data.(...) syntax
125 |         if (select.startsWith('(data.(') && select.endsWith('))')) {
126 |             // Extract field list from (data.(field1,field2))
127 |             const fieldsMatch = select.match(/\(data\.\(([^)]+)\)\)/);
128 |             if (fieldsMatch) {
129 |                 const fields = fieldsMatch[1].split(',').map(f => f.trim());
130 |                 return this.selectSpecificFields(obj, fields);
131 |             }
132 |         }
133 | 
134 |         // Other select patterns that don't work properly in real API
135 |         // Return basic format as fallback
136 |         return obj;
137 |     }
138 | 
139 |     /**
140 |      * Select only specific fields from the object
141 |      */
142 |     static selectSpecificFields(obj, fields) {
143 |         const result = {
144 |             "_type": "object_type_definition",
145 |             "_resource_state": obj._resource_state
146 |         };
147 | 
148 |         // Add requested fields
149 |         fields.forEach(field => {
150 |             if (field === 'object_type') {
151 |                 result.object_type = obj.object_type;
152 |             } else if (field === 'display_name') {
153 |                 result.display_name = this.getObjectTypeDisplayName(obj.object_type);
154 |             } else if (field === 'description') {
155 |                 result.description = this.getObjectTypeDescription(obj.object_type);
156 |             } else if (field === 'link') {
157 |                 result.link = obj.link;
158 |             } else if (field === 'read_only') {
159 |                 result.read_only = this.getObjectTypeReadOnly(obj.object_type);
160 |             } else if (field === 'queryable') {
161 |                 result.queryable = this.getObjectTypeQueryable(obj.object_type);
162 |             } else if (field === 'content_object') {
163 |                 result.content_object = this.getObjectTypeContentObject(obj.object_type);
164 |             } else if (field === 'attribute_definition_count') {
165 |                 result.attribute_definition_count = this.getObjectTypeAttributeCount(obj.object_type);
166 |             } else if (field === 'attribute_group_count') {
167 |                 result.attribute_group_count = this.getObjectTypeAttributeGroupCount(obj.object_type);
168 |             } else if (field === 'creation_date') {
169 |                 result.creation_date = "2024-02-19T10:18:31.000Z";
170 |             } else if (field === 'last_modified') {
171 |                 result.last_modified = "2024-02-19T10:18:31.000Z";
172 |             }
173 |         });
174 | 
175 |         return result;
176 |     }
177 | 
178 |     /**
179 |      * Enhance basic object definition with detailed fields to match real SFCC API response
180 |      */
181 |     static enhanceObjectWithDetailedFields(obj) {
182 |         // Return enhanced version with detailed fields (like the real API with select=(**))
183 |         const enhanced = {
184 |             "_type": "object_type_definition",
185 |             "_resource_state": obj._resource_state,
186 |             "attribute_definition_count": this.getObjectTypeAttributeCount(obj.object_type),
187 |             "attribute_group_count": this.getObjectTypeAttributeGroupCount(obj.object_type),
188 |             "content_object": this.getObjectTypeContentObject(obj.object_type),
189 |             "creation_date": "2024-02-19T10:18:31.000Z",
190 |             "description": this.getObjectTypeDescription(obj.object_type),
191 |             "display_name": this.getObjectTypeDisplayName(obj.object_type),
192 |             "last_modified": "2024-02-19T10:18:31.000Z",
193 |             "link": obj.link,
194 |             "object_type": obj.object_type,
195 |             "queryable": this.getObjectTypeQueryable(obj.object_type),
196 |             "read_only": this.getObjectTypeReadOnly(obj.object_type)
197 |         };
198 | 
199 |         return enhanced;
200 |     }
201 | 
202 |     /**
203 |      * Generate pagination URLs for API responses
204 |      */
205 |     static generatePaginationUrls(req, start, count, total, select) {
206 |         const baseUrl = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`;
207 |         let nextUrl = null;
208 |         let previousUrl = null;
209 |         
210 |         // Generate next URL if there are more items
211 |         const hasMore = (start + count) < total;
212 |         if (hasMore) {
213 |             const nextStart = start + count;
214 |             nextUrl = `${baseUrl}?start=${nextStart}&count=${count}`;
215 |             if (select !== '(**)') {
216 |                 nextUrl += `&select=${encodeURIComponent(select)}`;
217 |             }
218 |         }
219 |         
220 |         // Generate previous URL if not at the beginning
221 |         if (start > 0) {
222 |             const prevStart = Math.max(0, start - count);
223 |             previousUrl = `${baseUrl}?start=${prevStart}&count=${count}`;
224 |             if (select !== '(**)') {
225 |                 previousUrl += `&select=${encodeURIComponent(select)}`;
226 |             }
227 |         }
228 | 
229 |         return { nextUrl, previousUrl };
230 |     }
231 | 
232 |     /**
233 |      * Apply basic text search filtering to results
234 |      */
235 |     static applyTextSearch(results, query) {
236 |         if (!query || !query.text_query) {
237 |             return results;
238 |         }
239 | 
240 |         const searchTerm = query.text_query.search_phrase.toLowerCase();
241 |         const searchFields = query.text_query.fields || ["id"];
242 |         
243 |         return results.filter(item => {
244 |             return searchFields.some(field => {
245 |                 if (field === "id" && item.id) {
246 |                     return item.id.toLowerCase().includes(searchTerm);
247 |                 }
248 |                 if (field === "display_name" && item.display_name) {
249 |                     // Check if any display_name value contains the search term
250 |                     const displayNames = Object.values(item.display_name || {});
251 |                     return displayNames.some(name => 
252 |                         String(name).toLowerCase().includes(searchTerm)
253 |                     );
254 |                 }
255 |                 if (field === "description" && item.description) {
256 |                     return String(item.description).toLowerCase().includes(searchTerm);
257 |                 }
258 |                 if (field === "object_type" && item.object_type) {
259 |                     return item.object_type.toLowerCase().includes(searchTerm);
260 |                 }
261 |                 return false;
262 |             });
263 |         });
264 |     }
265 | 
266 |     /**
267 |      * Build proper query response with SFCC-compliant _type fields
268 |      */
269 |     static buildQueryResponse(originalQuery) {
270 |         let queryResponse = originalQuery || {"match_all_query": {}};
271 |         
272 |         if (queryResponse.match_all_query && !queryResponse.match_all_query._type) {
273 |             queryResponse = {
274 |                 ...queryResponse,
275 |                 match_all_query: {
276 |                     ...queryResponse.match_all_query,
277 |                     "_type": "match_all_query"
278 |                 }
279 |             };
280 |         }
281 |         
282 |         if (queryResponse.text_query && !queryResponse.text_query._type) {
283 |             queryResponse = {
284 |                 ...queryResponse,
285 |                 text_query: {
286 |                     ...queryResponse.text_query,
287 |                     "_type": "text_query"
288 |                 }
289 |             };
290 |         }
291 | 
292 |         return queryResponse;
293 |     }
294 | 
295 |     // Object type metadata helpers
296 |     
297 |     /**
298 |      * Get display name for object type (with internationalization)
299 |      */
300 |     static getObjectTypeDisplayName(objectType) {
301 |         const displayNames = {
302 |             'Appeasement': {
303 |                 "de": "Beschwerde",
304 |                 "default": "Appeasement",
305 |                 "ja": "譲歩",
306 |                 "it": "Riconciliazione", 
307 |                 "fr": "Geste commercial",
308 |                 "zh-CN": "协调",
309 |                 "es": "Compensación",
310 |                 "nl": "Tegemoetkoming"
311 |             },
312 |             'AppeasementItem': {
313 |                 "de": "Beschwerde-Artikel",
314 |                 "default": "Appeasement Item",
315 |                 "ja": "譲歩項目",
316 |                 "it": "Articolo di riconciliazione",
317 |                 "fr": "Article du geste commercial", 
318 |                 "zh-CN": "协调项目",
319 |                 "es": "Artículo de compensación",
320 |                 "nl": "Tegemoetkomingsitem"
321 |             },
322 |             'Basket': {
323 |                 "de": "Warenkorb",
324 |                 "default": "Basket",
325 |                 "ja": "買い物カゴ",
326 |                 "it": "Carrello",
327 |                 "fr": "Panier",
328 |                 "zh-CN": "购物车",
329 |                 "es": "Canasta",
330 |                 "nl": "Mandje"
331 |             }
332 |         };
333 |         
334 |         return displayNames[objectType] || {
335 |             "default": objectType
336 |         };
337 |     }
338 | 
339 |     /**
340 |      * Get description for object type (with internationalization)
341 |      */
342 |     static getObjectTypeDescription(objectType) {
343 |         const descriptions = {
344 |             'Appeasement': { "default": "Object type representing appeasements." },
345 |             'AppeasementItem': { "default": "Object type representing appeasement items." },
346 |             'Basket': { "default": "Object type representing baskets." },
347 |             'Product': { "default": "Object type representing products." },
348 |             'Category': { "default": "Object type representing categories." },
349 |             'Content': { "default": "Object type representing content assets." }
350 |         };
351 |         
352 |         return descriptions[objectType] || {
353 |             "default": `Object type representing ${objectType.toLowerCase()} entities.`
354 |         };
355 |     }
356 | 
357 |     /**
358 |      * Get read_only flag for object type
359 |      */
360 |     static getObjectTypeReadOnly(objectType) {
361 |         const readOnlyTypes = ['CustomerActiveData', 'CustomerCDPData'];
362 |         return readOnlyTypes.includes(objectType);
363 |     }
364 | 
365 |     /**
366 |      * Get queryable flag for object type
367 |      */
368 |     static getObjectTypeQueryable(objectType) {
369 |         const nonQueryableTypes = ['CustomObject'];
370 |         return !nonQueryableTypes.includes(objectType);
371 |     }
372 | 
373 |     /**
374 |      * Get content_object flag for object type
375 |      */
376 |     static getObjectTypeContentObject(objectType) {
377 |         const contentObjectTypes = ['Content'];
378 |         return contentObjectTypes.includes(objectType);
379 |     }
380 | 
381 |     /**
382 |      * Get attribute definition count for object type (matching real API data)
383 |      */
384 |     static getObjectTypeAttributeCount(objectType) {
385 |         const attributeCounts = {
386 |             'Appeasement': 9,
387 |             'AppeasementItem': 7,
388 |             'Basket': 11,
389 |             'BonusDiscountLineItem': 5,
390 |             'Campaign': 25,
391 |             'Catalog': 12,
392 |             'Category': 45,
393 |             'CategoryAssignment': 8,
394 |             'Content': 60,
395 |             'Coupon': 18,
396 |             'CouponLineItem': 6,
397 |             'CustomObject': 5,
398 |             'CustomerActiveData': 10,
399 |             'CustomerAddress': 15,
400 |             'CustomerCDPData': 10,
401 |             'CustomerGroup': 8,
402 |             'CustomerPaymentInstrument': 10,
403 |             'Customer': 28,
404 |             'Order': 65,
405 |             'Product': 166
406 |         };
407 |         return attributeCounts[objectType] || 10;
408 |     }
409 | 
410 |     /**
411 |      * Get attribute group count for object type (matching real API data)
412 |      */
413 |     static getObjectTypeAttributeGroupCount(objectType) {
414 |         const groupCounts = {
415 |             'Appeasement': 2,
416 |             'AppeasementItem': 1,
417 |             'Basket': 1,
418 |             'BonusDiscountLineItem': 1,
419 |             'Campaign': 2,
420 |             'Catalog': 1,
421 |             'Category': 3,
422 |             'CategoryAssignment': 1,
423 |             'Content': 3,
424 |             'Coupon': 2,
425 |             'CouponLineItem': 1,
426 |             'CustomObject': 1,
427 |             'CustomerActiveData': 2,
428 |             'CustomerAddress': 2,
429 |             'CustomerCDPData': 2,
430 |             'CustomerGroup': 1,
431 |             'CustomerPaymentInstrument': 2,
432 |             'Customer': 4,
433 |             'Order': 6,
434 |             'Product': 5
435 |         };
436 |         return groupCounts[objectType] || 2;
437 |     }
438 | }
439 | 
440 | module.exports = OCAPIUtils;
```

--------------------------------------------------------------------------------
/docs/dw_object/CustomObjectMgr.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.object
  2 | 
  3 | # Class CustomObjectMgr
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.CustomObjectMgr
  9 | 
 10 | ## Description
 11 | 
 12 | Manager class which provides methods for creating, retrieving, deleting, and searching for custom objects. To search for system objects, use SystemObjectMgr.
 13 | 
 14 | ## Constructor Summary
 15 | 
 16 | ## Method Summary
 17 | 
 18 | ### createCustomObject
 19 | 
 20 | **Signature:** `static createCustomObject(type : String, keyValue : String) : CustomObject`
 21 | 
 22 | Returns a new custom object instance of the specified type, using the given key value.
 23 | 
 24 | ### createCustomObject
 25 | 
 26 | **Signature:** `static createCustomObject(type : String, keyValue : Number) : CustomObject`
 27 | 
 28 | Returns a new custom object instance of the specified type, using the given key value.
 29 | 
 30 | ### describe
 31 | 
 32 | **Signature:** `static describe(type : String) : ObjectTypeDefinition`
 33 | 
 34 | Returns the meta data for the given type.
 35 | 
 36 | ### getAllCustomObjects
 37 | 
 38 | **Signature:** `static getAllCustomObjects(type : String) : SeekableIterator`
 39 | 
 40 | Returns all custom objects of a specific type.
 41 | 
 42 | ### getCustomObject
 43 | 
 44 | **Signature:** `static getCustomObject(type : String, keyValue : String) : CustomObject`
 45 | 
 46 | Returns a custom object based on it's type and unique key.
 47 | 
 48 | ### getCustomObject
 49 | 
 50 | **Signature:** `static getCustomObject(type : String, keyValue : Number) : CustomObject`
 51 | 
 52 | Returns a custom object based on it's type and unique key.
 53 | 
 54 | ### queryCustomObject
 55 | 
 56 | **Signature:** `static queryCustomObject(type : String, queryString : String, args : Object...) : CustomObject`
 57 | 
 58 | Searches for a single custom object instance.
 59 | 
 60 | ### queryCustomObjects
 61 | 
 62 | **Signature:** `static queryCustomObjects(type : String, queryString : String, sortString : String, args : Object...) : SeekableIterator`
 63 | 
 64 | Searches for custom object instances.
 65 | 
 66 | ### queryCustomObjects
 67 | 
 68 | **Signature:** `static queryCustomObjects(type : String, queryAttributes : Map, sortString : String) : SeekableIterator`
 69 | 
 70 | Searches for custom object instances.
 71 | 
 72 | ### remove
 73 | 
 74 | **Signature:** `static remove(object : CustomObject) : void`
 75 | 
 76 | Removes a given custom object.
 77 | 
 78 | ## Method Detail
 79 | 
 80 | ## Method Details
 81 | 
 82 | ### createCustomObject
 83 | 
 84 | **Signature:** `static createCustomObject(type : String, keyValue : String) : CustomObject`
 85 | 
 86 | **Description:** Returns a new custom object instance of the specified type, using the given key value. Custom object keys need to be unique for custom object type.
 87 | 
 88 | **Parameters:**
 89 | 
 90 | - `type`: The unique name of the custom object type.
 91 | - `keyValue`: The unique key value for the instance.
 92 | 
 93 | **Returns:**
 94 | 
 95 | The newly created custom object instance.
 96 | 
 97 | **Throws:**
 98 | 
 99 | IllegalArgumentException - if the given type is invalid
100 | 
101 | ---
102 | 
103 | ### createCustomObject
104 | 
105 | **Signature:** `static createCustomObject(type : String, keyValue : Number) : CustomObject`
106 | 
107 | **Description:** Returns a new custom object instance of the specified type, using the given key value. Custom object keys need to be unique for custom object type.
108 | 
109 | **Parameters:**
110 | 
111 | - `type`: The unique name of the custom object type.
112 | - `keyValue`: The unique key value for the instance.
113 | 
114 | **Returns:**
115 | 
116 | The newly created custom object instance.
117 | 
118 | **Throws:**
119 | 
120 | IllegalArgumentException - if the given type is invalid
121 | 
122 | ---
123 | 
124 | ### describe
125 | 
126 | **Signature:** `static describe(type : String) : ObjectTypeDefinition`
127 | 
128 | **Description:** Returns the meta data for the given type.
129 | 
130 | **Parameters:**
131 | 
132 | - `type`: the type whose meta data is returned.
133 | 
134 | **Returns:**
135 | 
136 | the meta data for the given type.
137 | 
138 | **Throws:**
139 | 
140 | IllegalArgumentException - if the given type is invalid
141 | 
142 | ---
143 | 
144 | ### getAllCustomObjects
145 | 
146 | **Signature:** `static getAllCustomObjects(type : String) : SeekableIterator`
147 | 
148 | **Description:** Returns all custom objects of a specific type. It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources.
149 | 
150 | **Parameters:**
151 | 
152 | - `type`: The name of the custom object type.
153 | 
154 | **See Also:**
155 | 
156 | SeekableIterator.close()
157 | 
158 | **Throws:**
159 | 
160 | IllegalArgumentException - if the given type is invalid
161 | 
162 | ---
163 | 
164 | ### getCustomObject
165 | 
166 | **Signature:** `static getCustomObject(type : String, keyValue : String) : CustomObject`
167 | 
168 | **Description:** Returns a custom object based on it's type and unique key.
169 | 
170 | **Parameters:**
171 | 
172 | - `type`: The name of the custom object type.
173 | - `keyValue`: The unique key value.
174 | 
175 | **Returns:**
176 | 
177 | The matching custom object instance or null in case no matching custom object instance could be found.
178 | 
179 | **Throws:**
180 | 
181 | IllegalArgumentException - if the given type is invalid
182 | 
183 | ---
184 | 
185 | ### getCustomObject
186 | 
187 | **Signature:** `static getCustomObject(type : String, keyValue : Number) : CustomObject`
188 | 
189 | **Description:** Returns a custom object based on it's type and unique key.
190 | 
191 | **Parameters:**
192 | 
193 | - `type`: The name of the custom object type.
194 | - `keyValue`: The unique key value.
195 | 
196 | **Returns:**
197 | 
198 | The matching custom object instance or null in case no matching custom object instance could be found.
199 | 
200 | **Throws:**
201 | 
202 | IllegalArgumentException - if the given type is invalid
203 | 
204 | ---
205 | 
206 | ### queryCustomObject
207 | 
208 | **Signature:** `static queryCustomObject(type : String, queryString : String, args : Object...) : CustomObject`
209 | 
210 | **Description:** Searches for a single custom object instance. The search can be configured using a simple query language, which provides most common filter and operator functionality. The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The following operators are supported in a condition: = Equals - All types; supports NULL value (thumbnail = NULL) != Not equals - All types; supports NULL value (thumbnail != NULL) < Less than - Integer, Number and Date types only > Greater than - Integer, Number and Date types only <= Less or equals than - Integer, Number and Date types only >= Greater or equals than - Integer, Number and Date types only LIKE Like - String types and Email only; use if leading or trailing wildcards will be used to support substring search(custom.country LIKE 'US*') ILIKE Case-independent Like - String types and Email only, use to support case insensitive query (custom.country ILIKE 'usa'), does also support wildcards for substring matching Conditions can be combined using logical expressions 'AND', 'OR' and 'NOT' and nested using parenthesis e.g. gender = {1} AND (age >= {2} OR (NOT profession LIKE {3})). The query language provides a placeholder syntax to pass objects as additional search parameters. Each passed object is related to a placeholder in the query string. The placeholder must be an Integer that is surrounded by braces. The first Integer value must be '0', the second '1' and so on, e.g. querySystemObjects("sample", "age = {0} or creationDate >= {1}", 18, date) If there is more than one object matching the specified query criteria, the result is not deterministic. In order to retrieve a single object from a sorted result set it is recommended to use the following code: queryCustomObjects("", "custom.myAttr asc", null).first(). The method first() returns only the next element and closes the iterator. This method does not consider locale specific attributes. It returns all objects by checking the default non-localizable attributes. Any locale specific filtering after fetching the objects must be done by other custom code. Example: Get the custom objects using this method with non-localized attributes query. Access the obj.getCustom("myattr"). It returns the localized value of the attribute.
211 | 
212 | **Parameters:**
213 | 
214 | - `type`: the custom object type for the query.
215 | - `queryString`: the actual query.
216 | - `args`: optional parameters for the queryString.
217 | 
218 | **Returns:**
219 | 
220 | the custom object defined by type which was found when executing the queryString.
221 | 
222 | **Throws:**
223 | 
224 | IllegalArgumentException - if the given type is invalid
225 | 
226 | ---
227 | 
228 | ### queryCustomObjects
229 | 
230 | **Signature:** `static queryCustomObjects(type : String, queryString : String, sortString : String, args : Object...) : SeekableIterator`
231 | 
232 | **Description:** Searches for custom object instances. The search can be configured using a simple query language, which provides most common filter and operator functionality. The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The following operators are supported in a condition: = Equals - All types; supports NULL value (thumbnail = NULL) != Not equals - All types; supports NULL value (thumbnail != NULL) < Less than - Integer, Number and Date types only > Greater than - Integer, Number and Date types only <= Less or equals than - Integer, Number and Date types only >= Greater or equals than - Integer, Number and Date types only LIKE Like - String types and Email only; use if leading or trailing wildcards will be used to support substring search(custom.country LIKE 'US*') ILIKE Caseindependent Like - String types and Email only, use to support case insensitive query (custom.country ILIKE 'usa'), does also support wildcards for substring matching Conditions can be combined using logical expressions 'AND', 'OR' and 'NOT' and nested using parenthesis e.g. gender = {1} AND (age >= {2} OR (NOT profession LIKE {3})). The query language provides a placeholder syntax to pass objects as additional search parameters. Each passed object is related to a placeholder in the query string. The placeholder must be an Integer that is surrounded by braces. The first Integer value must be '0', the second '1' and so on, e.g. querySystemObjects("sample", "age = {0} or creationDate >= {1}", 18, date) The sorting parameter is optional and may contain a comma separated list of attribute names to sort by. Each sort attribute name may be followed by an optional sort direction specifier ('asc' | 'desc'). Default sorting directions is ascending, if no direction was specified. Example: age desc, name Please note that specifying a localized custom attribute as the sorting attribute is currently not supported. Sometimes it is desired to get all instances of specified type with a special sorting condition. This can be easily done by providing the 'type' of the custom object and the 'sortString' in combination with an empty 'queryString', e.g. queryCustomObjects("sample", "", "custom.myAttr asc") It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources. This method does not consider locale specific attributes. It returns all objects by checking the default non-localizable attributes. Any locale specific filtering after fetching the objects must be done by other custom code. Example: Get the custom objects using this method with non-localized attributes query. Access the obj.getCustom("myattr"). It returns the localized value of the attribute.
233 | 
234 | **Parameters:**
235 | 
236 | - `type`: the custom object type for the query.
237 | - `queryString`: the actual query.
238 | - `sortString`: an optional sorting or null if no sorting is necessary.
239 | - `args`: optional parameters for the queryString.
240 | 
241 | **Returns:**
242 | 
243 | SeekableIterator containing the result set of the query.
244 | 
245 | **See Also:**
246 | 
247 | SeekableIterator.close()
248 | 
249 | **Throws:**
250 | 
251 | IllegalArgumentException - if the given type is invalid
252 | 
253 | ---
254 | 
255 | ### queryCustomObjects
256 | 
257 | **Signature:** `static queryCustomObjects(type : String, queryAttributes : Map, sortString : String) : SeekableIterator`
258 | 
259 | **Description:** Searches for custom object instances. The search can be configured with a map, which key-value pairs are converted into a query expression. The key-value pairs are turned into a sequence of '=' or 'like' conditions, which are combined with AND statements. Example: A map with the key/value pairs: 'name'/'tom*', 'age'/66 will be converted as follows: "name like 'tom*' and age = 66" The identifier for an attribute to use in a query condition is always the ID of the attribute as defined in the type definition. For custom defined attributes the prefix custom is required in the search term (e.g. custom.color = {1}), while for system attributes no prefix is used (e.g. name = {4}). Supported attribute value types with sample expression values: String 'String', 'Str*', 'Strin?' Integer 1, 3E4 Number 1.0, 3.99E5 Date yyyy-MM-dd e.g. 2007-05-31 (Default TimeZone = UTC) DateTime yyyy-MM-dd'T'hh:mm:ss+Z e.g. 2007-05-31T00:00+Z (Z TimeZone = UTC) or 2007-05-31T00:00:00 Boolean true, false Email '[email protected]', '*@demandware.com' Set of String 'String', 'Str*', 'Strin?' Set of Integer 1, 3E4 Set of Number 1.0, 3.99E5 Enum of String 'String', 'Str*', 'Strin?' Enum of Integer 1, 3E4 The following types of attributes are not queryable: Image HTML Text Quantity Password Note, that some system attributes are not queryable by default regardless of the actual value type code. The sorting parameter is optional and may contain a comma separated list of attribute names to sort by. Each sort attribute name may be followed by an optional sort direction specifier ('asc' | 'desc'). Default sorting directions is ascending, if no direction was specified. Example: age desc, name Please note that specifying a localized custom attribute as the sorting attribute is currently not supported. It is strongly recommended to call close() on the returned SeekableIterator if not all of its elements are being retrieved. This will ensure the proper cleanup of system resources. This method does not consider locale specific attributes. It returns all objects by checking the default non-localizable attributes. Any locale specific filtering after fetching the objects must be done by other custom code. Example: Get the custom objects using this method with non-localized attributes query. Access the obj.getCustom("myattr"). It returns the localized value of the attribute.
260 | 
261 | **Parameters:**
262 | 
263 | - `type`: the custom object type for the query.
264 | - `queryAttributes`: key-value pairs, which define the query.
265 | - `sortString`: an optional sorting or null if no sorting is necessary.
266 | 
267 | **Returns:**
268 | 
269 | SeekableIterator containing the result set of the query.
270 | 
271 | **See Also:**
272 | 
273 | SeekableIterator.close()
274 | 
275 | **Throws:**
276 | 
277 | IllegalArgumentException - if the given type is invalid
278 | 
279 | ---
280 | 
281 | ### remove
282 | 
283 | **Signature:** `static remove(object : CustomObject) : void`
284 | 
285 | **Description:** Removes a given custom object.
286 | 
287 | **Parameters:**
288 | 
289 | - `object`: the custom object to remove, must not be null.
290 | 
291 | ---
```

--------------------------------------------------------------------------------
/docs/best-practices/localserviceregistry.md:
--------------------------------------------------------------------------------

```markdown
  1 | # SFCC B2C Commerce: LocalServiceRegistry Best Practices
  2 | 
  3 | This guide provides a concise overview of best practices for creating server-to-server integrations in Salesforce B2C Commerce Cloud using the `dw.svc.LocalServiceRegistry`.
  4 | 
  5 | ## 1. Core Architecture: Configuration and Code
  6 | 
  7 | Integrations use a two-par5. Click **Apply** to save the new profile.
  8 | 
  9 | ### Step 3: Create the Service Definition (Tying It All Together)
 10 | 
 11 | The Service Definition links the Credential and Profile to create the final, named service that you will call from your code.
 12 | 
 13 | 1. Navigate to **Administration > Operations > Services**.
 14 | 2. Ensure you are on the **Services** tab and click **New**.
 15 | 3. Fill in the following fields on the New Service page:
 16 |    - **Name (ID)**: Enter the unique ID for the service. This ID must exactly match the one used in your `LocalServiceRegistry.createService()` call.
 17 |      
 18 |      **Best Practice**: Use a period-delimited pattern like `{cartridge}.{protocol}.{service}.{operation}` (e.g., `int_myapi.http.customer.get`). This structure automatically organizes your service logs into a helpful hierarchy.
 19 |    
 20 |    - **Service Type**: Select the protocol, typically HTTP for REST APIs.
 21 |    
 22 |    - **Mode**:
 23 |      - **Live**: For making real calls to the external API.
 24 |      - **Mocked**: For testing. This mode will invoke the `mockCall` or `mockFull` function in your script instead of making a network request.
 25 |    
 26 |    - **Credential**: Select the Service Credential you created in Step 1 from the dropdown list.
 27 |    
 28 |    - **Profile**: Select the Service Profile you configured in Step 2 from the dropdown list.
 29 |    
 30 |    - **Log Name Prefix**: (Optional but recommended) Enter a prefix (e.g., `myapi`) to create a dedicated log file for this service (`service-myapi-....log`), which simplifies debugging.
 31 |    
 32 |    - **Communication Log Enabled**: Check this box to log the full request and response data. This is useful for debugging but should be used with caution in production if sensitive data is being transmitted. Always implement a `filterLogMessage` callback in your script to redact sensitive information from these logs.
 33 | 
 34 | 4. Click **Apply** to save the service definition.
 35 | 
 36 | Your service is now fully configured in the Business Manager and ready to be implemented and called from your SFRA cartridge code.rchitecture:
 37 | 
 38 | **Declarative Configuration (Business Manager)**: Defines the what, who, and how of the service call. This includes the endpoint, credentials, and operational policies like timeouts and circuit breakers.
 39 | 
 40 | **Programmatic Definition (Script Module)**: Defines the dynamic behavior using callbacks. This includes creating the request payload, parsing the response, and defining mock behavior for testing.
 41 | 
 42 | ### Business Manager Configuration Summary
 43 | 
 44 | | Component | Purpose | Key Settings |
 45 | |-----------|---------|--------------|
 46 | | Credential | Stores endpoint URL and authentication details. | ID, URL, User, Password |
 47 | | Profile | Defines operational behavior and resilience. | Timeout, Rate Limiting, Circuit Breaker |
 48 | | Service | Binds a Credential and Profile to a named service ID. | Name (ID), Service Type, Mode (Live/Mocked) |
 49 | 
 50 | **Best Practice**: Use a period-delimited naming convention for the Service ID (e.g., `int_myapi.http.customer.get`) to organize logs effectively.
 51 | 
 52 | ## 2. Script Implementation with LocalServiceRegistry
 53 | 
 54 | The modern approach uses `dw.svc.LocalServiceRegistry` to define service behavior locally, eliminating the need for global initialization scripts.
 55 | 
 56 | ### Key Callbacks
 57 | 
 58 | The `createService` method takes a configuration object with the following core callback functions:
 59 | 
 60 | - **`createRequest(svc,...params)`**: Configures the outgoing request (URL, method, headers) and returns the request body (e.g., a JSON string).
 61 | 
 62 | - **`parseResponse(svc, client)`**: Processes the raw HTTP response (`dw.net.HTTPClient`). It should parse the response body (e.g., `JSON.parse(client.text)`) and throw an Error if the status code indicates a failure (e.g., `client.statusCode >= 400`). This ensures the `result.ok` flag is set correctly.
 63 | 
 64 | - **`mockCall(svc, requestObj)`**: Mocks only the network execution. `createRequest` and `parseResponse` still run. Ideal for integration testing your service's logic.
 65 | 
 66 | - **`mockFull(svc,...params)`**: Mocks the entire service call. No other callbacks are executed. Ideal for unit testing the consumer of the service (e.g., a controller).
 67 | 
 68 | ### Reusable Service Module Pattern
 69 | 
 70 | Encapsulate all logic for an integration into a single script module. Use a singleton pattern to avoid re-creating the service definition on every call.
 71 | 
 72 | ```javascript
 73 | 'use strict';
 74 | 
 75 | var LocalServiceRegistry = require('dw/svc/LocalServiceRegistry');
 76 | var Logger = require('dw/system/Logger');
 77 | 
 78 | // --- Private Helper for Operation-Specific Details ---
 79 | /**
 80 |  * Returns configuration details for a specific service operation.
 81 |  * @param {string} operation - The operation ID (e.g., 'getCustomer')
 82 |  * @returns {{method: string, path: string}}
 83 |  */
 84 | function getOperationDetails(operation) {
 85 |     switch (operation) {
 86 |         case 'getCustomer':
 87 |             return { method: 'GET', path: '/customers/' };
 88 |         case 'createCustomer':
 89 |             return { method: 'POST', path: '/customers' };
 90 |         default:
 91 |             Logger.error('Unknown operation in MyAPIService: {0}', operation);
 92 |             throw new Error('Operation not implemented: ' + operation);
 93 |     }
 94 | }
 95 | 
 96 | // --- Service Definition ---
 97 | var myAPIService = LocalServiceRegistry.createService('int_myapi.http.customer', {
 98 |     /**
 99 |      * @param {dw.svc.HTTPService} svc
100 |      * @param {string} operation - The name of the operation to perform
101 |      * @param {Object} params - The parameters for the operation
102 |      * @returns {string|null} - Request body or null
103 |      */
104 |     createRequest: function (svc, operation, params) {
105 |         var details = getOperationDetails(operation);
106 |         var credential = svc.getConfiguration().getCredential();
107 | 
108 |         svc.setRequestMethod(details.method);
109 |         svc.addHeader('Content-Type', 'application/json');
110 |         svc.addHeader('X-API-Key', 'your-api-key'); // Example custom header
111 | 
112 |         var url = credential.getURL() + details.path;
113 |         if (details.method === 'GET' && params && params.id) {
114 |             svc.setURL(url + params.id);
115 |             return null; // No body for GET
116 |         }
117 |         svc.setURL(url);
118 |         return params? JSON.stringify(params) : null;
119 |     },
120 | 
121 |     /**
122 |      * @param {dw.svc.HTTPService} svc
123 |      * @param {dw.net.HTTPClient} client
124 |      * @returns {Object} - The parsed JSON response
125 |      */
126 |     parseResponse: function (svc, client) {
127 |         if (client.statusCode >= 400) {
128 |             Logger.error('MyAPIService error: {0} {1} - {2}', client.statusCode, client.statusMessage, client.text);
129 |             throw new Error('API call failed with status ' + client.statusCode);
130 |         }
131 |         try {
132 |             return JSON.parse(client.text);
133 |         } catch (e) {
134 |             Logger.error('Error parsing JSON response from MyAPIService: {0}', client.text);
135 |             throw new Error('Invalid JSON response');
136 |         }
137 |     },
138 | 
139 |     /**
140 |      * @param {dw.svc.HTTPService} svc
141 |      * @param {string} operation
142 |      * @param {Object} params
143 |      * @returns {Object} - The final, parsed mock object
144 |      */
145 |     mockFull: function (svc, operation, params) {
146 |         var details = getOperationDetails(operation);
147 |         if (details.method === 'GET') {
148 |             return { id: params.id, name: 'Mock Customer', email: '[email protected]' };
149 |         }
150 |         if (details.method === 'POST') {
151 |             return { id: 'mock-' + new Date().getTime(), name: params.name, email: params.email };
152 |         }
153 |         return { error: 'Mock not implemented for ' + operation };
154 |     },
155 |     
156 |     /**
157 |      * Redacts sensitive data from logs.
158 |      * @param {string} msg - The log message
159 |      * @returns {string} - The filtered message
160 |      */
161 |     filterLogMessage: function (msg) {
162 |         // Basic redaction example for JSON strings
163 |         try {
164 |             var logObject = JSON.parse(msg);
165 |             if (logObject.password) {
166 |                 logObject.password = '<REDACTED>';
167 |             }
168 |             return JSON.stringify(logObject);
169 |         } catch (e) {
170 |             return msg; // Not a JSON message, return as is
171 |         }
172 |     }
173 | });
174 | 
175 | // --- Public API ---
176 | module.exports = {
177 |     OPERATION_GET_CUSTOMER: 'getCustomer',
178 |     OPERATION_CREATE_CUSTOMER: 'createCustomer',
179 |     call: function (operation, params) {
180 |         return myAPIService.call(operation, params);
181 |     }
182 | };
183 | ```
184 | ## 3. Practical Examples
185 | 
186 | ### GET Request (SFRA Controller)
187 | 
188 | ```javascript
189 | // In controller/MyController.js
190 | var server = require('server');
191 | var MyAPIService = require('~/cartridge/scripts/services/MyAPIService');
192 | 
193 | server.get('ShowCustomer', function(req, res, next) {
194 |     var customerId = req.querystring.id;
195 |     var result = MyAPIService.call(MyAPIService.OPERATION_GET_CUSTOMER, { id: customerId });
196 | 
197 |     if (result.ok) {
198 |         res.render('customer/customerDetails', {
199 |             customer: result.object
200 |         });
201 |     } else {
202 |         res.render('error', {
203 |             message: 'Could not retrieve customer data.',
204 |             error: result.errorMessage
205 |         });
206 |     }
207 |     next();
208 | });
209 | ```
210 | 
211 | ### POST Request with JSON (SFRA Controller)
212 | 
213 | ```javascript
214 | // In controller/MyController.js
215 | var server = require('server');
216 | var MyAPIService = require('~/cartridge/scripts/services/MyAPIService');
217 | 
218 | server.post('CreateCustomer', function(req, res, next) {
219 |     var customerData = {
220 |         name: req.form.name,
221 |         email: req.form.email
222 |     };
223 |     var result = MyAPIService.call(MyAPIService.OPERATION_CREATE_CUSTOMER, customerData);
224 | 
225 |     if (result.ok) {
226 |         res.json({ success: true, customer: result.object });
227 |     } else {
228 |         res.setStatusCode(500);
229 |         res.json({ success: false, error: result.errorMessage });
230 |     }
231 |     next();
232 | });
233 | ```
234 | 
235 | ### OAuth 2.0 Client Credentials Flow
236 | 
237 | Use a **Two-Service Pattern** for efficiency: one service to get the token, and another to call the API.
238 | 
239 | - **Auth Service (AuthTokenService.js)**: Handles the POST request to the token endpoint.
240 | 
241 | - **API Service (MyAPIService.js)**:
242 |   - Gets the token from the Auth Service.
243 |   - Caches the token (`dw.system.CacheMgr`) to avoid re-authenticating on every call.
244 |   - Adds the `Authorization: Bearer <token>` header in its `createRequest` callback.
245 | 
246 | ```javascript
247 | // Conceptual snippet for API Service createRequest with OAuth
248 | createRequest: function (svc, params) {
249 |     var CacheMgr = require('dw/system/CacheMgr');
250 |     var AuthTokenService = require('~/cartridge/scripts/services/AuthTokenService');
251 |     
252 |     // Get token from cache or fetch a new one
253 |     var tokenCache = CacheMgr.getCache('MyAPIToken');
254 |     var token = tokenCache.get('access_token', function () {
255 |         var result = AuthTokenService.call();
256 |         if (result.ok && result.object.access_token) {
257 |             // Set cache expiry to slightly less than the token's actual expiry
258 |             tokenCache.put('access_token', result.object.access_token, result.object.expires_in - 300);
259 |             return result.object.access_token;
260 |         }
261 |         return null;
262 |     });
263 | 
264 |     if (!token) {
265 |         throw new Error('Unable to retrieve valid API token.');
266 |     }
267 | 
268 |     svc.setRequestMethod('GET');
269 |     svc.addHeader('Authorization', 'Bearer ' + token);
270 |     //... set URL and other request details...
271 |     return null;
272 | }
273 | ```
274 | 
275 | ## 4. Step-by-Step Business Manager Configuration Guide
276 | 
277 | This guide walks through the three essential parts of setting up a new service in the Business Manager: creating the Credential, configuring the Profile, and defining the Service itself.
278 | 
279 | ### Step 1: Create the Service Credential (The "Who" and "What")
280 | 
281 | The Service Credential securely stores the endpoint URL and authentication details for the external system.
282 | 
283 | 1. Navigate to **Administration > Operations > Services**.
284 | 2. Click the **Credentials** tab.
285 | 3. On the Service Credentials page, click **New**.
286 | 4. Fill in the following fields on the New Service Credential page:
287 |    - **ID**: Enter a unique identifier. A recommended naming convention is `your.service.name.http.credentials`. This name cannot contain spaces.
288 |    - **URL**: Enter the base URL for the third-party API. For example: `https://api.example.com/v2/`.
289 |    - **User**: If using Basic Authentication, enter the Client ID or username provided by the third-party service.
290 |    - **Password**: Enter the Client Secret or password. For security, this field is write-only. Once saved, the value cannot be viewed again, so be sure to store it in a secure location.
291 | 5. Click **Apply** to save the new credential.
292 | 
293 | ### Step 2: Configure the Service Profile (The "How")
294 | 
295 | The Service Profile defines the operational behavior, such as timeouts and resilience patterns, for the service call.
296 | 
297 | 1. Navigate to **Administration > Operations > Services**.
298 | 2. Click the **Profiles** tab.
299 | 3. On the Service Profiles page, click **New**.
300 | 4. Fill in the following fields on the New Service Profile page:
301 |    - **Name (ID)**: Enter a descriptive name for the profile (e.g., `default-api-profile`, `realtime-payment-profile`).
302 |    - **Timeout**: Enter the connection timeout in milliseconds (e.g., `10000` for 10 seconds). This is the maximum time B2C Commerce will wait for a response before the call fails.
303 |    - **Enable Circuit Breaker**: Check this box to enable this crucial fault-tolerance feature. It is highly recommended to always enable this.
304 |      - **Calls**: The number of failed calls that will "trip" the circuit (e.g., `10`).
305 |      - **Interval (ms)**: The time window in which the failures must occur (e.g., `60000` for 1 minute). When the circuit is tripped, B2C Commerce will stop making calls to the service for a period to allow the external system to recover.
306 |    - **Enable Rate Limit**: Check this box if the third-party API has call limits.
307 |      - **Calls**: The maximum number of calls allowed in the interval (e.g., `1000`).
308 |      - **Interval (ms)**: The time window for the rate limit in milliseconds (e.g., `60000` for 1 minute).
309 | 5. Click **Apply** to save the new profile.
310 | 
311 | ### Step 3: Create the Service Definition (Tying It All Together)
312 | 
313 | The Service Definition links the Credential and Profile to create the final, named service that you will call from your code.
314 | 
315 | 1. Navigate to **Administration > Operations > Services**.
316 | 2. Ensure you are on the **Services** tab and click **New**.
317 | 3. Fill in the following fields on the New Service page:
318 |    - **Name (ID)**: Enter the unique ID for the service. This ID must exactly match the one used in your `LocalServiceRegistry.createService()` call.
319 |      
320 |      **Best Practice**: Use a period-delimited pattern like `{cartridge}.{protocol}.{service}.{operation}` (e.g., `int_myapi.http.customer.get`). This structure automatically organizes your service logs into a helpful hierarchy.
321 |    
322 |    - **Service Type**: Select the protocol, typically HTTP for REST APIs.
323 | 
324 |    - **Enabled**: Check this box to enable the service.
325 | 
326 |    - **Service Mode**:
327 |      - **Live**: For making real calls to the external API.
328 |      - **Mocked**: For testing. This mode will invoke the `mockCall` or `mockFull` function in your script instead of making a network request.
329 |    
330 |    - **Log Name Prefix**: (Optional but recommended) Enter a prefix (e.g., `myapi`) to create a dedicated log file for this service (`service-myapi-....log`), which simplifies debugging.
331 |    
332 |    - **Communication Log Enabled**: Check this box to log the full request and response data. This is useful for debugging but should be used with caution in production if sensitive data is being transmitted. Always implement a `filterLogMessage` callback in your script to redact sensitive information from these logs.
333 | 
334 |    - **Force PRD Behavior in Non-PRD Environments**: Check this box to force the service to behave as if it is in a production environment, even when it is not. This can be useful for testing how the service will behave in production.
335 | 
336 |    - **Profile**: Select the Service Profile you configured in Step 2 from the dropdown list.
337 | 
338 |    - **Credential**: Select the Service Credential you created in Step 1 from the dropdown list.
339 | 
340 | 4. Click **Apply** to save the service definition.Your service is now fully configured in the Business Manager and ready to be implemented and called from your SFRA cartridge code.
```

--------------------------------------------------------------------------------
/docs/dw_customer/CustomerActiveData.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.customer
  2 | 
  3 | # Class CustomerActiveData
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.object.PersistentObject
  9 |   - dw.object.ExtensibleObject
 10 |     - dw.object.ActiveData
 11 |       - dw.customer.CustomerActiveData
 12 | 
 13 | ## Description
 14 | 
 15 | Represents the active data for a Customer in Commerce Cloud Digital. Note: this class allows access to sensitive personal and private information. Pay attention to appropriate legal and regulatory requirements when developing.
 16 | 
 17 | ## Properties
 18 | 
 19 | ### avgOrderValue
 20 | 
 21 | **Type:** Number (Read Only)
 22 | 
 23 | The average order value of the customer, or null
 24 |  if none has been set or the value is no longer valid.
 25 | 
 26 | ### discountValueWithCoupon
 27 | 
 28 | **Type:** Number (Read Only)
 29 | 
 30 | The discount value resulting from coupons, that has been applied
 31 |  to orders of the customer, or null if none has been set or
 32 |  the value is no longer valid.
 33 | 
 34 | ### discountValueWithoutCoupon
 35 | 
 36 | **Type:** Number (Read Only)
 37 | 
 38 | The discount value resulting from promotions other than coupons,
 39 |  that has been applied to orders of the customer, or null
 40 |  if none has been set or the value is no longer valid.
 41 | 
 42 | ### giftOrders
 43 | 
 44 | **Type:** Number (Read Only)
 45 | 
 46 | The number of orders for the Customer that contained at least
 47 |  one product unit marked as a gift, or null if none has been
 48 |  set or the value is no longer valid.
 49 | 
 50 | ### giftUnits
 51 | 
 52 | **Type:** Number (Read Only)
 53 | 
 54 | The number of product units in orders for the customer
 55 |  that were marked as a gift, or null if none has been set
 56 |  or the value is no longer valid.
 57 | 
 58 | ### lastOrderDate
 59 | 
 60 | **Type:** Date (Read Only)
 61 | 
 62 | The date of the last order for the customer, or null
 63 |  if there are no orders for the customer.
 64 | 
 65 | ### orders
 66 | 
 67 | **Type:** Number (Read Only)
 68 | 
 69 | The orders of the customer, or null if none
 70 |  has been set or the value is no longer valid.
 71 | 
 72 | ### orderValue
 73 | 
 74 | **Type:** Number (Read Only)
 75 | 
 76 | The lifetime order value of the customer, or null
 77 |  if none has been set or the value is no longer valid.
 78 | 
 79 | ### orderValueMonth
 80 | 
 81 | **Type:** Number (Read Only)
 82 | 
 83 | The order value of the customer, over the most recent 30 days,
 84 |  or null if none has been set or the value is no longer valid.
 85 | 
 86 | ### productMastersOrdered
 87 | 
 88 | **Type:** String (Read Only)
 89 | 
 90 | An array containing the master product SKUs of variation products
 91 |  in orders for the customer, or an empty collection if no SKUs have been
 92 |  set or the collection of SKUs is no longer valid. There is no specific
 93 |  limit on the number of SKUs that will be returned in the collection, but
 94 |  there is also no guarantee that it will contain the SKUs for all products
 95 |  ordered by the customer.
 96 | 
 97 | ### productsAbandonedMonth
 98 | 
 99 | **Type:** String (Read Only)
100 | 
101 | An array containing the SKUs of products in baskets abandoned
102 |  by the customer in the last 30 days, or an empty collection if no SKUs
103 |  have been set or the collection is no longer valid.  There is no specific
104 |  limit on the number of SKUs that will be returned in the collection, but
105 |  there is also no guarantee that it will contain the SKUs for all products
106 |  in baskets abandoned by the customer.
107 | 
108 | ### productsOrdered
109 | 
110 | **Type:** String (Read Only)
111 | 
112 | An array containing the SKUs of products in orders
113 |  for the customer, or an empty collection if no SKUs have been set or the
114 |  collection of SKUs is no longer valid.  There is no specific limit on the
115 |  number of SKUs that will be returned in the collection, but there is also
116 |  no guarantee that it will contain the SKUs for all products ordered by
117 |  the customer.
118 | 
119 | ### productsViewedMonth
120 | 
121 | **Type:** String (Read Only)
122 | 
123 | An array containing the SKUs of products viewed by the
124 |  customer in the last 30 days, or an empty collection if no SKUs have been
125 |  set or the collection is no longer valid.  There is no specific limit on
126 |  the number of SKUs that will be returned in the collection, but there is
127 |  also no guarantee that it will contain the SKUs for all products viewed
128 |  by the customer.
129 | 
130 | ### returns
131 | 
132 | **Type:** Number (Read Only)
133 | 
134 | The number of returns of the customer, or null
135 |  if none has been set or the value is no longer valid.
136 | 
137 | ### returnValue
138 | 
139 | **Type:** Number (Read Only)
140 | 
141 | The returned revenue of the customer, or null
142 |  if none has been set or the value is no longer valid.
143 | 
144 | ### sourceCodeOrders
145 | 
146 | **Type:** Number (Read Only)
147 | 
148 | The number of orders for the customer where a source code was
149 |  in effect, or null if none has been set or the value
150 |  is no longer valid.
151 | 
152 | ### topCategoriesOrdered
153 | 
154 | **Type:** String (Read Only)
155 | 
156 | An array containing the IDs of up to the top 20 categories for
157 |  customer orders, or an empty list if no categories have been set or the
158 |  list of categories is no longer valid. The top category is the one for
159 |  which the most orders for the customer contained at least one product
160 |  found in that category.
161 | 
162 | ### visitsMonth
163 | 
164 | **Type:** Number (Read Only)
165 | 
166 | The visits of the customer, over the most recent 30 days,
167 |  or null if none has been set or the value
168 |  is no longer valid.
169 | 
170 | ### visitsWeek
171 | 
172 | **Type:** Number (Read Only)
173 | 
174 | The visits of the customer, over the most recent 7 days,
175 |  or null if none has been set or the value
176 |  is no longer valid.
177 | 
178 | ### visitsYear
179 | 
180 | **Type:** Number (Read Only)
181 | 
182 | The visits of the customer, over the most recent 365 days,
183 |  or null if none has been set or the value
184 |  is no longer valid.
185 | 
186 | ## Constructor Summary
187 | 
188 | ## Method Summary
189 | 
190 | ### getAvgOrderValue
191 | 
192 | **Signature:** `getAvgOrderValue() : Number`
193 | 
194 | Returns the average order value of the customer, or null if none has been set or the value is no longer valid.
195 | 
196 | ### getDiscountValueWithCoupon
197 | 
198 | **Signature:** `getDiscountValueWithCoupon() : Number`
199 | 
200 | Returns the discount value resulting from coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid.
201 | 
202 | ### getDiscountValueWithoutCoupon
203 | 
204 | **Signature:** `getDiscountValueWithoutCoupon() : Number`
205 | 
206 | Returns the discount value resulting from promotions other than coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid.
207 | 
208 | ### getGiftOrders
209 | 
210 | **Signature:** `getGiftOrders() : Number`
211 | 
212 | Returns the number of orders for the Customer that contained at least one product unit marked as a gift, or null if none has been set or the value is no longer valid.
213 | 
214 | ### getGiftUnits
215 | 
216 | **Signature:** `getGiftUnits() : Number`
217 | 
218 | Returns the number of product units in orders for the customer that were marked as a gift, or null if none has been set or the value is no longer valid.
219 | 
220 | ### getLastOrderDate
221 | 
222 | **Signature:** `getLastOrderDate() : Date`
223 | 
224 | Returns the date of the last order for the customer, or null if there are no orders for the customer.
225 | 
226 | ### getOrders
227 | 
228 | **Signature:** `getOrders() : Number`
229 | 
230 | Returns the orders of the customer, or null if none has been set or the value is no longer valid.
231 | 
232 | ### getOrderValue
233 | 
234 | **Signature:** `getOrderValue() : Number`
235 | 
236 | Returns the lifetime order value of the customer, or null if none has been set or the value is no longer valid.
237 | 
238 | ### getOrderValueMonth
239 | 
240 | **Signature:** `getOrderValueMonth() : Number`
241 | 
242 | Returns the order value of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid.
243 | 
244 | ### getProductMastersOrdered
245 | 
246 | **Signature:** `getProductMastersOrdered() : String[]`
247 | 
248 | Returns an array containing the master product SKUs of variation products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid.
249 | 
250 | ### getProductsAbandonedMonth
251 | 
252 | **Signature:** `getProductsAbandonedMonth() : String[]`
253 | 
254 | Returns an array containing the SKUs of products in baskets abandoned by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid.
255 | 
256 | ### getProductsOrdered
257 | 
258 | **Signature:** `getProductsOrdered() : String[]`
259 | 
260 | Returns an array containing the SKUs of products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid.
261 | 
262 | ### getProductsViewedMonth
263 | 
264 | **Signature:** `getProductsViewedMonth() : String[]`
265 | 
266 | Returns an array containing the SKUs of products viewed by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid.
267 | 
268 | ### getReturns
269 | 
270 | **Signature:** `getReturns() : Number`
271 | 
272 | Returns the number of returns of the customer, or null if none has been set or the value is no longer valid.
273 | 
274 | ### getReturnValue
275 | 
276 | **Signature:** `getReturnValue() : Number`
277 | 
278 | Returns the returned revenue of the customer, or null if none has been set or the value is no longer valid.
279 | 
280 | ### getSourceCodeOrders
281 | 
282 | **Signature:** `getSourceCodeOrders() : Number`
283 | 
284 | Returns the number of orders for the customer where a source code was in effect, or null if none has been set or the value is no longer valid.
285 | 
286 | ### getTopCategoriesOrdered
287 | 
288 | **Signature:** `getTopCategoriesOrdered() : String[]`
289 | 
290 | Returns an array containing the IDs of up to the top 20 categories for customer orders, or an empty list if no categories have been set or the list of categories is no longer valid.
291 | 
292 | ### getVisitsMonth
293 | 
294 | **Signature:** `getVisitsMonth() : Number`
295 | 
296 | Returns the visits of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid.
297 | 
298 | ### getVisitsWeek
299 | 
300 | **Signature:** `getVisitsWeek() : Number`
301 | 
302 | Returns the visits of the customer, over the most recent 7 days, or null if none has been set or the value is no longer valid.
303 | 
304 | ### getVisitsYear
305 | 
306 | **Signature:** `getVisitsYear() : Number`
307 | 
308 | Returns the visits of the customer, over the most recent 365 days, or null if none has been set or the value is no longer valid.
309 | 
310 | ## Method Detail
311 | 
312 | ## Method Details
313 | 
314 | ### getAvgOrderValue
315 | 
316 | **Signature:** `getAvgOrderValue() : Number`
317 | 
318 | **Description:** Returns the average order value of the customer, or null if none has been set or the value is no longer valid.
319 | 
320 | **Returns:**
321 | 
322 | the average order size.
323 | 
324 | ---
325 | 
326 | ### getDiscountValueWithCoupon
327 | 
328 | **Signature:** `getDiscountValueWithCoupon() : Number`
329 | 
330 | **Description:** Returns the discount value resulting from coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid.
331 | 
332 | **Returns:**
333 | 
334 | the discount value resulting from coupons.
335 | 
336 | ---
337 | 
338 | ### getDiscountValueWithoutCoupon
339 | 
340 | **Signature:** `getDiscountValueWithoutCoupon() : Number`
341 | 
342 | **Description:** Returns the discount value resulting from promotions other than coupons, that has been applied to orders of the customer, or null if none has been set or the value is no longer valid.
343 | 
344 | **Returns:**
345 | 
346 | the discount value resulting from promotions other than coupons.
347 | 
348 | ---
349 | 
350 | ### getGiftOrders
351 | 
352 | **Signature:** `getGiftOrders() : Number`
353 | 
354 | **Description:** Returns the number of orders for the Customer that contained at least one product unit marked as a gift, or null if none has been set or the value is no longer valid.
355 | 
356 | **Returns:**
357 | 
358 | the number of gift orders.
359 | 
360 | ---
361 | 
362 | ### getGiftUnits
363 | 
364 | **Signature:** `getGiftUnits() : Number`
365 | 
366 | **Description:** Returns the number of product units in orders for the customer that were marked as a gift, or null if none has been set or the value is no longer valid.
367 | 
368 | **Returns:**
369 | 
370 | the number of gift product units.
371 | 
372 | ---
373 | 
374 | ### getLastOrderDate
375 | 
376 | **Signature:** `getLastOrderDate() : Date`
377 | 
378 | **Description:** Returns the date of the last order for the customer, or null if there are no orders for the customer.
379 | 
380 | **Returns:**
381 | 
382 | the date of the last order for the customer.
383 | 
384 | ---
385 | 
386 | ### getOrders
387 | 
388 | **Signature:** `getOrders() : Number`
389 | 
390 | **Description:** Returns the orders of the customer, or null if none has been set or the value is no longer valid.
391 | 
392 | **Returns:**
393 | 
394 | the orders.
395 | 
396 | ---
397 | 
398 | ### getOrderValue
399 | 
400 | **Signature:** `getOrderValue() : Number`
401 | 
402 | **Description:** Returns the lifetime order value of the customer, or null if none has been set or the value is no longer valid.
403 | 
404 | **Returns:**
405 | 
406 | the lifetime value.
407 | 
408 | ---
409 | 
410 | ### getOrderValueMonth
411 | 
412 | **Signature:** `getOrderValueMonth() : Number`
413 | 
414 | **Description:** Returns the order value of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid.
415 | 
416 | **Returns:**
417 | 
418 | the value over the last 30 days.
419 | 
420 | ---
421 | 
422 | ### getProductMastersOrdered
423 | 
424 | **Signature:** `getProductMastersOrdered() : String[]`
425 | 
426 | **Description:** Returns an array containing the master product SKUs of variation products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products ordered by the customer.
427 | 
428 | **Returns:**
429 | 
430 | a collection containing the master product SKUs of variation products that were ordered.
431 | 
432 | ---
433 | 
434 | ### getProductsAbandonedMonth
435 | 
436 | **Signature:** `getProductsAbandonedMonth() : String[]`
437 | 
438 | **Description:** Returns an array containing the SKUs of products in baskets abandoned by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products in baskets abandoned by the customer.
439 | 
440 | **Returns:**
441 | 
442 | a collection containing the SKUs of products that were abandoned.
443 | 
444 | ---
445 | 
446 | ### getProductsOrdered
447 | 
448 | **Signature:** `getProductsOrdered() : String[]`
449 | 
450 | **Description:** Returns an array containing the SKUs of products in orders for the customer, or an empty collection if no SKUs have been set or the collection of SKUs is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products ordered by the customer.
451 | 
452 | **Returns:**
453 | 
454 | a collection containing the SKUs of products that were ordered.
455 | 
456 | ---
457 | 
458 | ### getProductsViewedMonth
459 | 
460 | **Signature:** `getProductsViewedMonth() : String[]`
461 | 
462 | **Description:** Returns an array containing the SKUs of products viewed by the customer in the last 30 days, or an empty collection if no SKUs have been set or the collection is no longer valid. There is no specific limit on the number of SKUs that will be returned in the collection, but there is also no guarantee that it will contain the SKUs for all products viewed by the customer.
463 | 
464 | **Returns:**
465 | 
466 | a collection containing the SKUs of products that were ordered.
467 | 
468 | ---
469 | 
470 | ### getReturns
471 | 
472 | **Signature:** `getReturns() : Number`
473 | 
474 | **Description:** Returns the number of returns of the customer, or null if none has been set or the value is no longer valid.
475 | 
476 | **Returns:**
477 | 
478 | the returns.
479 | 
480 | ---
481 | 
482 | ### getReturnValue
483 | 
484 | **Signature:** `getReturnValue() : Number`
485 | 
486 | **Description:** Returns the returned revenue of the customer, or null if none has been set or the value is no longer valid.
487 | 
488 | **Returns:**
489 | 
490 | the returned revenue.
491 | 
492 | ---
493 | 
494 | ### getSourceCodeOrders
495 | 
496 | **Signature:** `getSourceCodeOrders() : Number`
497 | 
498 | **Description:** Returns the number of orders for the customer where a source code was in effect, or null if none has been set or the value is no longer valid.
499 | 
500 | **Returns:**
501 | 
502 | the number of orders with source codes in effect.
503 | 
504 | ---
505 | 
506 | ### getTopCategoriesOrdered
507 | 
508 | **Signature:** `getTopCategoriesOrdered() : String[]`
509 | 
510 | **Description:** Returns an array containing the IDs of up to the top 20 categories for customer orders, or an empty list if no categories have been set or the list of categories is no longer valid. The top category is the one for which the most orders for the customer contained at least one product found in that category.
511 | 
512 | **Returns:**
513 | 
514 | a list containing the top 20 categories.
515 | 
516 | ---
517 | 
518 | ### getVisitsMonth
519 | 
520 | **Signature:** `getVisitsMonth() : Number`
521 | 
522 | **Description:** Returns the visits of the customer, over the most recent 30 days, or null if none has been set or the value is no longer valid.
523 | 
524 | **Returns:**
525 | 
526 | the visits over the last 30 days.
527 | 
528 | ---
529 | 
530 | ### getVisitsWeek
531 | 
532 | **Signature:** `getVisitsWeek() : Number`
533 | 
534 | **Description:** Returns the visits of the customer, over the most recent 7 days, or null if none has been set or the value is no longer valid.
535 | 
536 | **Returns:**
537 | 
538 | the visits over the last 7 days.
539 | 
540 | ---
541 | 
542 | ### getVisitsYear
543 | 
544 | **Signature:** `getVisitsYear() : Number`
545 | 
546 | **Description:** Returns the visits of the customer, over the most recent 365 days, or null if none has been set or the value is no longer valid.
547 | 
548 | **Returns:**
549 | 
550 | the visits over the last 365 days.
551 | 
552 | ---
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-best-practices.docs-only.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | description: "SFCC Dev MCP Server - search_best_practices Tool Tests (Docs-Only Mode)"
  2 | tests:
  3 |   # Basic functionality tests
  4 |   - it: "should return array of search results for validation query"
  5 |     request:
  6 |       jsonrpc: "2.0"
  7 |       id: "search-validation-1"
  8 |       method: "tools/call"
  9 |       params:
 10 |         name: "search_best_practices"
 11 |         arguments:
 12 |           query: "validation"
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "search-validation-1"
 17 |         result:
 18 |           content:
 19 |             - type: "text"
 20 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
 21 |           isError: false
 22 |       stderr: "toBeEmpty"
 23 | 
 24 |   - it: "should find security-related content in best practices"
 25 |     request:
 26 |       jsonrpc: "2.0"
 27 |       id: "search-security-1"
 28 |       method: "tools/call"
 29 |       params:
 30 |         name: "search_best_practices"
 31 |         arguments:
 32 |           query: "security"
 33 |     expect:
 34 |       response:
 35 |         jsonrpc: "2.0"
 36 |         id: "search-security-1"
 37 |         result:
 38 |           content:
 39 |             - type: "text"
 40 |               text: "match:contains:security"
 41 |           isError: false
 42 |       stderr: "toBeEmpty"
 43 | 
 44 |   - it: "should find performance-related content"
 45 |     request:
 46 |       jsonrpc: "2.0"
 47 |       id: "search-performance-1"
 48 |       method: "tools/call"
 49 |       params:
 50 |         name: "search_best_practices"
 51 |         arguments:
 52 |           query: "performance"
 53 |     expect:
 54 |       response:
 55 |         jsonrpc: "2.0"
 56 |         id: "search-performance-1"
 57 |         result:
 58 |           content:
 59 |             - type: "text"
 60 |               text: "match:contains:performance"
 61 |           isError: false
 62 |       stderr: "toBeEmpty"
 63 | 
 64 |   - it: "should find OCAPI hook references"
 65 |     request:
 66 |       jsonrpc: "2.0"
 67 |       id: "search-ocapi-1"
 68 |       method: "tools/call"
 69 |       params:
 70 |         name: "search_best_practices"
 71 |         arguments:
 72 |           query: "ocapi"
 73 |     expect:
 74 |       response:
 75 |         jsonrpc: "2.0"
 76 |         id: "search-ocapi-1"
 77 |         result:
 78 |           content:
 79 |             - type: "text"
 80 |               text: "match:contains:ocapi"
 81 |           isError: false
 82 |       stderr: "toBeEmpty"
 83 | 
 84 |   - it: "should find SCAPI-related content"
 85 |     request:
 86 |       jsonrpc: "2.0"
 87 |       id: "search-scapi-1"
 88 |       method: "tools/call"
 89 |       params:
 90 |         name: "search_best_practices"
 91 |         arguments:
 92 |           query: "scapi"
 93 |     expect:
 94 |       response:
 95 |         jsonrpc: "2.0"
 96 |         id: "search-scapi-1"
 97 |         result:
 98 |           content:
 99 |             - type: "text"
100 |               text: "match:contains:scapi"
101 |           isError: false
102 |       stderr: "toBeEmpty"
103 | 
104 |   - it: "should find cartridge-related content"
105 |     request:
106 |       jsonrpc: "2.0"
107 |       id: "search-cartridge-1"
108 |       method: "tools/call"
109 |       params:
110 |         name: "search_best_practices"
111 |         arguments:
112 |           query: "cartridge"
113 |     expect:
114 |       response:
115 |         jsonrpc: "2.0"
116 |         id: "search-cartridge-1"
117 |         result:
118 |           content:
119 |             - type: "text"
120 |               text: "match:contains:cartridge"
121 |           isError: false
122 |       stderr: "toBeEmpty"
123 | 
124 |   - it: "should find ISML template references"
125 |     request:
126 |       jsonrpc: "2.0"
127 |       id: "search-isml-1"
128 |       method: "tools/call"
129 |       params:
130 |         name: "search_best_practices"
131 |         arguments:
132 |           query: "isml"
133 |     expect:
134 |       response:
135 |         jsonrpc: "2.0"
136 |         id: "search-isml-1"
137 |         result:
138 |           content:
139 |             - type: "text"
140 |               text: "match:contains:isml"
141 |           isError: false
142 |       stderr: "toBeEmpty"
143 | 
144 |   - it: "should find controller-related content"
145 |     request:
146 |       jsonrpc: "2.0"
147 |       id: "search-controller-1"
148 |       method: "tools/call"
149 |       params:
150 |         name: "search_best_practices"
151 |         arguments:
152 |           query: "controller"
153 |     expect:
154 |       response:
155 |         jsonrpc: "2.0"
156 |         id: "search-controller-1"
157 |         result:
158 |           content:
159 |             - type: "text"
160 |               text: "match:contains:controller"
161 |           isError: false
162 |       stderr: "toBeEmpty"
163 | 
164 |   - it: "should surface SFRA client-side JavaScript guidance"
165 |     request:
166 |       jsonrpc: "2.0"
167 |       id: "search-client-js-1"
168 |       method: "tools/call"
169 |       params:
170 |         name: "search_best_practices"
171 |         arguments:
172 |           query: "javascript"
173 |     expect:
174 |       response:
175 |         jsonrpc: "2.0"
176 |         id: "search-client-js-1"
177 |         result:
178 |           content:
179 |             - type: "text"
180 |               text: "match:contains:sfra_client_side_js"
181 |           isError: false
182 |       stderr: "toBeEmpty"
183 | 
184 |   - it: "should surface SFRA SCSS override guidance"
185 |     request:
186 |       jsonrpc: "2.0"
187 |       id: "search-scss-1"
188 |       method: "tools/call"
189 |       params:
190 |         name: "search_best_practices"
191 |         arguments:
192 |           query: "scss"
193 |     expect:
194 |       response:
195 |         jsonrpc: "2.0"
196 |         id: "search-scss-1"
197 |         result:
198 |           content:
199 |             - type: "text"
200 |               text: "match:contains:sfra_scss"
201 |           isError: false
202 |       stderr: "toBeEmpty"
203 | 
204 |   - it: "should find job framework references"
205 |     request:
206 |       jsonrpc: "2.0"
207 |       id: "search-job-1"
208 |       method: "tools/call"
209 |       params:
210 |         name: "search_best_practices"
211 |         arguments:
212 |           query: "job"
213 |     expect:
214 |       response:
215 |         jsonrpc: "2.0"
216 |         id: "search-job-1"
217 |         result:
218 |           content:
219 |             - type: "text"
220 |               text: "match:contains:job"
221 |           isError: false
222 |       stderr: "toBeEmpty"
223 | 
224 |   # Edge cases and error handling
225 |   - it: "should return empty array for non-existent terms"
226 |     request:
227 |       jsonrpc: "2.0"
228 |       id: "search-empty-1"
229 |       method: "tools/call"
230 |       params:
231 |         name: "search_best_practices"
232 |         arguments:
233 |           query: "zzznomatchesexpected"
234 |     expect:
235 |       response:
236 |         jsonrpc: "2.0"
237 |         id: "search-empty-1"
238 |         result:
239 |           content:
240 |             - type: "text"
241 |               text: "match:regex:^\\[\\s*\\]$"  # Empty array
242 |           isError: false
243 |       stderr: "toBeEmpty"
244 | 
245 |   - it: "should handle empty query with error"
246 |     request:
247 |       jsonrpc: "2.0"
248 |       id: "search-error-1"
249 |       method: "tools/call"
250 |       params:
251 |         name: "search_best_practices"
252 |         arguments:
253 |           query: ""
254 |     expect:
255 |       response:
256 |         jsonrpc: "2.0"
257 |         id: "search-error-1"
258 |         result:
259 |           content:
260 |             - type: "text"
261 |               text: "match:contains:Error"
262 |           isError: true
263 |       stderr: "toBeEmpty"
264 | 
265 |   - it: "should handle missing query parameter with error"
266 |     request:
267 |       jsonrpc: "2.0"
268 |       id: "search-missing-1"
269 |       method: "tools/call"
270 |       params:
271 |         name: "search_best_practices"
272 |         arguments: {}
273 |     expect:
274 |       response:
275 |         jsonrpc: "2.0"
276 |         id: "search-missing-1"
277 |         result:
278 |           content:
279 |             - type: "text"
280 |               text: "match:contains:Error"
281 |           isError: true
282 |       stderr: "toBeEmpty"
283 | 
284 |   # Response structure validation tests
285 |   - it: "should validate detailed response structure for validation search"
286 |     request:
287 |       jsonrpc: "2.0"
288 |       id: "search-structure-1"
289 |       method: "tools/call"
290 |       params:
291 |         name: "search_best_practices"
292 |         arguments:
293 |           query: "validation"
294 |     expect:
295 |       response:
296 |         jsonrpc: "2.0"
297 |         id: "search-structure-1"
298 |         result:
299 |           content:
300 |             - type: "text"
301 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # JSON array format
302 |           isError: false
303 |       stderr: "toBeEmpty"
304 | 
305 |   # Content validation tests for specific guides
306 |   - it: "should find form validation in ISML templates guide"
307 |     request:
308 |       jsonrpc: "2.0"
309 |       id: "search-form-validation-1"
310 |       method: "tools/call"
311 |       params:
312 |         name: "search_best_practices"
313 |         arguments:
314 |           query: "form"
315 |     expect:
316 |       response:
317 |         jsonrpc: "2.0"
318 |         id: "search-form-validation-1"
319 |         result:
320 |           content:
321 |             - type: "text"
322 |               text: "match:contains:form"
323 |           isError: false
324 |       stderr: "toBeEmpty"
325 | 
326 |   - it: "should find authorization patterns in hook guides"
327 |     request:
328 |       jsonrpc: "2.0"
329 |       id: "search-authorization-1"
330 |       method: "tools/call"
331 |       params:
332 |         name: "search_best_practices"
333 |         arguments:
334 |           query: "authorization"
335 |     expect:
336 |       response:
337 |         jsonrpc: "2.0"
338 |         id: "search-authorization-1"
339 |         result:
340 |           content:
341 |             - type: "text"
342 |               text: "match:contains:authorization"
343 |           isError: false
344 |       stderr: "toBeEmpty"
345 | 
346 |   - it: "should find middleware patterns"
347 |     request:
348 |       jsonrpc: "2.0"
349 |       id: "search-middleware-1"
350 |       method: "tools/call"
351 |       params:
352 |         name: "search_best_practices"
353 |         arguments:
354 |           query: "middleware"
355 |     expect:
356 |       response:
357 |         jsonrpc: "2.0"
358 |         id: "search-middleware-1"
359 |         result:
360 |           content:
361 |             - type: "text"
362 |               text: "match:contains:middleware"
363 |           isError: false
364 |       stderr: "toBeEmpty"
365 | 
366 |   - it: "should find transaction handling patterns"
367 |     request:
368 |       jsonrpc: "2.0"
369 |       id: "search-transaction-1"
370 |       method: "tools/call"
371 |       params:
372 |         name: "search_best_practices"
373 |         arguments:
374 |           query: "transaction"
375 |     expect:
376 |       response:
377 |         jsonrpc: "2.0"
378 |         id: "search-transaction-1"
379 |         result:
380 |           content:
381 |             - type: "text"
382 |               text: "match:contains:transaction"
383 |           isError: false
384 |       stderr: "toBeEmpty"
385 | 
386 |   - it: "should find error handling patterns"
387 |     request:
388 |       jsonrpc: "2.0"
389 |       id: "search-error-handling-1"
390 |       method: "tools/call"
391 |       params:
392 |         name: "search_best_practices"
393 |         arguments:
394 |           query: "error"
395 |     expect:
396 |       response:
397 |         jsonrpc: "2.0"
398 |         id: "search-error-handling-1"
399 |         result:
400 |           content:
401 |             - type: "text"
402 |               text: "match:contains:error"
403 |           isError: false
404 |       stderr: "toBeEmpty"
405 | 
406 |   # Case sensitivity tests
407 |   - it: "should handle case insensitive search for SFRA"
408 |     request:
409 |       jsonrpc: "2.0"
410 |       id: "search-case-1"
411 |       method: "tools/call"
412 |       params:
413 |         name: "search_best_practices"
414 |         arguments:
415 |           query: "SFRA"
416 |     expect:
417 |       response:
418 |         jsonrpc: "2.0"
419 |         id: "search-case-1"
420 |         result:
421 |           content:
422 |             - type: "text"
423 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
424 |           isError: false
425 |       stderr: "toBeEmpty"
426 | 
427 |   - it: "should handle lowercase search terms"
428 |     request:
429 |       jsonrpc: "2.0"
430 |       id: "search-lowercase-1"
431 |       method: "tools/call"
432 |       params:
433 |         name: "search_best_practices"
434 |         arguments:
435 |           query: "api"
436 |     expect:
437 |       response:
438 |         jsonrpc: "2.0"
439 |         id: "search-lowercase-1"
440 |         result:
441 |           content:
442 |             - type: "text"
443 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
444 |           isError: false
445 |       stderr: "toBeEmpty"
446 | 
447 |   # Multiple word search tests
448 |   - it: "should find authentication patterns"
449 |     request:
450 |       jsonrpc: "2.0"
451 |       id: "search-auth-1"
452 |       method: "tools/call"
453 |       params:
454 |         name: "search_best_practices"
455 |         arguments:
456 |           query: "authentication"
457 |     expect:
458 |       response:
459 |         jsonrpc: "2.0"
460 |         id: "search-auth-1"
461 |         result:
462 |           content:
463 |             - type: "text"
464 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
465 |           isError: false
466 |       stderr: "toBeEmpty"
467 | 
468 |   - it: "should find encryption references"
469 |     request:
470 |       jsonrpc: "2.0"
471 |       id: "search-encryption-1"
472 |       method: "tools/call"
473 |       params:
474 |         name: "search_best_practices"
475 |         arguments:
476 |           query: "encryption"
477 |     expect:
478 |       response:
479 |         jsonrpc: "2.0"
480 |         id: "search-encryption-1"
481 |         result:
482 |           content:
483 |             - type: "text"
484 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
485 |           isError: false
486 |       stderr: "toBeEmpty"
487 | 
488 |   # Special characters handling
489 |   - it: "should handle search terms with special characters"
490 |     request:
491 |       jsonrpc: "2.0"
492 |       id: "search-special-1"
493 |       method: "tools/call"
494 |       params:
495 |         name: "search_best_practices"
496 |         arguments:
497 |           query: "REST"
498 |     expect:
499 |       response:
500 |         jsonrpc: "2.0"
501 |         id: "search-special-1"
502 |         result:
503 |           content:
504 |             - type: "text"
505 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
506 |           isError: false
507 |       stderr: "toBeEmpty"
508 | 
509 |   - it: "should handle numeric search terms"
510 |     request:
511 |       jsonrpc: "2.0"
512 |       id: "search-numeric-1"
513 |       method: "tools/call"
514 |       params:
515 |         name: "search_best_practices"
516 |         arguments:
517 |           query: "HTTPS"
518 |     expect:
519 |       response:
520 |         jsonrpc: "2.0"
521 |         id: "search-numeric-1"
522 |         result:
523 |           content:
524 |             - type: "text"
525 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
526 |           isError: false
527 |       stderr: "toBeEmpty"
528 | 
529 |   # Common development terms
530 |   - it: "should find model-related patterns"
531 |     request:
532 |       jsonrpc: "2.0"
533 |       id: "search-model-1"
534 |       method: "tools/call"
535 |       params:
536 |         name: "search_best_practices"
537 |         arguments:
538 |           query: "model"
539 |     expect:
540 |       response:
541 |         jsonrpc: "2.0"
542 |         id: "search-model-1"
543 |         result:
544 |           content:
545 |             - type: "text"
546 |               text: "match:contains:model"
547 |           isError: false
548 |       stderr: "toBeEmpty"
549 | 
550 |   - it: "should find configuration patterns"
551 |     request:
552 |       jsonrpc: "2.0"
553 |       id: "search-config-1"
554 |       method: "tools/call"
555 |       params:
556 |         name: "search_best_practices"
557 |         arguments:
558 |           query: "config"
559 |     expect:
560 |       response:
561 |         jsonrpc: "2.0"
562 |         id: "search-config-1"
563 |         result:
564 |           content:
565 |             - type: "text"
566 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
567 |           isError: false
568 |       stderr: "toBeEmpty"
569 | 
570 |   - it: "should find database transaction patterns"
571 |     request:
572 |       jsonrpc: "2.0"
573 |       id: "search-database-1"
574 |       method: "tools/call"
575 |       params:
576 |         name: "search_best_practices"
577 |         arguments:
578 |           query: "database"
579 |     expect:
580 |       response:
581 |         jsonrpc: "2.0"
582 |         id: "search-database-1"
583 |         result:
584 |           content:
585 |             - type: "text"
586 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
587 |           isError: false
588 |       stderr: "toBeEmpty"
589 | 
590 |   # Performance testing with timing
591 |   - it: "should respond quickly for validation search"
592 |     request:
593 |       jsonrpc: "2.0"
594 |       id: "search-perf-1"
595 |       method: "tools/call"
596 |       params:
597 |         name: "search_best_practices"
598 |         arguments:
599 |           query: "validation"
600 |     expect:
601 |       response:
602 |         jsonrpc: "2.0"
603 |         id: "search-perf-1"
604 |         result:
605 |           content:
606 |             - type: "text"
607 |               text: "match:regex:^\\[[\\s\\S]*\\]$"
608 |           isError: false
609 |       performance:
610 |         maxResponseTime: "2000ms"  # Documentation search should be fast
611 |       stderr: "toBeEmpty"
612 | 
613 |   - it: "should respond quickly for empty results"
614 |     request:
615 |       jsonrpc: "2.0"
616 |       id: "search-perf-empty-1"
617 |       method: "tools/call"
618 |       params:
619 |         name: "search_best_practices"
620 |         arguments:
621 |           query: "zzznomatchesexpected"
622 |     expect:
623 |       response:
624 |         jsonrpc: "2.0"
625 |         id: "search-perf-empty-1"
626 |         result:
627 |           content:
628 |             - type: "text"
629 |               text: "match:regex:^\\[\\s*\\]$"
630 |           isError: false
631 |       performance:
632 |         maxResponseTime: "1000ms"  # Empty results should be very fast
633 |       stderr: "toBeEmpty"
634 | 
635 |   - it: "should handle errors quickly"
636 |     request:
637 |       jsonrpc: "2.0"
638 |       id: "search-perf-error-1"
639 |       method: "tools/call"
640 |       params:
641 |         name: "search_best_practices"
642 |         arguments:
643 |           query: ""
644 |     expect:
645 |       response:
646 |         jsonrpc: "2.0"
647 |         id: "search-perf-error-1"
648 |         result:
649 |           content:
650 |             - type: "text"
651 |               text: "match:contains:Error"
652 |           isError: true
653 |       performance:
654 |         maxResponseTime: "500ms"  # Error responses should be fastest
655 |       stderr: "toBeEmpty"
656 | 
```

--------------------------------------------------------------------------------
/tests/mcp/yaml/search-best-practices.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
  1 | description: "SFCC Dev MCP Server - search_best_practices Tool Tests (Docs-Only Mode)"
  2 | tests:
  3 |   # Basic functionality tests
  4 |   - it: "should return array of search results for validation query"
  5 |     request:
  6 |       jsonrpc: "2.0"
  7 |       id: "search-validation-1"
  8 |       method: "tools/call"
  9 |       params:
 10 |         name: "search_best_practices"
 11 |         arguments:
 12 |           query: "validation"
 13 |     expect:
 14 |       response:
 15 |         jsonrpc: "2.0"
 16 |         id: "search-validation-1"
 17 |         result:
 18 |           content:
 19 |             - type: "text"
 20 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
 21 |           isError: false
 22 |       stderr: "toBeEmpty"
 23 | 
 24 |   - it: "should find security-related content in best practices"
 25 |     request:
 26 |       jsonrpc: "2.0"
 27 |       id: "search-security-1"
 28 |       method: "tools/call"
 29 |       params:
 30 |         name: "search_best_practices"
 31 |         arguments:
 32 |           query: "security"
 33 |     expect:
 34 |       response:
 35 |         jsonrpc: "2.0"
 36 |         id: "search-security-1"
 37 |         result:
 38 |           content:
 39 |             - type: "text"
 40 |               text: "match:contains:security"
 41 |           isError: false
 42 |       stderr: "toBeEmpty"
 43 | 
 44 |   - it: "should find performance-related content"
 45 |     request:
 46 |       jsonrpc: "2.0"
 47 |       id: "search-performance-1"
 48 |       method: "tools/call"
 49 |       params:
 50 |         name: "search_best_practices"
 51 |         arguments:
 52 |           query: "performance"
 53 |     expect:
 54 |       response:
 55 |         jsonrpc: "2.0"
 56 |         id: "search-performance-1"
 57 |         result:
 58 |           content:
 59 |             - type: "text"
 60 |               text: "match:contains:performance"
 61 |           isError: false
 62 |       stderr: "toBeEmpty"
 63 | 
 64 |   - it: "should find OCAPI hook references"
 65 |     request:
 66 |       jsonrpc: "2.0"
 67 |       id: "search-ocapi-1"
 68 |       method: "tools/call"
 69 |       params:
 70 |         name: "search_best_practices"
 71 |         arguments:
 72 |           query: "ocapi"
 73 |     expect:
 74 |       response:
 75 |         jsonrpc: "2.0"
 76 |         id: "search-ocapi-1"
 77 |         result:
 78 |           content:
 79 |             - type: "text"
 80 |               text: "match:contains:ocapi"
 81 |           isError: false
 82 |       stderr: "toBeEmpty"
 83 | 
 84 |   - it: "should find SCAPI-related content"
 85 |     request:
 86 |       jsonrpc: "2.0"
 87 |       id: "search-scapi-1"
 88 |       method: "tools/call"
 89 |       params:
 90 |         name: "search_best_practices"
 91 |         arguments:
 92 |           query: "scapi"
 93 |     expect:
 94 |       response:
 95 |         jsonrpc: "2.0"
 96 |         id: "search-scapi-1"
 97 |         result:
 98 |           content:
 99 |             - type: "text"
100 |               text: "match:contains:scapi"
101 |           isError: false
102 |       stderr: "toBeEmpty"
103 | 
104 |   - it: "should find cartridge-related content"
105 |     request:
106 |       jsonrpc: "2.0"
107 |       id: "search-cartridge-1"
108 |       method: "tools/call"
109 |       params:
110 |         name: "search_best_practices"
111 |         arguments:
112 |           query: "cartridge"
113 |     expect:
114 |       response:
115 |         jsonrpc: "2.0"
116 |         id: "search-cartridge-1"
117 |         result:
118 |           content:
119 |             - type: "text"
120 |               text: "match:contains:cartridge"
121 |           isError: false
122 |       stderr: "toBeEmpty"
123 | 
124 |   - it: "should find ISML template references"
125 |     request:
126 |       jsonrpc: "2.0"
127 |       id: "search-isml-1"
128 |       method: "tools/call"
129 |       params:
130 |         name: "search_best_practices"
131 |         arguments:
132 |           query: "isml"
133 |     expect:
134 |       response:
135 |         jsonrpc: "2.0"
136 |         id: "search-isml-1"
137 |         result:
138 |           content:
139 |             - type: "text"
140 |               text: "match:contains:isml"
141 |           isError: false
142 |       stderr: "toBeEmpty"
143 | 
144 |   - it: "should find controller-related content"
145 |     request:
146 |       jsonrpc: "2.0"
147 |       id: "search-controller-1"
148 |       method: "tools/call"
149 |       params:
150 |         name: "search_best_practices"
151 |         arguments:
152 |           query: "controller"
153 |     expect:
154 |       response:
155 |         jsonrpc: "2.0"
156 |         id: "search-controller-1"
157 |         result:
158 |           content:
159 |             - type: "text"
160 |               text: "match:contains:controller"
161 |           isError: false
162 |       stderr: "toBeEmpty"
163 | 
164 |   - it: "should surface SFRA client-side JavaScript guidance"
165 |     request:
166 |       jsonrpc: "2.0"
167 |       id: "search-client-js-1"
168 |       method: "tools/call"
169 |       params:
170 |         name: "search_best_practices"
171 |         arguments:
172 |           query: "javascript"
173 |     expect:
174 |       response:
175 |         jsonrpc: "2.0"
176 |         id: "search-client-js-1"
177 |         result:
178 |           content:
179 |             - type: "text"
180 |               text: "match:contains:sfra_client_side_js"
181 |           isError: false
182 |       stderr: "toBeEmpty"
183 | 
184 |   - it: "should surface SFRA SCSS override guidance"
185 |     request:
186 |       jsonrpc: "2.0"
187 |       id: "search-scss-1"
188 |       method: "tools/call"
189 |       params:
190 |         name: "search_best_practices"
191 |         arguments:
192 |           query: "scss"
193 |     expect:
194 |       response:
195 |         jsonrpc: "2.0"
196 |         id: "search-scss-1"
197 |         result:
198 |           content:
199 |             - type: "text"
200 |               text: "match:contains:sfra_scss"
201 |           isError: false
202 |       stderr: "toBeEmpty"
203 | 
204 |   - it: "should find job framework references"
205 |     request:
206 |       jsonrpc: "2.0"
207 |       id: "search-job-1"
208 |       method: "tools/call"
209 |       params:
210 |         name: "search_best_practices"
211 |         arguments:
212 |           query: "job"
213 |     expect:
214 |       response:
215 |         jsonrpc: "2.0"
216 |         id: "search-job-1"
217 |         result:
218 |           content:
219 |             - type: "text"
220 |               text: "match:contains:job"
221 |           isError: false
222 |       stderr: "toBeEmpty"
223 | 
224 |   # Edge cases and error handling
225 |   - it: "should return empty array for non-existent terms"
226 |     request:
227 |       jsonrpc: "2.0"
228 |       id: "search-empty-1"
229 |       method: "tools/call"
230 |       params:
231 |         name: "search_best_practices"
232 |         arguments:
233 |           query: "zzznomatchesexpected"
234 |     expect:
235 |       response:
236 |         jsonrpc: "2.0"
237 |         id: "search-empty-1"
238 |         result:
239 |           content:
240 |             - type: "text"
241 |               text: "match:regex:^\\[\\s*\\]$"  # Empty array
242 |           isError: false
243 |       stderr: "toBeEmpty"
244 | 
245 |   - it: "should handle empty query with error"
246 |     request:
247 |       jsonrpc: "2.0"
248 |       id: "search-error-1"
249 |       method: "tools/call"
250 |       params:
251 |         name: "search_best_practices"
252 |         arguments:
253 |           query: ""
254 |     expect:
255 |       response:
256 |         jsonrpc: "2.0"
257 |         id: "search-error-1"
258 |         result:
259 |           content:
260 |             - type: "text"
261 |               text: "match:contains:Error"
262 |           isError: true
263 |       stderr: "toBeEmpty"
264 | 
265 |   - it: "should handle missing query parameter with error"
266 |     request:
267 |       jsonrpc: "2.0"
268 |       id: "search-missing-1"
269 |       method: "tools/call"
270 |       params:
271 |         name: "search_best_practices"
272 |         arguments: {}
273 |     expect:
274 |       response:
275 |         jsonrpc: "2.0"
276 |         id: "search-missing-1"
277 |         result:
278 |           content:
279 |             - type: "text"
280 |               text: "match:contains:Error"
281 |           isError: true
282 |       stderr: "toBeEmpty"
283 | 
284 |   # Response structure validation tests
285 |   - it: "should validate detailed response structure for validation search"
286 |     request:
287 |       jsonrpc: "2.0"
288 |       id: "search-structure-1"
289 |       method: "tools/call"
290 |       params:
291 |         name: "search_best_practices"
292 |         arguments:
293 |           query: "validation"
294 |     expect:
295 |       response:
296 |         jsonrpc: "2.0"
297 |         id: "search-structure-1"
298 |         result:
299 |           content:
300 |             - type: "text"
301 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # JSON array format
302 |           isError: false
303 |       stderr: "toBeEmpty"
304 | 
305 |   # Content validation tests for specific guides
306 |   - it: "should find form validation in ISML templates guide"
307 |     request:
308 |       jsonrpc: "2.0"
309 |       id: "search-form-validation-1"
310 |       method: "tools/call"
311 |       params:
312 |         name: "search_best_practices"
313 |         arguments:
314 |           query: "form"
315 |     expect:
316 |       response:
317 |         jsonrpc: "2.0"
318 |         id: "search-form-validation-1"
319 |         result:
320 |           content:
321 |             - type: "text"
322 |               text: "match:contains:form"
323 |           isError: false
324 |       stderr: "toBeEmpty"
325 | 
326 |   - it: "should find authorization patterns in hook guides"
327 |     request:
328 |       jsonrpc: "2.0"
329 |       id: "search-authorization-1"
330 |       method: "tools/call"
331 |       params:
332 |         name: "search_best_practices"
333 |         arguments:
334 |           query: "authorization"
335 |     expect:
336 |       response:
337 |         jsonrpc: "2.0"
338 |         id: "search-authorization-1"
339 |         result:
340 |           content:
341 |             - type: "text"
342 |               text: "match:contains:authorization"
343 |           isError: false
344 |       stderr: "toBeEmpty"
345 | 
346 |   - it: "should find middleware patterns"
347 |     request:
348 |       jsonrpc: "2.0"
349 |       id: "search-middleware-1"
350 |       method: "tools/call"
351 |       params:
352 |         name: "search_best_practices"
353 |         arguments:
354 |           query: "middleware"
355 |     expect:
356 |       response:
357 |         jsonrpc: "2.0"
358 |         id: "search-middleware-1"
359 |         result:
360 |           content:
361 |             - type: "text"
362 |               text: "match:contains:middleware"
363 |           isError: false
364 |       stderr: "toBeEmpty"
365 | 
366 |   - it: "should find transaction handling patterns"
367 |     request:
368 |       jsonrpc: "2.0"
369 |       id: "search-transaction-1"
370 |       method: "tools/call"
371 |       params:
372 |         name: "search_best_practices"
373 |         arguments:
374 |           query: "transaction"
375 |     expect:
376 |       response:
377 |         jsonrpc: "2.0"
378 |         id: "search-transaction-1"
379 |         result:
380 |           content:
381 |             - type: "text"
382 |               text: "match:contains:transaction"
383 |           isError: false
384 |       stderr: "toBeEmpty"
385 | 
386 |   - it: "should find error handling patterns"
387 |     request:
388 |       jsonrpc: "2.0"
389 |       id: "search-error-handling-1"
390 |       method: "tools/call"
391 |       params:
392 |         name: "search_best_practices"
393 |         arguments:
394 |           query: "error"
395 |     expect:
396 |       response:
397 |         jsonrpc: "2.0"
398 |         id: "search-error-handling-1"
399 |         result:
400 |           content:
401 |             - type: "text"
402 |               text: "match:contains:error"
403 |           isError: false
404 |       stderr: "toBeEmpty"
405 | 
406 |   # Case sensitivity tests
407 |   - it: "should handle case insensitive search for SFRA"
408 |     request:
409 |       jsonrpc: "2.0"
410 |       id: "search-case-1"
411 |       method: "tools/call"
412 |       params:
413 |         name: "search_best_practices"
414 |         arguments:
415 |           query: "SFRA"
416 |     expect:
417 |       response:
418 |         jsonrpc: "2.0"
419 |         id: "search-case-1"
420 |         result:
421 |           content:
422 |             - type: "text"
423 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
424 |           isError: false
425 |       stderr: "toBeEmpty"
426 | 
427 |   - it: "should handle lowercase search terms"
428 |     request:
429 |       jsonrpc: "2.0"
430 |       id: "search-lowercase-1"
431 |       method: "tools/call"
432 |       params:
433 |         name: "search_best_practices"
434 |         arguments:
435 |           query: "api"
436 |     expect:
437 |       response:
438 |         jsonrpc: "2.0"
439 |         id: "search-lowercase-1"
440 |         result:
441 |           content:
442 |             - type: "text"
443 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
444 |           isError: false
445 |       stderr: "toBeEmpty"
446 | 
447 |   # Multiple word search tests
448 |   - it: "should find authentication patterns"
449 |     request:
450 |       jsonrpc: "2.0"
451 |       id: "search-auth-1"
452 |       method: "tools/call"
453 |       params:
454 |         name: "search_best_practices"
455 |         arguments:
456 |           query: "authentication"
457 |     expect:
458 |       response:
459 |         jsonrpc: "2.0"
460 |         id: "search-auth-1"
461 |         result:
462 |           content:
463 |             - type: "text"
464 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
465 |           isError: false
466 |       stderr: "toBeEmpty"
467 | 
468 |   - it: "should find encryption references"
469 |     request:
470 |       jsonrpc: "2.0"
471 |       id: "search-encryption-1"
472 |       method: "tools/call"
473 |       params:
474 |         name: "search_best_practices"
475 |         arguments:
476 |           query: "encryption"
477 |     expect:
478 |       response:
479 |         jsonrpc: "2.0"
480 |         id: "search-encryption-1"
481 |         result:
482 |           content:
483 |             - type: "text"
484 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
485 |           isError: false
486 |       stderr: "toBeEmpty"
487 | 
488 |   # Special characters handling
489 |   - it: "should handle search terms with special characters"
490 |     request:
491 |       jsonrpc: "2.0"
492 |       id: "search-special-1"
493 |       method: "tools/call"
494 |       params:
495 |         name: "search_best_practices"
496 |         arguments:
497 |           query: "REST"
498 |     expect:
499 |       response:
500 |         jsonrpc: "2.0"
501 |         id: "search-special-1"
502 |         result:
503 |           content:
504 |             - type: "text"
505 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
506 |           isError: false
507 |       stderr: "toBeEmpty"
508 | 
509 |   - it: "should handle numeric search terms"
510 |     request:
511 |       jsonrpc: "2.0"
512 |       id: "search-numeric-1"
513 |       method: "tools/call"
514 |       params:
515 |         name: "search_best_practices"
516 |         arguments:
517 |           query: "HTTPS"
518 |     expect:
519 |       response:
520 |         jsonrpc: "2.0"
521 |         id: "search-numeric-1"
522 |         result:
523 |           content:
524 |             - type: "text"
525 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
526 |           isError: false
527 |       stderr: "toBeEmpty"
528 | 
529 |   # Common development terms
530 |   - it: "should find model-related patterns"
531 |     request:
532 |       jsonrpc: "2.0"
533 |       id: "search-model-1"
534 |       method: "tools/call"
535 |       params:
536 |         name: "search_best_practices"
537 |         arguments:
538 |           query: "model"
539 |     expect:
540 |       response:
541 |         jsonrpc: "2.0"
542 |         id: "search-model-1"
543 |         result:
544 |           content:
545 |             - type: "text"
546 |               text: "match:contains:model"
547 |           isError: false
548 |       stderr: "toBeEmpty"
549 | 
550 |   - it: "should find configuration patterns"
551 |     request:
552 |       jsonrpc: "2.0"
553 |       id: "search-config-1"
554 |       method: "tools/call"
555 |       params:
556 |         name: "search_best_practices"
557 |         arguments:
558 |           query: "config"
559 |     expect:
560 |       response:
561 |         jsonrpc: "2.0"
562 |         id: "search-config-1"
563 |         result:
564 |           content:
565 |             - type: "text"
566 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
567 |           isError: false
568 |       stderr: "toBeEmpty"
569 | 
570 |   - it: "should find database transaction patterns"
571 |     request:
572 |       jsonrpc: "2.0"
573 |       id: "search-database-1"
574 |       method: "tools/call"
575 |       params:
576 |         name: "search_best_practices"
577 |         arguments:
578 |           query: "database"
579 |     expect:
580 |       response:
581 |         jsonrpc: "2.0"
582 |         id: "search-database-1"
583 |         result:
584 |           content:
585 |             - type: "text"
586 |               text: "match:regex:^\\[[\\s\\S]*\\]$"  # Valid JSON array
587 |           isError: false
588 |       stderr: "toBeEmpty"
589 | 
590 |   # Performance testing with timing
591 |   - it: "should respond quickly for validation search"
592 |     request:
593 |       jsonrpc: "2.0"
594 |       id: "search-perf-1"
595 |       method: "tools/call"
596 |       params:
597 |         name: "search_best_practices"
598 |         arguments:
599 |           query: "validation"
600 |     expect:
601 |       response:
602 |         jsonrpc: "2.0"
603 |         id: "search-perf-1"
604 |         result:
605 |           content:
606 |             - type: "text"
607 |               text: "match:regex:^\\[[\\s\\S]*\\]$"
608 |           isError: false
609 |       performance:
610 |         maxResponseTime: "2000ms"  # Documentation search should be fast
611 |       stderr: "toBeEmpty"
612 | 
613 |   - it: "should respond quickly for empty results"
614 |     request:
615 |       jsonrpc: "2.0"
616 |       id: "search-perf-empty-1"
617 |       method: "tools/call"
618 |       params:
619 |         name: "search_best_practices"
620 |         arguments:
621 |           query: "zzznomatchesexpected"
622 |     expect:
623 |       response:
624 |         jsonrpc: "2.0"
625 |         id: "search-perf-empty-1"
626 |         result:
627 |           content:
628 |             - type: "text"
629 |               text: "match:regex:^\\[\\s*\\]$"
630 |           isError: false
631 |       performance:
632 |         maxResponseTime: "1000ms"  # Empty results should be very fast
633 |       stderr: "toBeEmpty"
634 | 
635 |   - it: "should handle errors quickly"
636 |     request:
637 |       jsonrpc: "2.0"
638 |       id: "search-perf-error-1"
639 |       method: "tools/call"
640 |       params:
641 |         name: "search_best_practices"
642 |         arguments:
643 |           query: ""
644 |     expect:
645 |       response:
646 |         jsonrpc: "2.0"
647 |         id: "search-perf-error-1"
648 |         result:
649 |           content:
650 |             - type: "text"
651 |               text: "match:contains:Error"
652 |           isError: true
653 |       performance:
654 |         maxResponseTime: "500ms"  # Error responses should be fastest
655 |       stderr: "toBeEmpty"
656 | 
```

--------------------------------------------------------------------------------
/docs/dw_net/SFTPClient.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: dw.net
  2 | 
  3 | # Class SFTPClient
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - dw.net.SFTPClient
  9 | 
 10 | ## Description
 11 | 
 12 | The SFTPClient class supports the SFTP commands GET, PUT, DEL, MKDIR, RENAME, and LIST. The transfer of files can be text or binary. Note: when this class is used with sensitive data, be careful in persisting sensitive information. An example usage is as follows: var sftp : SFTPClient = new dw.net.SFTPClient(); sftp.connect("my.sftp-server.com", "username", "password"); var data : String = sftp.get("simple.txt"); sftp.disconnect(); The default connection timeout depends on the script context timeout and will be set to a maximum of 30 seconds (default script context timeout is 10 seconds within storefront requests and 15 minutes within jobs). IMPORTANT NOTE: Before you can make an outbound SFTP connection to a port other than 22, the SFTP server IP address must be enabled for outbound traffic at the Commerce Cloud Digital firewall for your POD. Please file a support request to request a new firewall rule. SSH Version 2 is supported with the following algorithms: TypeAlgorithms Host Keyssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, rsa-sha2-512, rsa-sha2-256, ssh-rsa, ssh-dss Key Exchange (KEX)curve25519-sha256, [email protected], ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1, diffie-hellman-group-exchange-sha1, diffie-hellman-group1-sha1 Cipheraes128-ctr, aes192-ctr, aes256-ctr, [email protected], [email protected], aes128-cbc, 3des-ctr, 3des-cbc, blowfish-cbc, aes192-cbc, aes256-cbc Message Authentication Code (MAC)[email protected], [email protected], [email protected], hmac-sha2-256, hmac-sha2-512, hmac-sha1, hmac-md5, hmac-sha1-96, hmac-md5-96 Public Key Authenticationrsa-sha2-512, rsa-sha2-256, ssh-rsa
 13 | 
 14 | ## Constants
 15 | 
 16 | ### MAX_GET_FILE_SIZE
 17 | 
 18 | **Type:** Number = 209715200
 19 | 
 20 | The maximum size for get() methods returning a File is 200 MB.
 21 | 
 22 | ### MAX_GET_STRING_SIZE
 23 | 
 24 | **Type:** Number = 10485760
 25 | 
 26 | The maximum size for get() methods returning a String is 10 MB.
 27 | 
 28 | ## Properties
 29 | 
 30 | ### connected
 31 | 
 32 | **Type:** boolean (Read Only)
 33 | 
 34 | Identifies if the SFTP client is currently connected to the SFTP server.
 35 | 
 36 | ### errorMessage
 37 | 
 38 | **Type:** String (Read Only)
 39 | 
 40 | The error message from the last SFTP action.
 41 | 
 42 | ### identity
 43 | 
 44 | **Type:** KeyRef
 45 | 
 46 | Gets the identity (private key) used for the connection.
 47 |  
 48 |  The key is only associated to this instance of the SFTP client.
 49 | 
 50 | ### timeout
 51 | 
 52 | **Type:** Number
 53 | 
 54 | The timeout for this client, in milliseconds.
 55 | 
 56 | ## Constructor Summary
 57 | 
 58 | SFTPClient() Constructor.
 59 | 
 60 | ## Method Summary
 61 | 
 62 | ### addKnownHostKey
 63 | 
 64 | **Signature:** `addKnownHostKey(type : String, key : String) : void`
 65 | 
 66 | Adds a known public host key for the next connection attempt.
 67 | 
 68 | ### cd
 69 | 
 70 | **Signature:** `cd(path : String) : boolean`
 71 | 
 72 | Changes the current directory on the remote server to the given path.
 73 | 
 74 | ### connect
 75 | 
 76 | **Signature:** `connect(host : String, user : String, password : String) : boolean`
 77 | 
 78 | Connects and logs on to a SFTP server and returns a boolean indicating success or failure.
 79 | 
 80 | ### connect
 81 | 
 82 | **Signature:** `connect(host : String, port : Number, user : String, password : String) : boolean`
 83 | 
 84 | Connects and logs on to a SFTP server and returns a boolean indicating success or failure.
 85 | 
 86 | ### del
 87 | 
 88 | **Signature:** `del(path : String) : boolean`
 89 | 
 90 | Deletes the remote file on the server identified by the path parameter.
 91 | 
 92 | ### disconnect
 93 | 
 94 | **Signature:** `disconnect() : void`
 95 | 
 96 | The method first logs the current user out from the server and then disconnects from the server.
 97 | 
 98 | ### get
 99 | 
100 | **Signature:** `get(path : String) : String`
101 | 
102 | Reads the content of a remote file and returns it as a string using "ISO-8859-1" encoding to read it.
103 | 
104 | ### get
105 | 
106 | **Signature:** `get(path : String, encoding : String) : String`
107 | 
108 | Reads the content of a remote file and returns it as a string using the specified encoding.
109 | 
110 | ### get
111 | 
112 | **Signature:** `get(path : String, encoding : String, file : File) : boolean`
113 | 
114 | Reads the content of a remote file and creates a local copy in the given file using the passed string encoding to read the file content and using the system standard encoding "UTF-8" to write the file.
115 | 
116 | ### getBinary
117 | 
118 | **Signature:** `getBinary(path : String, file : File) : boolean`
119 | 
120 | Reads the content of a remote file and creates a local copy in the given file.
121 | 
122 | ### getConnected
123 | 
124 | **Signature:** `getConnected() : boolean`
125 | 
126 | Identifies if the SFTP client is currently connected to the SFTP server.
127 | 
128 | ### getErrorMessage
129 | 
130 | **Signature:** `getErrorMessage() : String`
131 | 
132 | Returns the error message from the last SFTP action.
133 | 
134 | ### getFileInfo
135 | 
136 | **Signature:** `getFileInfo(path : String) : SFTPFileInfo`
137 | 
138 | Returns a SFTPFileInfo objects containing information about the given file/directory path.
139 | 
140 | ### getIdentity
141 | 
142 | **Signature:** `getIdentity() : KeyRef`
143 | 
144 | Gets the identity (private key) used for the connection.
145 | 
146 | ### getTimeout
147 | 
148 | **Signature:** `getTimeout() : Number`
149 | 
150 | Returns the timeout for this client, in milliseconds.
151 | 
152 | ### list
153 | 
154 | **Signature:** `list() : SFTPFileInfo[]`
155 | 
156 | Returns a list of SFTPFileInfo objects containing information about the files in the current directory.
157 | 
158 | ### list
159 | 
160 | **Signature:** `list(path : String) : SFTPFileInfo[]`
161 | 
162 | Returns a list of SFTPFileInfo objects containing information about the files in the remote directory defined by the given path.
163 | 
164 | ### mkdir
165 | 
166 | **Signature:** `mkdir(path : String) : boolean`
167 | 
168 | Creates a directory
169 | 
170 | ### put
171 | 
172 | **Signature:** `put(path : String, content : String) : boolean`
173 | 
174 | Puts the specified content to the specified path using "ISO-8859-1" encoding.
175 | 
176 | ### put
177 | 
178 | **Signature:** `put(path : String, content : String, encoding : String) : boolean`
179 | 
180 | Put the given content to a file on the given path on the SFTP server.
181 | 
182 | ### putBinary
183 | 
184 | **Signature:** `putBinary(path : String, file : File) : boolean`
185 | 
186 | Put the content of the given file into a file on the remote SFTP server with the given absolute path.
187 | 
188 | ### removeDirectory
189 | 
190 | **Signature:** `removeDirectory(path : String) : boolean`
191 | 
192 | Deletes the remote directory on the server identified by the path parameter.
193 | 
194 | ### rename
195 | 
196 | **Signature:** `rename(from : String, to : String) : boolean`
197 | 
198 | Renames an existing file.
199 | 
200 | ### setIdentity
201 | 
202 | **Signature:** `setIdentity(keyRef : KeyRef) : void`
203 | 
204 | Sets the identity (private key) to use for the next connection attempt.
205 | 
206 | ### setTimeout
207 | 
208 | **Signature:** `setTimeout(timeoutMillis : Number) : void`
209 | 
210 | Sets the timeout for connections made with the SFTP client to the given number of milliseconds.
211 | 
212 | ## Constructor Detail
213 | 
214 | ## Method Detail
215 | 
216 | ## Method Details
217 | 
218 | ### addKnownHostKey
219 | 
220 | **Signature:** `addKnownHostKey(type : String, key : String) : void`
221 | 
222 | **Description:** Adds a known public host key for the next connection attempt. This method associates the key to the host used in the subsequent connect method, and must be called prior to connect. The key is not persisted, and is only associated to this instance of the SFTP client. Multiple keys may be added, and the validation will succeed if the remote host matches any of them. The default behavior is to persist and trust an unknown host key if there are no known host keys available. If addKnownHostKey is later used to trust specific a specific key or keys, then any previously persisted keys will be ignored.
223 | 
224 | **Parameters:**
225 | 
226 | - `type`: Type of the host key, such as ssh-rsa
227 | - `key`: Public host key, in the same format as OpenSSH.
228 | 
229 | ---
230 | 
231 | ### cd
232 | 
233 | **Signature:** `cd(path : String) : boolean`
234 | 
235 | **Description:** Changes the current directory on the remote server to the given path.
236 | 
237 | **Parameters:**
238 | 
239 | - `path`: the new current directory
240 | 
241 | **Returns:**
242 | 
243 | true if the directory change was okay
244 | 
245 | ---
246 | 
247 | ### connect
248 | 
249 | **Signature:** `connect(host : String, user : String, password : String) : boolean`
250 | 
251 | **Description:** Connects and logs on to a SFTP server and returns a boolean indicating success or failure.
252 | 
253 | **Parameters:**
254 | 
255 | - `host`: Name of the SFTP sever
256 | - `user`: Username for the login
257 | - `password`: Password for the login
258 | 
259 | **Returns:**
260 | 
261 | true when connection is successful, false otherwise.
262 | 
263 | ---
264 | 
265 | ### connect
266 | 
267 | **Signature:** `connect(host : String, port : Number, user : String, password : String) : boolean`
268 | 
269 | **Description:** Connects and logs on to a SFTP server and returns a boolean indicating success or failure.
270 | 
271 | **Parameters:**
272 | 
273 | - `host`: Name of the SFTP sever
274 | - `port`: Port for SFTP server
275 | - `user`: Username for the login
276 | - `password`: Password for the login
277 | 
278 | **Returns:**
279 | 
280 | true when connection is successful, false otherwise.
281 | 
282 | ---
283 | 
284 | ### del
285 | 
286 | **Signature:** `del(path : String) : boolean`
287 | 
288 | **Description:** Deletes the remote file on the server identified by the path parameter.
289 | 
290 | **Parameters:**
291 | 
292 | - `path`: the path to the file.
293 | 
294 | **Returns:**
295 | 
296 | true if the file was successfully deleted, false otherwise.
297 | 
298 | ---
299 | 
300 | ### disconnect
301 | 
302 | **Signature:** `disconnect() : void`
303 | 
304 | **Description:** The method first logs the current user out from the server and then disconnects from the server.
305 | 
306 | ---
307 | 
308 | ### get
309 | 
310 | **Signature:** `get(path : String) : String`
311 | 
312 | **Description:** Reads the content of a remote file and returns it as a string using "ISO-8859-1" encoding to read it. Files with at most MAX_GET_STRING_SIZE bytes are read.
313 | 
314 | **Parameters:**
315 | 
316 | - `path`: remote path of the file to be read.
317 | 
318 | **Returns:**
319 | 
320 | the contents of the file or null if an error occurred while reading the file.
321 | 
322 | ---
323 | 
324 | ### get
325 | 
326 | **Signature:** `get(path : String, encoding : String) : String`
327 | 
328 | **Description:** Reads the content of a remote file and returns it as a string using the specified encoding. Files with at most MAX_GET_STRING_SIZE bytes are read.
329 | 
330 | **Parameters:**
331 | 
332 | - `path`: the remote path of the file to be read.
333 | - `encoding`: the encoding to use.
334 | 
335 | **Returns:**
336 | 
337 | the contents of the file or null if an error occurred while reading the file.
338 | 
339 | ---
340 | 
341 | ### get
342 | 
343 | **Signature:** `get(path : String, encoding : String, file : File) : boolean`
344 | 
345 | **Description:** Reads the content of a remote file and creates a local copy in the given file using the passed string encoding to read the file content and using the system standard encoding "UTF-8" to write the file. Copies at most MAX_GET_FILE_SIZE bytes.
346 | 
347 | **Parameters:**
348 | 
349 | - `path`: the remote path of the file to be read.
350 | - `encoding`: the encoding to use.
351 | - `file`: the local file name
352 | 
353 | **Returns:**
354 | 
355 | true if complete remote file is fetched and copied into local file. false, otherwise.
356 | 
357 | ---
358 | 
359 | ### getBinary
360 | 
361 | **Signature:** `getBinary(path : String, file : File) : boolean`
362 | 
363 | **Description:** Reads the content of a remote file and creates a local copy in the given file. Copies at most MAX_GET_FILE_SIZE bytes. The SFTP transfer is done in binary mode.
364 | 
365 | **Parameters:**
366 | 
367 | - `path`: the remote path of the file to be read.
368 | - `file`: the local file name
369 | 
370 | **Returns:**
371 | 
372 | true if complete remote file is fetched and copied into local file. false otherwise
373 | 
374 | ---
375 | 
376 | ### getConnected
377 | 
378 | **Signature:** `getConnected() : boolean`
379 | 
380 | **Description:** Identifies if the SFTP client is currently connected to the SFTP server.
381 | 
382 | **Returns:**
383 | 
384 | true if the client is currently connected.
385 | 
386 | ---
387 | 
388 | ### getErrorMessage
389 | 
390 | **Signature:** `getErrorMessage() : String`
391 | 
392 | **Description:** Returns the error message from the last SFTP action.
393 | 
394 | **Returns:**
395 | 
396 | the error message from the last SFTP action
397 | 
398 | ---
399 | 
400 | ### getFileInfo
401 | 
402 | **Signature:** `getFileInfo(path : String) : SFTPFileInfo`
403 | 
404 | **Description:** Returns a SFTPFileInfo objects containing information about the given file/directory path.
405 | 
406 | **Parameters:**
407 | 
408 | - `path`: the remote path from which the file info should be listed.
409 | 
410 | **Returns:**
411 | 
412 | the remote file information or null if not present.
413 | 
414 | ---
415 | 
416 | ### getIdentity
417 | 
418 | **Signature:** `getIdentity() : KeyRef`
419 | 
420 | **Description:** Gets the identity (private key) used for the connection. The key is only associated to this instance of the SFTP client.
421 | 
422 | **Returns:**
423 | 
424 | Reference to the private key, or null if not configured
425 | 
426 | ---
427 | 
428 | ### getTimeout
429 | 
430 | **Signature:** `getTimeout() : Number`
431 | 
432 | **Description:** Returns the timeout for this client, in milliseconds.
433 | 
434 | **Returns:**
435 | 
436 | the timeout in milliseconds
437 | 
438 | ---
439 | 
440 | ### list
441 | 
442 | **Signature:** `list() : SFTPFileInfo[]`
443 | 
444 | **Description:** Returns a list of SFTPFileInfo objects containing information about the files in the current directory.
445 | 
446 | **Returns:**
447 | 
448 | list of objects with remote file information or null if not present.
449 | 
450 | ---
451 | 
452 | ### list
453 | 
454 | **Signature:** `list(path : String) : SFTPFileInfo[]`
455 | 
456 | **Description:** Returns a list of SFTPFileInfo objects containing information about the files in the remote directory defined by the given path.
457 | 
458 | **Parameters:**
459 | 
460 | - `path`: the remote path from which the file info should be listed.
461 | 
462 | **Returns:**
463 | 
464 | list of objects with remote file information or null if not present.
465 | 
466 | ---
467 | 
468 | ### mkdir
469 | 
470 | **Signature:** `mkdir(path : String) : boolean`
471 | 
472 | **Description:** Creates a directory
473 | 
474 | **Parameters:**
475 | 
476 | - `path`: the path to the directory to create.
477 | 
478 | **Returns:**
479 | 
480 | true if the directory was successfully created, false otherwise.
481 | 
482 | ---
483 | 
484 | ### put
485 | 
486 | **Signature:** `put(path : String, content : String) : boolean`
487 | 
488 | **Description:** Puts the specified content to the specified path using "ISO-8859-1" encoding. If the content of a local file is to be uploaded, please use method putBinary(String,File) instead. NOTE: If the remote file already exists, it is overwritten.
489 | 
490 | **Parameters:**
491 | 
492 | - `path`: the path on the remote SFTP server, where the file will be stored.
493 | - `content`: the content to put.
494 | 
495 | **Returns:**
496 | 
497 | true or false indicating success or failure.
498 | 
499 | ---
500 | 
501 | ### put
502 | 
503 | **Signature:** `put(path : String, content : String, encoding : String) : boolean`
504 | 
505 | **Description:** Put the given content to a file on the given path on the SFTP server. The transformation from String into binary data is done via the encoding provided with the method call. If the content of a local file is to be uploaded, please use method putBinary(String,File) instead. NOTE: If the remote file already exists, it is overwritten.
506 | 
507 | **Parameters:**
508 | 
509 | - `path`: the path on the remote SFTP server, where the file will be stored.
510 | - `content`: the content to put.
511 | - `encoding`: the encoding to use.
512 | 
513 | **Returns:**
514 | 
515 | true or false indicating success or failure.
516 | 
517 | ---
518 | 
519 | ### putBinary
520 | 
521 | **Signature:** `putBinary(path : String, file : File) : boolean`
522 | 
523 | **Description:** Put the content of the given file into a file on the remote SFTP server with the given absolute path. NOTE: If the remote file already exists, it is overwritten.
524 | 
525 | **Parameters:**
526 | 
527 | - `path`: the path on the remote SFTP server where the file will be stored.
528 | - `file`: the file on the local system, which content is send to the remote SFTP server.
529 | 
530 | **Returns:**
531 | 
532 | true or false indicating success or failure.
533 | 
534 | ---
535 | 
536 | ### removeDirectory
537 | 
538 | **Signature:** `removeDirectory(path : String) : boolean`
539 | 
540 | **Description:** Deletes the remote directory on the server identified by the path parameter. In order to delete the directory successfully the directory needs to be empty, otherwise the removeDirectory() method will return false.
541 | 
542 | **Parameters:**
543 | 
544 | - `path`: the path to the directory.
545 | 
546 | **Returns:**
547 | 
548 | true if the directory was successfully deleted, false otherwise.
549 | 
550 | ---
551 | 
552 | ### rename
553 | 
554 | **Signature:** `rename(from : String, to : String) : boolean`
555 | 
556 | **Description:** Renames an existing file.
557 | 
558 | **Parameters:**
559 | 
560 | - `from`: the file that will be renamed.
561 | - `to`: the name of the new file.
562 | 
563 | **Returns:**
564 | 
565 | true if the file was successfully renamed, false otherwise.
566 | 
567 | ---
568 | 
569 | ### setIdentity
570 | 
571 | **Signature:** `setIdentity(keyRef : KeyRef) : void`
572 | 
573 | **Description:** Sets the identity (private key) to use for the next connection attempt. The key is only associated to this instance of the SFTP client.
574 | 
575 | **Parameters:**
576 | 
577 | - `keyRef`: Reference to the private key
578 | 
579 | ---
580 | 
581 | ### setTimeout
582 | 
583 | **Signature:** `setTimeout(timeoutMillis : Number) : void`
584 | 
585 | **Description:** Sets the timeout for connections made with the SFTP client to the given number of milliseconds. If the given timeout is less than or equal to zero, the timeout is set to the same value as the script context timeout but will only be set to a maximum of 30 seconds. The maximum and default timeout depend on the script context timeout. The maximum timeout is set to a maximum of 2 minutes. The default timeout for a new client is set to a maximum of 30 seconds. This method can be called at any time, and will affect the next connection made with this client. It is not possible to set the timeout for an open connection.
586 | 
587 | **Parameters:**
588 | 
589 | - `timeoutMillis`: timeout, in milliseconds, up to a maximum of 2 minutes.
590 | 
591 | ---
```
Page 29/61FirstPrevNextLast