#
tokens: 46822/50000 5/825 files (page 38/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 38 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

--------------------------------------------------------------------------------
/.github/instructions/mcp-yml-tests.instructions.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | applyTo: "**/*.test.mcp.yml"
  3 | ---
  4 | 
  5 | # MCP Aegis - YAML Testing Guide for AI Agents
  6 | 
  7 | **Target**: AI assistants generating declarative YAML test files for Model Context Protocol servers.
  8 | 
  9 | **Core Purpose**: Test MCP servers with human-readable YAML files using 35+ advanced pattern matching capabilities including string patterns, numeric comparisons, date validation, array operations, field extraction, cross-field validation, and pattern negation.
 10 | 
 11 | ## 🆕 New Features: Pipe-Separated Parameters & Enhanced Testing
 12 | 
 13 | MCP Aegis now supports **CLI-friendly pipe-separated parameter format** alongside traditional JSON:
 14 | 
 15 | ```bash
 16 | # 🆕 Pipe format (CLI-friendly): key:value|other:123
 17 | npx aegis query calculator 'operation:add|a:5|b:3' --config "config.json"
 18 | 
 19 | # Traditional JSON (still supported): {"key":"value","other":123}
 20 | npx aegis query calculator '{"operation": "add", "a": 5, "b": 3}' --config "config.json"
 21 | ```
 22 | 
 23 | **Benefits**: ✅ No shell escaping ✅ Readable syntax ✅ Type inference ✅ Nested objects via dot notation ✅ Backward compatible
 24 | 
 25 | ## 🥇 GOLDEN RULE: Always Discover Response Formats First
 26 | 
 27 | **CRITICAL**: Before writing ANY YAML test, you MUST use `aegis query` to discover actual response formats for both success and failure scenarios. Never assume response structure.
 28 | 
 29 | ### Test Development Workflow (Discovery-First)
 30 | 
 31 | **Step 1: Discovery Commands (Mandatory)**
 32 | ```bash
 33 | # Test successful execution
 34 | npx aegis query [tool_name] '[valid_params]' --config "config.json"
 35 | 
 36 | # Test failure scenarios  
 37 | npx aegis query [tool_name] '[invalid_params]' --config "config.json"
 38 | npx aegis query [tool_name] '' --config "config.json"  # Empty/missing params
 39 | ```
 40 | 
 41 | **Step 2: Document Findings & Write Tests**
 42 | ```yaml
 43 | # Document discovery results as comments
 44 | # Discovery: npx aegis query search_sfcc_classes 'query:catalog'
 45 | # Success: ["dw.catalog.Product", "dw.catalog.Catalog"] (simple array)
 46 | # Empty: [] (empty array)
 47 | # Error: {"content": [{"type": "text", "text": "Error: ..."}], "isError": true}
 48 | 
 49 | - it: "should return class array"
 50 |   expect:
 51 |     response:
 52 |       result:
 53 |         text: "match:regex:\\[[\\s\\S]*\\]"     # Based on actual format
 54 |         text: "match:contains:dw.catalog"      # Contains expected content
 55 | ```
 56 | 
 57 | **Step 3: Common Discovery Patterns**
 58 | ```bash
 59 | # Examples with expected patterns:
 60 | npx aegis query list_tools --config "config.json"
 61 | # → ["tool1", "tool2"] → YAML: text: "match:regex:\\[[\\s\\S]*\\]"
 62 | 
 63 | npx aegis query search_nothing 'query:zzznothingfound' --config "config.json"  
 64 | # → [] → YAML: text: "match:regex:^\\[\\s*\\]$"
 65 | 
 66 | npx aegis query invalid_tool 'bad:params' --config "config.json"
 67 | # → {"content": [...], "isError": true} → YAML: text: "match:contains:Error"
 68 | ```
 69 | 
 70 | ## Configuration & Basic Usage
 71 | 
 72 | ### Required Configuration (`*.config.json`)
 73 | ```json
 74 | {
 75 |   "name": "My MCP Server",                    // Human-readable name for reports
 76 |   "command": "node",                          // Executable (node, python, ./binary)
 77 |   "args": ["./server.js"],                    // Arguments array
 78 |   "cwd": "./optional/directory",              // Working directory (optional)
 79 |   "env": {"CUSTOM_VAR": "value"},            // Environment variables (optional)
 80 |   "startupTimeout": 5000,                     // Max startup wait (ms, default: 10000)
 81 |   "readyPattern": "Server ready"              // Stderr regex for ready state (optional)
 82 | }
 83 | ```
 84 | 
 85 | ### Common Configurations
 86 | ```json
 87 | // Node.js Server
 88 | {"name": "Node.js MCP", "command": "node", "args": ["./dist/index.js"], "startupTimeout": 5000}
 89 | 
 90 | // Python Server  
 91 | {"name": "Python MCP", "command": "python", "args": ["-m", "my_mcp_server"], "env": {"PYTHONPATH": "./src"}}
 92 | 
 93 | // Development Server
 94 | {"name": "Dev Server", "command": "npm", "args": ["run", "dev"], "startupTimeout": 15000}
 95 | ```
 96 | ### Basic Test Structure (`*.test.mcp.yml`)
 97 | ```yaml
 98 | description: "Test suite description"
 99 | tests:
100 |   - it: "Test description"
101 |     request:
102 |       jsonrpc: "2.0"
103 |       id: "unique-id"
104 |       method: "tools/list|tools/call"
105 |       params: {}  # or tool call params
106 |     expect:
107 |       response:
108 |         jsonrpc: "2.0"
109 |         id: "unique-id"
110 |         result: {}  # expected response
111 |       stderr: "toBeEmpty"  # optional
112 | ```
113 | 
114 | ### Execute Tests
115 | ```bash
116 | # 🥇 GOLDEN RULE: ALWAYS discover response formats first!
117 | # Before writing any test, run these discovery commands:
118 | npx aegis query [tool_name] '[success_params]' --config "config.json"
119 | npx aegis query [tool_name] '[failure_params]' --config "config.json"
120 | 
121 | # Then run your tests based on discovered formats
122 | npx aegis "tests/**/*.test.mcp.yml" --config "config.json"
123 | npx aegis "tests/*.yml" --config "config.json" --verbose --filter "tools"
124 | ```
125 | 
126 | ## Parameter Formats: Pipe vs JSON
127 | 
128 | ### Pipe Format (Recommended for CLI)
129 | ```bash
130 | # Simple: No parameters
131 | npx aegis query get_code_versions --config "config.json"
132 | 
133 | # Simple: key:value|other:123
134 | npx aegis query read_file 'path:test.txt' --config "config.json"
135 | 
136 | # Nested via dot notation: config.host:localhost|config.port:8080
137 | npx aegis query api_client 'config.host:localhost|config.port:8080|timeout:30' --config "config.json"
138 | 
139 | # Method syntax: name:tool|arguments.key:value
140 | npx aegis query --method tools/call --params 'name:read_file|arguments.path:test.txt' --config "config.json"
141 | ```
142 | 
143 | ### JSON Format (Complex Structures)
144 | ```bash
145 | npx aegis query complex_tool '{"config": {"host": "localhost"}, "data": [1,2,3]}' --config "config.json"
146 | ```
147 | 
148 | **Note**: For tools with no parameters, omit arguments entirely rather than using `'{}'` - use `npx aegis query tool_name --config "config.json"` instead of `npx aegis query tool_name '{}' --config "config.json"`.
149 | 
150 | ## Complete Pattern Matching Reference (35+)
151 | 
152 | ### Core Patterns
153 | ```yaml
154 | # 1. DEEP EQUALITY (default) - Exact match
155 | result: {tools: [{name: "read_file", description: "Reads a file"}]}
156 | 
157 | # 2. TYPE VALIDATION
158 | result:
159 |   tools: "match:type:array"
160 |   count: "match:type:number"
161 |   name: "match:type:string"
162 | 
163 | # 3. STRING PATTERNS
164 | result:
165 |   text: "match:contains:substring"
166 |   name: "match:startsWith:prefix"
167 |   file: "match:endsWith:.txt"
168 |   pattern: "match:regex:\\d{4}-\\d{2}-\\d{2}"  # YAML: escape backslashes
169 |   
170 |   # String length validation
171 |   title: "match:stringLength:10"                        # Exactly 10 characters
172 |   description: "match:stringLengthGreaterThan:5"        # More than 5 chars
173 |   summary: "match:stringLengthLessThan:100"             # Less than 100 chars
174 |   content: "match:stringLengthBetween:10:200"           # Between 10-200 chars
175 |   error: "match:stringEmpty"                            # Must be empty
176 |   text: "match:stringNotEmpty"                          # Must not be empty
177 | 
178 | # 4. ARRAY PATTERNS
179 | result:
180 |   tools: "match:arrayLength:3"
181 |   data: "match:arrayContains:value"
182 |   tools: "match:arrayContains:name:read_file"  # Object field matching
183 |   tools:
184 |     match:arrayElements:  # All elements must match
185 |       name: "match:type:string"
186 |       description: "match:contains:tool"
187 | 
188 | # 5. FIELD EXTRACTION (dot notation)
189 | result:
190 |   match:extractField: "tools.*.name"  # Extract all tool names
191 |   value: ["read_file", "write_file"]
192 | 
193 | # 6. NUMERIC COMPARISONS
194 | result:
195 |   count: "match:greaterThan:5"
196 |   price: "match:lessThanOrEqual:100.50"
197 |   amount: "match:greaterThanOrEqual:0"
198 |   score: "match:between:0:100"
199 |   exact: "match:equals:42"
200 |   not_equal: "match:notEquals:0"
201 |   approximate: "match:approximately:3.14159:0.001"  # tolerance
202 |   decimal: "match:decimalPlaces:2"  # exactly 2 decimal places
203 |   multiple: "match:multipleOf:5"    # divisible by 5
204 | 
205 | # 7. DATE/TIMESTAMP PATTERNS
206 | result:
207 |   createdAt: "match:dateValid"
208 |   publishDate: "match:dateAfter:2023-01-01"
209 |   expireDate: "match:dateBefore:2025-01-01"
210 |   eventDate: "match:dateBetween:2023-01-01:2024-12-31"
211 |   lastUpdate: "match:dateAge:1d"  # within last day
212 | 
213 | # 8. CROSS-FIELD VALIDATION
214 | result:
215 |   "match:crossField": "price > minPrice"  # Field comparison
216 |   "match:crossField": "endDate >= startDate"
217 | 
218 | # 9. PATTERN NEGATION (prefix with "not:")
219 | result:
220 |   tools: "match:not:arrayLength:0"  # NOT empty
221 |   text: "match:not:contains:error"  # NOT containing error
222 | 
223 | # 10. PARTIAL MATCHING
224 | result:
225 |   match:partial:  # Only check specified fields
226 |     tools:
227 |       - name: "read_file"
228 |         description: "match:contains:Reads"
229 | 
230 | # 11. COMBINED PATTERNS - arrayElements + partial (POWERFUL!)
231 | result:
232 |   tools:
233 |     match:arrayElements:  # Apply to ALL array elements
234 |       match:partial:      # But only validate specified fields
235 |         name: "match:regex:^[a-z_]+$"
236 |         description: "match:contains:tool"
237 |         # Ignores any other fields like inputSchema, etc.
238 | 
239 | # 12. ADVANCED PATTERNS
240 | result:
241 |   email: "match:regex:[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"
242 |   name: "match:equalsIgnoreCase:Hello World"
243 |   text: "match:containsIgnoreCase:ERROR"
244 |   value: "match:range:0:100"        # Between 0-100 inclusive
245 |   score: "match:between:60:90"      # Between 60-90 exclusive
246 |   match:extractField: "tools.*.inputSchema.properties.*.type"
247 |   value: ["string", "number", "object"]
248 | ```
249 | 
250 | ## YAML Syntax Rules & Real-World Examples
251 | 
252 | ### ❌ Common Errors to Avoid
253 | ```yaml
254 | # WRONG - Duplicate YAML keys (overwrites previous)
255 | result:
256 |   tools: "match:arrayLength:1"
257 |   tools: ["read_file"]  # OVERWRITES above line!
258 | 
259 | # WRONG - Invalid escaping in regex
260 | result:
261 |   text: "match:regex:\d+"  # Missing double backslash
262 | 
263 | # WRONG - Mixing patterns in same object
264 | result:
265 |   tools: "match:arrayLength:1"
266 |   match:extractField: "tools.*.name"  # Can't mix in same object
267 | ```
268 | 
269 | ### ✅ Correct Best Practices
270 | ```yaml
271 | # CORRECT - Separate pattern validations into different tests
272 | - it: "should have exactly one tool"
273 |   expect:
274 |     response:
275 |       result:
276 |         tools: "match:arrayLength:1"
277 | 
278 | - it: "should extract correct tool name"
279 |   expect:
280 |     response:
281 |       result:
282 |         match:extractField: "tools.*.name"
283 |         value: ["read_file"]
284 | 
285 | # CORRECT - Proper regex escaping in YAML
286 | result:
287 |   text: "match:regex:\\d{4}-\\d{2}-\\d{2}"  # Double backslashes
288 | 
289 | # CORRECT - Flexible arrayElements with partial matching
290 | result:
291 |   tools:
292 |     match:arrayElements:
293 |       match:partial:  # 🔥 Only validate what matters, ignore extra fields
294 |         name: "match:regex:^[a-z][a-z0-9_]*$"  
295 |         description: "match:regex:.{10,}"
296 | ```
297 | 
298 | ### Real-World Test Examples
299 | 
300 | #### Tool Discovery Test
301 | ```yaml
302 | - it: "should list available tools with correct structure"
303 |   request:
304 |     jsonrpc: "2.0"
305 |     id: "list-1"
306 |     method: "tools/list"
307 |     params: {}
308 |   expect:
309 |     response:
310 |       jsonrpc: "2.0"
311 |       id: "list-1"
312 |       result:
313 |         tools:
314 |           match:arrayElements:
315 |             match:partial:  # Flexible - recommended approach
316 |               name: "match:regex:^[a-z][a-z0-9_]*$"  # snake_case
317 |               description: "match:regex:.{10,}"      # min 10 chars
318 | ```
319 | 
320 | #### Tool Execution Test
321 | ```yaml
322 | - it: "should execute tool successfully"
323 |   request:
324 |     jsonrpc: "2.0"
325 |     id: "exec-1" 
326 |     method: "tools/call"
327 |     params:
328 |       name: "read_file"
329 |       arguments:
330 |         path: "test.txt"
331 |   expect:
332 |     response:
333 |       jsonrpc: "2.0"
334 |       id: "exec-1"
335 |       result:
336 |         content:
337 |           - type: "text"
338 |             text: "match:contains:expected content"
339 |         isError: false
340 |     stderr: "toBeEmpty"
341 | ```
342 | 
343 | #### Error Handling Test
344 | ```yaml
345 | - it: "should handle invalid file gracefully"
346 |   request:
347 |     jsonrpc: "2.0"
348 |     id: "error-1"
349 |     method: "tools/call"
350 |     params:
351 |       name: "read_file"
352 |       arguments:
353 |         path: "nonexistent.txt"
354 |   expect:
355 |     response:
356 |       jsonrpc: "2.0" 
357 |       id: "error-1"
358 |       result:
359 |         content:
360 |           - type: "text"
361 |             text: "match:contains:not found"
362 |         isError: true
363 | ```
364 | 
365 | #### Performance Testing with SLA Validation
366 | ```yaml
367 | - it: "should meet performance SLA"
368 |   request:
369 |     jsonrpc: "2.0"
370 |     id: "perf-1"
371 |     method: "tools/call"
372 |     params:
373 |       name: "search_tools"
374 |       arguments:
375 |         category: "documentation"
376 |   expect:
377 |     response:
378 |       result:
379 |         tools:
380 |           match:arrayElements:
381 |             match:partial:
382 |               name: "match:regex:^[a-z][a-z0-9_]*$"
383 |               description: "match:regex:.{10,}"
384 |         count: "match:type:number"
385 |     performance:
386 |       maxResponseTime: "1500ms"  # SLA requirement
387 |     stderr: "toBeEmpty"
388 | ```
389 | 
390 | ## CLI Commands & Interactive Testing
391 | 
392 | ### Test Execution
393 | ```bash
394 | # Basic testing
395 | npx aegis "tests/**/*.test.mcp.yml" --config "config.json"
396 | 
397 | # Debug modes & filtering
398 | npx aegis "tests/*.yml" --config "config.json" --verbose --debug --timing
399 | npx aegis "tests/*.yml" --config "config.json" --errors-only --filter "tools"
400 | npx aegis "tests/*.yml" --config "config.json" --filter "should validate" --json
401 | ```
402 | 
403 | ### Interactive Tool Testing (Dual Format Support)
404 | ```bash
405 | # List all tools
406 | npx aegis query --config "config.json"
407 | 
408 | # Pipe format (recommended) - ALWAYS test success AND failure
409 | npx aegis query read_file 'path:test.txt' --config "config.json"        # Success case
410 | npx aegis query read_file 'path:nonexistent.txt' --config "config.json" # Failure case
411 | 
412 | # JSON format for complex structures
413 | npx aegis query complex_tool '{"config": {"host": "localhost"}, "data": [1,2,3]}' --config "config.json"
414 | 
415 | # Discovery examples for edge cases
416 | npx aegis query search_tool 'query:' --config "config.json"           # Empty input
417 | npx aegis query search_tool 'query:zzznothingfound' --config "config.json" # No results
418 | ```
419 | 
420 | ### Performance Testing Guidelines
421 | | Operation Type | Recommended Timeout | Use Case |
422 | |----------------|-------------------|----------|
423 | | Tool Listing | `200-500ms` | Metadata operations |
424 | | Simple File Ops | `1000ms` | Basic I/O |
425 | | Complex Operations | `2000ms` | Search, computation |
426 | | Error Responses | `800ms` | Should be faster than success |
427 | | Heavy Operations | `5000ms` | Database, large files |
428 | 
429 | **Always use `--timing` flag** to see actual response times and adjust expectations.
430 | 
431 | ## Pattern Selection Guide & Advanced Combinations
432 | 
433 | ### When to Use Each Pattern
434 | - **Deep Equality**: Exact value matching (default)
435 | - **Type Validation**: Verify data types (`match:type:`)  
436 | - **String Patterns**: Text validation (`contains`, `startsWith`, `endsWith`, `regex`, `stringLength`)
437 | - **Array Patterns**: Array validation (`arrayLength`, `arrayContains`, `arrayElements`)
438 | - **Field Extraction**: Extract nested values (`match:extractField`)
439 | - **Numeric**: Math comparisons (`greaterThan`, `approximately`, `decimalPlaces`)
440 | - **Date/Time**: Date validation (`dateValid`, `dateAfter`, `dateAge`)
441 | - **Cross-Field**: Compare fields (`match:crossField`)
442 | - **Negation**: Exclude patterns (`match:not:*`)
443 | - **Partial**: Subset validation (`match:partial`)
444 | - **🔥 Combined arrayElements + partial**: Validate specific fields across ALL array elements while ignoring others - extremely powerful for flexible schema validation!
445 | 
446 | ### Multi-Step Validation Pattern (Recommended)
447 | ```yaml
448 | # Test 1: Basic structure
449 | - it: "should return array of tools"
450 |   expect:
451 |     response:
452 |       result:
453 |         tools: "match:type:array"
454 | 
455 | # Test 2: Array length  
456 | - it: "should have expected number of tools"
457 |   expect:
458 |     response:
459 |       result:
460 |         tools: "match:arrayLength:3"
461 | 
462 | # Test 3: Extract and validate specific fields
463 | - it: "should have correct tool names"
464 |   expect:
465 |     response:
466 |       result:
467 |         match:extractField: "tools.*.name"
468 |         value: ["read_file", "write_file", "list_files"]
469 | ```
470 | 
471 | ### Comprehensive Tool Validation (Best Practice)
472 | ```yaml
473 | - it: "should validate tools with flexible schema handling"
474 |   expect:
475 |     response:
476 |       result:
477 |         tools:
478 |           match:arrayElements:
479 |             match:partial:  # 🔥 RECOMMENDED: Combines power with flexibility
480 |               name: "match:regex:^[a-z][a-z0-9_]*$"     # snake_case names
481 |               description: "match:regex:.{10,200}"       # 10-200 chars
482 |               inputSchema:
483 |                 type: "object"
484 |                 properties: "match:type:object"
485 | ```
486 | 
487 | ## MCP Protocol Basics
488 | 
489 | ### Standard JSON-RPC 2.0 Methods
490 | 
491 | #### Initialize Request (Required for handshake)
492 | ```yaml
493 | request:
494 |   jsonrpc: "2.0"
495 |   id: "init-1"
496 |   method: "initialize"  
497 |   params:
498 |     protocolVersion: "2025-06-18"
499 |     capabilities: {"tools": {}}
500 |     clientInfo: {"name": "MCP Aegis", "version": "1.0.0"}
501 | ```
502 | 
503 | #### Tools List Request  
504 | ```yaml
505 | request:
506 |   jsonrpc: "2.0"
507 |   id: "list-1"
508 |   method: "tools/list"
509 |   params: {}
510 | ```
511 | 
512 | #### Tool Call Request
513 | ```yaml
514 | request:
515 |   jsonrpc: "2.0"
516 |   id: "call-1"
517 |   method: "tools/call"
518 |   params:
519 |     name: "tool_name"
520 |     arguments:
521 |       key: "value"
522 | ```
523 | 
524 | ### Standard Response Structure
525 | ```yaml
526 | expect:
527 |   response:
528 |     jsonrpc: "2.0"
529 |     id: "matching-request-id"
530 |     result:           # For successful responses
531 |       # Response data
532 |     # OR for errors:
533 |     error:
534 |       code: -32601    # Standard JSON-RPC error codes
535 |       message: "Method not found"
536 |   stderr: "toBeEmpty"  # Optional stderr validation
537 | ```
538 | 
539 | ## MCP Protocol Basics & Troubleshooting
540 | 
541 | ### Standard JSON-RPC 2.0 Methods
542 | ```yaml
543 | # Initialize Request (Required for handshake)
544 | request:
545 |   jsonrpc: "2.0"
546 |   id: "init-1"
547 |   method: "initialize"  
548 |   params:
549 |     protocolVersion: "2025-06-18"
550 |     capabilities: {"tools": {}}
551 |     clientInfo: {"name": "MCP Aegis", "version": "1.0.0"}
552 | 
553 | # Tools List Request  
554 | request:
555 |   jsonrpc: "2.0"
556 |   id: "list-1"
557 |   method: "tools/list"
558 |   params: {}
559 | 
560 | # Tool Call Request
561 | request:
562 |   jsonrpc: "2.0"
563 |   id: "call-1"
564 |   method: "tools/call"
565 |   params:
566 |     name: "tool_name"
567 |     arguments:
568 |       key: "value"
569 | 
570 | # Standard Response Structure
571 | expect:
572 |   response:
573 |     jsonrpc: "2.0"
574 |     id: "matching-request-id"
575 |     result: {}           # For successful responses
576 |     # OR for errors:
577 |     error:
578 |       code: -32601       # Standard JSON-RPC error codes
579 |       message: "Method not found"
580 |   stderr: "toBeEmpty"    # Optional stderr validation
581 | ```
582 | 
583 | ## Quick Debugging Workflow
584 | 
585 | ### 🥇 Discovery-First Development (Essential)
586 | 
587 | **Before ANY test writing, run discovery commands:**
588 | ```bash
589 | # Success case
590 | npx aegis query [tool_name] '[valid_params]' --config "config.json"
591 | # Failure case  
592 | npx aegis query [tool_name] '[invalid_params]' --config "config.json"
593 | ```
594 | 
595 | ### Common Issues & Solutions
596 | 
597 | #### Server Won't Start
598 | ```bash
599 | npx aegis "test.yml" --config "config.json" --debug  # Check startup issues
600 | # Increase startupTimeout if server is slow
601 | # Verify command/args in config are correct
602 | ```
603 | 
604 | #### Pattern Not Matching
605 | ```yaml
606 | # ❌ WRONG - Assuming structure without discovery
607 | - it: "should return tools object"
608 |   expect:
609 |     response:
610 |       result:
611 |         tools: "match:arrayLength:3"      # Assumes 'tools' field exists
612 |         count: "match:type:number"        # Assumes 'count' field exists
613 | 
614 | # ✅ CORRECT - Based on actual npx aegis query discovery
615 | # First run: npx aegis query list_tools --config "config.json"
616 | # Result: ["tool1", "tool2", "tool3"]  # Simple array, no wrapper object!
617 | - it: "should return tools array"
618 |   expect:
619 |     response:
620 |       result:
621 |         text: "match:regex:\\[[\\s\\S]*\\]"        # Match JSON array format
622 |         text: "match:contains:tool1"               # Check content exists
623 | ```
624 | 
625 | #### Regex Patterns Failing
626 | ```yaml
627 | # YAML requires double escaping backslashes
628 | text: "match:regex:\\d+"        # ✅ Correct  
629 | text: "match:regex:\d+"         # ❌ Wrong
630 | ```
631 | 
632 | #### Array Pattern Issues
633 | ```yaml
634 | # ❌ Duplicate YAML keys (overwrites previous)
635 | result:
636 |   tools: "match:arrayLength:1"
637 |   tools: ["exact_tool"]  # OVERWRITES above!
638 | 
639 | # ✅ Use flexible arrayElements with partial matching instead
640 | result:
641 |   tools:
642 |     match:arrayElements:
643 |       match:partial:  # Only validate what you care about
644 |         name: "match:type:string"
645 |         description: "match:type:string"
646 | ```
647 | 
648 | ### 2. Test Filtering for Focus Development
649 | ```bash
650 | # Filter by suite/test description (case-insensitive)
651 | npx aegis "tests/**/*.yml" --config "config.json" --filter "Tools validation"
652 | npx aegis "tests/**/*.yml" --config "config.json" --filter "should handle errors"
653 | 
654 | # Use regex patterns for advanced filtering  
655 | npx aegis "tests/**/*.yml" --config "config.json" --filter "/should (read|write|validate)/"
656 | 
657 | # Combine with debugging options
658 | npx aegis "tests/**/*.yml" --config "config.json" --filter "tools" --errors-only --timing
659 | ```
660 | 
661 | ### 3. Pipe Format Quick Reference
662 | ```bash
663 | # Basic syntax: key:value|other:123
664 | 'path:test.txt|encoding:utf8'
665 | 
666 | # Nested objects: config.host:localhost|config.port:8080
667 | 'database.host:localhost|database.port:5432|cache.enabled:true'
668 | 
669 | # Auto data types: text:hello|count:42|enabled:true|data:null
670 | 'operation:add|a:5|b:3|precise:true'
671 | 
672 | # JSON values within pipe: simple:value|complex:{"nested":"object"}
673 | 'metadata:{"version":"1.0"}|tags:["test","demo"]|count:5'
674 | ```
675 | 
676 | ### 3. Pipe Format Quick Reference
677 | ```bash
678 | # Basic: key:value|other:123
679 | 'path:test.txt|encoding:utf8'
680 | 
681 | # Discovery examples:
682 | 'query:catalog|limit:10'        # Success case
683 | 'query:zzznothingfound'         # No results
684 | 'query:'                        # Validation error
685 | 'fileName:nonexistent.txt'      # File not found
686 | ```
687 | 
688 | ### 4. Performance Testing Guidelines
689 | - **Tool Listing**: 200-500ms (metadata - should be fast)
690 | - **Simple File Ops**: 1000ms (basic I/O operations)  
691 | - **Complex Operations**: 2000ms (search, computation, API calls)
692 | - **Error Responses**: 800ms (often faster than successful operations)
693 | - **Heavy Operations**: 5000ms (database queries, large file processing)
694 | 
695 | Use `--timing` flag to see actual response times and adjust expectations based on your environment.
696 | 
697 | ## Complete Pattern Reference (35+ Patterns)
698 | 
699 | ```yaml
700 | # STRING PATTERNS
701 | "match:contains:substring"
702 | "match:startsWith:prefix"  
703 | "match:endsWith:suffix"
704 | "match:containsIgnoreCase:TEXT"
705 | "match:equalsIgnoreCase:value"
706 | "match:regex:pattern"
707 | "match:stringLength:10"
708 | "match:stringLengthGreaterThan:5"
709 | "match:stringLengthLessThan:100"
710 | "match:stringLengthBetween:10:200"
711 | "match:stringEmpty|stringNotEmpty"
712 | 
713 | # TYPE & STRUCTURE
714 | "match:type:string|number|boolean|object|array"
715 | "match:exists"
716 | "match:length:5"
717 | 
718 | # NUMERIC PATTERNS
719 | "match:greaterThan:10"
720 | "match:lessThanOrEqual:50"
721 | "match:between:10:90"
722 | "match:equals:42"
723 | "match:notEquals:0"
724 | "match:approximately:3.14:0.01"
725 | "match:multipleOf:5"
726 | "match:decimalPlaces:2"
727 | 
728 | # ARRAY PATTERNS
729 | "match:arrayLength:3"
730 | "match:arrayContains:value"
731 | match:arrayElements:
732 |   field: "match:type:string"
733 | 
734 | # DATE PATTERNS
735 | "match:dateValid"
736 | "match:dateAfter:2023-01-01"
737 | "match:dateBetween:2023-01-01:2024-12-31"
738 | "match:dateAge:1d"
739 | 
740 | # ADVANCED PATTERNS
741 | "match:crossField": "field1 > field2"
742 | "match:not:pattern"  # Negate any pattern
743 | match:extractField: "path.*.field"
744 | match:partial:       # Check subset of fields
745 | match:arrayElements: # Validate ALL array elements
746 |   match:partial:     # 🔥 POWERFUL COMBO
747 |     field: "pattern"
748 | ```
749 | 
750 | ### Installation & Getting Started
751 | ```bash
752 | npm install -g mcp-aegis
753 | npx aegis init                    # Create sample config and tests
754 | npx aegis "tests/*.yml" --config "config.json"
755 | ```
756 | 
```

--------------------------------------------------------------------------------
/docs/dw_customer/ProductList.md:
--------------------------------------------------------------------------------

```markdown
   1 | ## Package: dw.customer
   2 | 
   3 | # Class ProductList
   4 | 
   5 | ## Inheritance Hierarchy
   6 | 
   7 | - Object
   8 |   - dw.object.PersistentObject
   9 |   - dw.object.ExtensibleObject
  10 |     - dw.customer.ProductList
  11 | 
  12 | ## Description
  13 | 
  14 | Represents a list of products (and optionally a gift certificate) that is typically maintained by a customer. This class can be used to implement a number of different storefront features, e.g. shopping list, wish list and gift registry. A product list is always owned by a customer. The owner can be anonymous or a registered customer. The owner can be the person for which items from that list will be purchased (wish list). Or it can be a person who maintains the list, for example a gift registry, on behalf of the bridal couple. Each product list can have a registrant and a co-registrant. A registrant is typically associated with an event related product list such as a gift registry. It holds information about a person associated with the event such as a bride or groom. A shipping address can be associated with this product list to ship the items, e.g. to an event location. A post-event shipping address can be associated to ship items to which could not be delivered on event date. The product list can also hold information about the event date and event location.
  15 | 
  16 | ## Constants
  17 | 
  18 | ### EXPORT_STATUS_EXPORTED
  19 | 
  20 | **Type:** Number = 1
  21 | 
  22 | Constant for when Export Status is Exported
  23 | 
  24 | ### EXPORT_STATUS_NOTEXPORTED
  25 | 
  26 | **Type:** Number = 0
  27 | 
  28 | Constant for when Export Status is Not Exported
  29 | 
  30 | ### TYPE_CUSTOM_1
  31 | 
  32 | **Type:** Number = 100
  33 | 
  34 | Constant representing a custom list type attribute.
  35 | 
  36 | ### TYPE_CUSTOM_2
  37 | 
  38 | **Type:** Number = 101
  39 | 
  40 | Constant representing a custom list type attribute.
  41 | 
  42 | ### TYPE_CUSTOM_3
  43 | 
  44 | **Type:** Number = 102
  45 | 
  46 | Constant representing a custom list type attribute.
  47 | 
  48 | ### TYPE_GIFT_REGISTRY
  49 | 
  50 | **Type:** Number = 11
  51 | 
  52 | Constant representing the gift registry type attribute.
  53 | 
  54 | ### TYPE_SHOPPING_LIST
  55 | 
  56 | **Type:** Number = 12
  57 | 
  58 | Constant representing the shopping list type attribute.
  59 | 
  60 | ### TYPE_WISH_LIST
  61 | 
  62 | **Type:** Number = 10
  63 | 
  64 | Constant representing the wish list registry type attribute.
  65 | 
  66 | ## Properties
  67 | 
  68 | ### anonymous
  69 | 
  70 | **Type:** boolean (Read Only)
  71 | 
  72 | Returns true if this product list is owned by an anonymous customer.
  73 | 
  74 | ### coRegistrant
  75 | 
  76 | **Type:** ProductListRegistrant (Read Only)
  77 | 
  78 | The ProductListRegistrant assigned to the coRegistrant attribute or null
  79 |  if this list has no co-registrant.
  80 | 
  81 | ### currentShippingAddress
  82 | 
  83 | **Type:** CustomerAddress (Read Only)
  84 | 
  85 | This is a helper method typically used with an event related list.
  86 |  It provides the appropriate shipping address based on the eventDate.
  87 |  If the current date is after the eventDate, then the postEventShippingAddress
  88 |  is returned, otherwise the shippingAddress is returned.  If the eventDate
  89 |  is null, then null is returned.
  90 | 
  91 | ### description
  92 | 
  93 | **Type:** String
  94 | 
  95 | A description text that, for example, explains the purpose of this product list.
  96 | 
  97 | ### eventCity
  98 | 
  99 | **Type:** String
 100 | 
 101 | For event related uses (e.g. gift registry), this holds the event city.
 102 | 
 103 | ### eventCountry
 104 | 
 105 | **Type:** String
 106 | 
 107 | For event related uses (e.g. gift registry), this holds the event country.
 108 | 
 109 | ### eventDate
 110 | 
 111 | **Type:** Date
 112 | 
 113 | For event related uses (e.g. gift registry), this holds the date
 114 |  of the event.
 115 | 
 116 | ### eventState
 117 | 
 118 | **Type:** String
 119 | 
 120 | For event related uses (e.g. gift registry), this holds the event state.
 121 | 
 122 | ### eventType
 123 | 
 124 | **Type:** String
 125 | 
 126 | For event related uses (e.g. gift registry), this holds the type
 127 |  of event, e.g. Wedding, Baby Shower.
 128 | 
 129 | ### exportStatus
 130 | 
 131 | **Type:** EnumValue (Read Only)
 132 | 
 133 | The export status of the product list.
 134 |  Possible values are: EXPORT_STATUS_NOTEXPORTED,
 135 |  EXPORT_STATUS_EXPORTED.
 136 | 
 137 | ### giftCertificateItem
 138 | 
 139 | **Type:** ProductListItem (Read Only)
 140 | 
 141 | The item in the list that represents a gift certificate.
 142 | 
 143 | ### ID
 144 | 
 145 | **Type:** String (Read Only)
 146 | 
 147 | The unique system generated ID of the object.
 148 | 
 149 | ### items
 150 | 
 151 | **Type:** Collection (Read Only)
 152 | 
 153 | A collection containing all items in the list.
 154 | 
 155 | ### lastExportTime
 156 | 
 157 | **Type:** Date (Read Only)
 158 | 
 159 | The date where this product list has been exported successfully
 160 |  the last time.
 161 | 
 162 | ### name
 163 | 
 164 | **Type:** String
 165 | 
 166 | The name of this product list given by its owner.
 167 | 
 168 | ### owner
 169 | 
 170 | **Type:** Customer (Read Only)
 171 | 
 172 | The customer that created and owns the product list.
 173 | 
 174 | ### postEventShippingAddress
 175 | 
 176 | **Type:** CustomerAddress
 177 | 
 178 | The shipping address for purchases made after the event date.
 179 | 
 180 | ### productItems
 181 | 
 182 | **Type:** Collection (Read Only)
 183 | 
 184 | A collection containing all items in the list that reference products.
 185 | 
 186 | ### public
 187 | 
 188 | **Type:** boolean
 189 | 
 190 | A flag, typically used to determine if the object is searchable
 191 |  by other customers.
 192 | 
 193 | ### publicItems
 194 | 
 195 | **Type:** Collection (Read Only)
 196 | 
 197 | A collection containing all items in the list that are flagged as public.
 198 | 
 199 | ### purchases
 200 | 
 201 | **Type:** Collection (Read Only)
 202 | 
 203 | The aggregated purchases from all the individual items.
 204 | 
 205 | ### registrant
 206 | 
 207 | **Type:** ProductListRegistrant (Read Only)
 208 | 
 209 | The ProductListRegistrant assigned to the registrant attribute or null
 210 |  if this list has no registrant.
 211 | 
 212 | ### shippingAddress
 213 | 
 214 | **Type:** CustomerAddress
 215 | 
 216 | Return the address that should be used as the shipping address for purchases
 217 |  made from the list.
 218 | 
 219 | ### type
 220 | 
 221 | **Type:** Number (Read Only)
 222 | 
 223 | An int representing the type of object (e.g. wish list,
 224 |  gift registry). This is set at object creation time.
 225 | 
 226 | ## Constructor Summary
 227 | 
 228 | ## Method Summary
 229 | 
 230 | ### createCoRegistrant
 231 | 
 232 | **Signature:** `createCoRegistrant() : ProductListRegistrant`
 233 | 
 234 | Create a ProductListRegistrant and assign it to the coRegistrant attribute of the list.
 235 | 
 236 | ### createGiftCertificateItem
 237 | 
 238 | **Signature:** `createGiftCertificateItem() : ProductListItem`
 239 | 
 240 | Create an item in the list that represents a gift certificate.
 241 | 
 242 | ### createProductItem
 243 | 
 244 | **Signature:** `createProductItem(product : Product) : ProductListItem`
 245 | 
 246 | Create an item in the list that references the specified product.
 247 | 
 248 | ### createRegistrant
 249 | 
 250 | **Signature:** `createRegistrant() : ProductListRegistrant`
 251 | 
 252 | Create a ProductListRegistrant and assign it to the registrant attribute of the list.
 253 | 
 254 | ### getCoRegistrant
 255 | 
 256 | **Signature:** `getCoRegistrant() : ProductListRegistrant`
 257 | 
 258 | Returns the ProductListRegistrant assigned to the coRegistrant attribute or null if this list has no co-registrant.
 259 | 
 260 | ### getCurrentShippingAddress
 261 | 
 262 | **Signature:** `getCurrentShippingAddress() : CustomerAddress`
 263 | 
 264 | This is a helper method typically used with an event related list.
 265 | 
 266 | ### getDescription
 267 | 
 268 | **Signature:** `getDescription() : String`
 269 | 
 270 | Returns a description text that, for example, explains the purpose of this product list.
 271 | 
 272 | ### getEventCity
 273 | 
 274 | **Signature:** `getEventCity() : String`
 275 | 
 276 | For event related uses (e.g.
 277 | 
 278 | ### getEventCountry
 279 | 
 280 | **Signature:** `getEventCountry() : String`
 281 | 
 282 | For event related uses (e.g.
 283 | 
 284 | ### getEventDate
 285 | 
 286 | **Signature:** `getEventDate() : Date`
 287 | 
 288 | For event related uses (e.g.
 289 | 
 290 | ### getEventState
 291 | 
 292 | **Signature:** `getEventState() : String`
 293 | 
 294 | For event related uses (e.g.
 295 | 
 296 | ### getEventType
 297 | 
 298 | **Signature:** `getEventType() : String`
 299 | 
 300 | For event related uses (e.g.
 301 | 
 302 | ### getExportStatus
 303 | 
 304 | **Signature:** `getExportStatus() : EnumValue`
 305 | 
 306 | Returns the export status of the product list. Possible values are: EXPORT_STATUS_NOTEXPORTED, EXPORT_STATUS_EXPORTED.
 307 | 
 308 | ### getGiftCertificateItem
 309 | 
 310 | **Signature:** `getGiftCertificateItem() : ProductListItem`
 311 | 
 312 | Returns the item in the list that represents a gift certificate.
 313 | 
 314 | ### getID
 315 | 
 316 | **Signature:** `getID() : String`
 317 | 
 318 | Returns the unique system generated ID of the object.
 319 | 
 320 | ### getItem
 321 | 
 322 | **Signature:** `getItem(ID : String) : ProductListItem`
 323 | 
 324 | Returns the item from the list that has the specified ID.
 325 | 
 326 | ### getItems
 327 | 
 328 | **Signature:** `getItems() : Collection`
 329 | 
 330 | Returns a collection containing all items in the list.
 331 | 
 332 | ### getLastExportTime
 333 | 
 334 | **Signature:** `getLastExportTime() : Date`
 335 | 
 336 | Returns the date where this product list has been exported successfully the last time.
 337 | 
 338 | ### getName
 339 | 
 340 | **Signature:** `getName() : String`
 341 | 
 342 | Returns the name of this product list given by its owner.
 343 | 
 344 | ### getOwner
 345 | 
 346 | **Signature:** `getOwner() : Customer`
 347 | 
 348 | Returns the customer that created and owns the product list.
 349 | 
 350 | ### getPostEventShippingAddress
 351 | 
 352 | **Signature:** `getPostEventShippingAddress() : CustomerAddress`
 353 | 
 354 | Returns the shipping address for purchases made after the event date.
 355 | 
 356 | ### getProductItems
 357 | 
 358 | **Signature:** `getProductItems() : Collection`
 359 | 
 360 | Returns a collection containing all items in the list that reference products.
 361 | 
 362 | ### getPublicItems
 363 | 
 364 | **Signature:** `getPublicItems() : Collection`
 365 | 
 366 | Returns a collection containing all items in the list that are flagged as public.
 367 | 
 368 | ### getPurchases
 369 | 
 370 | **Signature:** `getPurchases() : Collection`
 371 | 
 372 | Returns the aggregated purchases from all the individual items.
 373 | 
 374 | ### getRegistrant
 375 | 
 376 | **Signature:** `getRegistrant() : ProductListRegistrant`
 377 | 
 378 | Returns the ProductListRegistrant assigned to the registrant attribute or null if this list has no registrant.
 379 | 
 380 | ### getShippingAddress
 381 | 
 382 | **Signature:** `getShippingAddress() : CustomerAddress`
 383 | 
 384 | Return the address that should be used as the shipping address for purchases made from the list.
 385 | 
 386 | ### getType
 387 | 
 388 | **Signature:** `getType() : Number`
 389 | 
 390 | Returns an int representing the type of object (e.g.
 391 | 
 392 | ### isAnonymous
 393 | 
 394 | **Signature:** `isAnonymous() : boolean`
 395 | 
 396 | Returns true if this product list is owned by an anonymous customer.
 397 | 
 398 | ### isPublic
 399 | 
 400 | **Signature:** `isPublic() : boolean`
 401 | 
 402 | A flag, typically used to determine if the object is searchable by other customers.
 403 | 
 404 | ### removeCoRegistrant
 405 | 
 406 | **Signature:** `removeCoRegistrant() : void`
 407 | 
 408 | Removes the ProductListRegistrant assigned to the coRegistrant attribute.
 409 | 
 410 | ### removeItem
 411 | 
 412 | **Signature:** `removeItem(item : ProductListItem) : void`
 413 | 
 414 | Removes the specified item from the list.
 415 | 
 416 | ### removeRegistrant
 417 | 
 418 | **Signature:** `removeRegistrant() : void`
 419 | 
 420 | Removes the ProductListRegistrant assigned to the registrant attribute.
 421 | 
 422 | ### setDescription
 423 | 
 424 | **Signature:** `setDescription(description : String) : void`
 425 | 
 426 | Set the description of this product list.
 427 | 
 428 | ### setEventCity
 429 | 
 430 | **Signature:** `setEventCity(eventCity : String) : void`
 431 | 
 432 | Set the event city to which this product list is related.
 433 | 
 434 | ### setEventCountry
 435 | 
 436 | **Signature:** `setEventCountry(eventCountry : String) : void`
 437 | 
 438 | Set the event country to which this product list is related.
 439 | 
 440 | ### setEventDate
 441 | 
 442 | **Signature:** `setEventDate(eventDate : Date) : void`
 443 | 
 444 | Set the date of the event to which this product list is related.
 445 | 
 446 | ### setEventState
 447 | 
 448 | **Signature:** `setEventState(eventState : String) : void`
 449 | 
 450 | Set the event state to which this product list is related.
 451 | 
 452 | ### setEventType
 453 | 
 454 | **Signature:** `setEventType(eventType : String) : void`
 455 | 
 456 | Set the event type for which this product list was created by the owner.
 457 | 
 458 | ### setName
 459 | 
 460 | **Signature:** `setName(name : String) : void`
 461 | 
 462 | Set the name of this product list.
 463 | 
 464 | ### setPostEventShippingAddress
 465 | 
 466 | **Signature:** `setPostEventShippingAddress(address : CustomerAddress) : void`
 467 | 
 468 | This is typically used by an event related list (e.g.
 469 | 
 470 | ### setPublic
 471 | 
 472 | **Signature:** `setPublic(flag : boolean) : void`
 473 | 
 474 | Makes this product list visible to other customers or hides it.
 475 | 
 476 | ### setShippingAddress
 477 | 
 478 | **Signature:** `setShippingAddress(address : CustomerAddress) : void`
 479 | 
 480 | Associate an address, used as the shipping address for purchases made from the list.
 481 | 
 482 | ## Method Detail
 483 | 
 484 | ## Method Details
 485 | 
 486 | ### createCoRegistrant
 487 | 
 488 | **Signature:** `createCoRegistrant() : ProductListRegistrant`
 489 | 
 490 | **Description:** Create a ProductListRegistrant and assign it to the coRegistrant attribute of the list. An exception is thrown if the list already has a coRegistrant assigned to it.
 491 | 
 492 | **Returns:**
 493 | 
 494 | the created ProductListRegistrant instance.
 495 | 
 496 | **Throws:**
 497 | 
 498 | CreateException - if one already exists
 499 | 
 500 | ---
 501 | 
 502 | ### createGiftCertificateItem
 503 | 
 504 | **Signature:** `createGiftCertificateItem() : ProductListItem`
 505 | 
 506 | **Description:** Create an item in the list that represents a gift certificate. A list may only contain a single gift certificate, so an exception is thrown if one already exists in the list.
 507 | 
 508 | **Returns:**
 509 | 
 510 | the created item.
 511 | 
 512 | **Throws:**
 513 | 
 514 | CreateException - if a gift certificate item already exists in the list.
 515 | 
 516 | ---
 517 | 
 518 | ### createProductItem
 519 | 
 520 | **Signature:** `createProductItem(product : Product) : ProductListItem`
 521 | 
 522 | **Description:** Create an item in the list that references the specified product.
 523 | 
 524 | **Parameters:**
 525 | 
 526 | - `product`: the product to use to create the list item.
 527 | 
 528 | **Returns:**
 529 | 
 530 | the created item.
 531 | 
 532 | ---
 533 | 
 534 | ### createRegistrant
 535 | 
 536 | **Signature:** `createRegistrant() : ProductListRegistrant`
 537 | 
 538 | **Description:** Create a ProductListRegistrant and assign it to the registrant attribute of the list. An exception is thrown if the list already has a registrant assigned to it.
 539 | 
 540 | **Returns:**
 541 | 
 542 | the created ProductListRegistrant instance.
 543 | 
 544 | **Throws:**
 545 | 
 546 | CreateException - if one already exists
 547 | 
 548 | ---
 549 | 
 550 | ### getCoRegistrant
 551 | 
 552 | **Signature:** `getCoRegistrant() : ProductListRegistrant`
 553 | 
 554 | **Description:** Returns the ProductListRegistrant assigned to the coRegistrant attribute or null if this list has no co-registrant.
 555 | 
 556 | **Returns:**
 557 | 
 558 | the ProductListRegistrant assigned to the coRegistrant attribute or null if this list has no co-registrant.
 559 | 
 560 | ---
 561 | 
 562 | ### getCurrentShippingAddress
 563 | 
 564 | **Signature:** `getCurrentShippingAddress() : CustomerAddress`
 565 | 
 566 | **Description:** This is a helper method typically used with an event related list. It provides the appropriate shipping address based on the eventDate. If the current date is after the eventDate, then the postEventShippingAddress is returned, otherwise the shippingAddress is returned. If the eventDate is null, then null is returned.
 567 | 
 568 | **Returns:**
 569 | 
 570 | the appropriate address, as described above.
 571 | 
 572 | ---
 573 | 
 574 | ### getDescription
 575 | 
 576 | **Signature:** `getDescription() : String`
 577 | 
 578 | **Description:** Returns a description text that, for example, explains the purpose of this product list.
 579 | 
 580 | **Returns:**
 581 | 
 582 | a description text explaining the purpose of this product list. Returns an empty string if the description is not set.
 583 | 
 584 | ---
 585 | 
 586 | ### getEventCity
 587 | 
 588 | **Signature:** `getEventCity() : String`
 589 | 
 590 | **Description:** For event related uses (e.g. gift registry), this holds the event city.
 591 | 
 592 | **Returns:**
 593 | 
 594 | the event city. The event city or an empty string if no event city is set.
 595 | 
 596 | ---
 597 | 
 598 | ### getEventCountry
 599 | 
 600 | **Signature:** `getEventCountry() : String`
 601 | 
 602 | **Description:** For event related uses (e.g. gift registry), this holds the event country.
 603 | 
 604 | **Returns:**
 605 | 
 606 | the event country. The event country or an empty string if no event country is set.
 607 | 
 608 | ---
 609 | 
 610 | ### getEventDate
 611 | 
 612 | **Signature:** `getEventDate() : Date`
 613 | 
 614 | **Description:** For event related uses (e.g. gift registry), this holds the date of the event.
 615 | 
 616 | **Returns:**
 617 | 
 618 | the date of the event.
 619 | 
 620 | ---
 621 | 
 622 | ### getEventState
 623 | 
 624 | **Signature:** `getEventState() : String`
 625 | 
 626 | **Description:** For event related uses (e.g. gift registry), this holds the event state.
 627 | 
 628 | **Returns:**
 629 | 
 630 | the event state. The event state or an empty string if no event state is set.
 631 | 
 632 | ---
 633 | 
 634 | ### getEventType
 635 | 
 636 | **Signature:** `getEventType() : String`
 637 | 
 638 | **Description:** For event related uses (e.g. gift registry), this holds the type of event, e.g. Wedding, Baby Shower.
 639 | 
 640 | **Returns:**
 641 | 
 642 | the type of event. Returns an empty string, if not set.
 643 | 
 644 | ---
 645 | 
 646 | ### getExportStatus
 647 | 
 648 | **Signature:** `getExportStatus() : EnumValue`
 649 | 
 650 | **Description:** Returns the export status of the product list. Possible values are: EXPORT_STATUS_NOTEXPORTED, EXPORT_STATUS_EXPORTED.
 651 | 
 652 | **Returns:**
 653 | 
 654 | Product list export status
 655 | 
 656 | ---
 657 | 
 658 | ### getGiftCertificateItem
 659 | 
 660 | **Signature:** `getGiftCertificateItem() : ProductListItem`
 661 | 
 662 | **Description:** Returns the item in the list that represents a gift certificate.
 663 | 
 664 | **Returns:**
 665 | 
 666 | the gift certificate item, or null if it doesn't exist.
 667 | 
 668 | ---
 669 | 
 670 | ### getID
 671 | 
 672 | **Signature:** `getID() : String`
 673 | 
 674 | **Description:** Returns the unique system generated ID of the object.
 675 | 
 676 | **Returns:**
 677 | 
 678 | the ID of object.
 679 | 
 680 | ---
 681 | 
 682 | ### getItem
 683 | 
 684 | **Signature:** `getItem(ID : String) : ProductListItem`
 685 | 
 686 | **Description:** Returns the item from the list that has the specified ID.
 687 | 
 688 | **Parameters:**
 689 | 
 690 | - `ID`: the product list item identifier.
 691 | 
 692 | **Returns:**
 693 | 
 694 | the specified item, or null if it's not found in the list.
 695 | 
 696 | ---
 697 | 
 698 | ### getItems
 699 | 
 700 | **Signature:** `getItems() : Collection`
 701 | 
 702 | **Description:** Returns a collection containing all items in the list.
 703 | 
 704 | **Returns:**
 705 | 
 706 | all items.
 707 | 
 708 | ---
 709 | 
 710 | ### getLastExportTime
 711 | 
 712 | **Signature:** `getLastExportTime() : Date`
 713 | 
 714 | **Description:** Returns the date where this product list has been exported successfully the last time.
 715 | 
 716 | **Returns:**
 717 | 
 718 | The time of the last successful export or null if this product list was not exported yet.
 719 | 
 720 | ---
 721 | 
 722 | ### getName
 723 | 
 724 | **Signature:** `getName() : String`
 725 | 
 726 | **Description:** Returns the name of this product list given by its owner.
 727 | 
 728 | **Returns:**
 729 | 
 730 | the name of this product list. Returns an empty string if the name is not set.
 731 | 
 732 | ---
 733 | 
 734 | ### getOwner
 735 | 
 736 | **Signature:** `getOwner() : Customer`
 737 | 
 738 | **Description:** Returns the customer that created and owns the product list.
 739 | 
 740 | **Returns:**
 741 | 
 742 | Owning customer
 743 | 
 744 | ---
 745 | 
 746 | ### getPostEventShippingAddress
 747 | 
 748 | **Signature:** `getPostEventShippingAddress() : CustomerAddress`
 749 | 
 750 | **Description:** Returns the shipping address for purchases made after the event date.
 751 | 
 752 | **Returns:**
 753 | 
 754 | the shipping address for purchases made after the event date. Returns null if no post-event shipping address is associated.
 755 | 
 756 | ---
 757 | 
 758 | ### getProductItems
 759 | 
 760 | **Signature:** `getProductItems() : Collection`
 761 | 
 762 | **Description:** Returns a collection containing all items in the list that reference products.
 763 | 
 764 | **Returns:**
 765 | 
 766 | all product items.
 767 | 
 768 | ---
 769 | 
 770 | ### getPublicItems
 771 | 
 772 | **Signature:** `getPublicItems() : Collection`
 773 | 
 774 | **Description:** Returns a collection containing all items in the list that are flagged as public.
 775 | 
 776 | **Returns:**
 777 | 
 778 | all public items.
 779 | 
 780 | ---
 781 | 
 782 | ### getPurchases
 783 | 
 784 | **Signature:** `getPurchases() : Collection`
 785 | 
 786 | **Description:** Returns the aggregated purchases from all the individual items.
 787 | 
 788 | **Returns:**
 789 | 
 790 | purchases
 791 | 
 792 | ---
 793 | 
 794 | ### getRegistrant
 795 | 
 796 | **Signature:** `getRegistrant() : ProductListRegistrant`
 797 | 
 798 | **Description:** Returns the ProductListRegistrant assigned to the registrant attribute or null if this list has no registrant.
 799 | 
 800 | **Returns:**
 801 | 
 802 | the ProductListRegistrant assigned to the registrant attribute or null if this list has no registrant.
 803 | 
 804 | ---
 805 | 
 806 | ### getShippingAddress
 807 | 
 808 | **Signature:** `getShippingAddress() : CustomerAddress`
 809 | 
 810 | **Description:** Return the address that should be used as the shipping address for purchases made from the list.
 811 | 
 812 | **Returns:**
 813 | 
 814 | the shipping address. The shipping address of this list or null if no address is associated.
 815 | 
 816 | ---
 817 | 
 818 | ### getType
 819 | 
 820 | **Signature:** `getType() : Number`
 821 | 
 822 | **Description:** Returns an int representing the type of object (e.g. wish list, gift registry). This is set at object creation time.
 823 | 
 824 | **Returns:**
 825 | 
 826 | the type of object.
 827 | 
 828 | ---
 829 | 
 830 | ### isAnonymous
 831 | 
 832 | **Signature:** `isAnonymous() : boolean`
 833 | 
 834 | **Description:** Returns true if this product list is owned by an anonymous customer.
 835 | 
 836 | **Returns:**
 837 | 
 838 | true if the owner of this product list is anonymous, false otherwise.
 839 | 
 840 | ---
 841 | 
 842 | ### isPublic
 843 | 
 844 | **Signature:** `isPublic() : boolean`
 845 | 
 846 | **Description:** A flag, typically used to determine if the object is searchable by other customers.
 847 | 
 848 | **Returns:**
 849 | 
 850 | true if the product list is public. False otherwise.
 851 | 
 852 | ---
 853 | 
 854 | ### removeCoRegistrant
 855 | 
 856 | **Signature:** `removeCoRegistrant() : void`
 857 | 
 858 | **Description:** Removes the ProductListRegistrant assigned to the coRegistrant attribute.
 859 | 
 860 | ---
 861 | 
 862 | ### removeItem
 863 | 
 864 | **Signature:** `removeItem(item : ProductListItem) : void`
 865 | 
 866 | **Description:** Removes the specified item from the list. This will also cause all purchase information associated with that item to be removed.
 867 | 
 868 | **Parameters:**
 869 | 
 870 | - `item`: The item to remove.
 871 | 
 872 | ---
 873 | 
 874 | ### removeRegistrant
 875 | 
 876 | **Signature:** `removeRegistrant() : void`
 877 | 
 878 | **Description:** Removes the ProductListRegistrant assigned to the registrant attribute.
 879 | 
 880 | ---
 881 | 
 882 | ### setDescription
 883 | 
 884 | **Signature:** `setDescription(description : String) : void`
 885 | 
 886 | **Description:** Set the description of this product list.
 887 | 
 888 | **Parameters:**
 889 | 
 890 | - `description`: The description of this product list. The description can have up to 256 characters, longer descriptions get truncated. If an empty string is provided, the description gets set to null.
 891 | 
 892 | ---
 893 | 
 894 | ### setEventCity
 895 | 
 896 | **Signature:** `setEventCity(eventCity : String) : void`
 897 | 
 898 | **Description:** Set the event city to which this product list is related.
 899 | 
 900 | **Parameters:**
 901 | 
 902 | - `eventCity`: The event city can have up to 256 characters, longer event city get truncated. If an empty string is provided, the event city gets set to null.
 903 | 
 904 | ---
 905 | 
 906 | ### setEventCountry
 907 | 
 908 | **Signature:** `setEventCountry(eventCountry : String) : void`
 909 | 
 910 | **Description:** Set the event country to which this product list is related.
 911 | 
 912 | **Parameters:**
 913 | 
 914 | - `eventCountry`: The event country can have up to 256 characters, longer event country get truncated. If an empty string is provided, the event country gets set to null.
 915 | 
 916 | ---
 917 | 
 918 | ### setEventDate
 919 | 
 920 | **Signature:** `setEventDate(eventDate : Date) : void`
 921 | 
 922 | **Description:** Set the date of the event to which this product list is related.
 923 | 
 924 | **Parameters:**
 925 | 
 926 | - `eventDate`: The event date or null if no event date should be available.
 927 | 
 928 | ---
 929 | 
 930 | ### setEventState
 931 | 
 932 | **Signature:** `setEventState(eventState : String) : void`
 933 | 
 934 | **Description:** Set the event state to which this product list is related.
 935 | 
 936 | **Parameters:**
 937 | 
 938 | - `eventState`: The event state can have up to 256 characters, longer event state get truncated. If an empty string is provided, the event state gets set to null.
 939 | 
 940 | ---
 941 | 
 942 | ### setEventType
 943 | 
 944 | **Signature:** `setEventType(eventType : String) : void`
 945 | 
 946 | **Description:** Set the event type for which this product list was created by the owner.
 947 | 
 948 | **Parameters:**
 949 | 
 950 | - `eventType`: The event type can have up to 256 characters, longer event type get truncated. If an empty string is provided, the event type gets set to null.
 951 | 
 952 | ---
 953 | 
 954 | ### setName
 955 | 
 956 | **Signature:** `setName(name : String) : void`
 957 | 
 958 | **Description:** Set the name of this product list.
 959 | 
 960 | **Parameters:**
 961 | 
 962 | - `name`: The name of this product list. The name can have up to 256 characters, longer names get truncated. If an empty string is provided, the name gets set to null.
 963 | 
 964 | ---
 965 | 
 966 | ### setPostEventShippingAddress
 967 | 
 968 | **Signature:** `setPostEventShippingAddress(address : CustomerAddress) : void`
 969 | 
 970 | **Description:** This is typically used by an event related list (e.g. gift registry) to specify a shipping address for purchases made after the event date.
 971 | 
 972 | **Parameters:**
 973 | 
 974 | - `address`: The shipping address.
 975 | 
 976 | ---
 977 | 
 978 | ### setPublic
 979 | 
 980 | **Signature:** `setPublic(flag : boolean) : void`
 981 | 
 982 | **Description:** Makes this product list visible to other customers or hides it.
 983 | 
 984 | **Parameters:**
 985 | 
 986 | - `flag`: If true, this product list becomes visible to other customers. If false, this product list can only be seen and searched by its owner.
 987 | 
 988 | ---
 989 | 
 990 | ### setShippingAddress
 991 | 
 992 | **Signature:** `setShippingAddress(address : CustomerAddress) : void`
 993 | 
 994 | **Description:** Associate an address, used as the shipping address for purchases made from the list.
 995 | 
 996 | **Parameters:**
 997 | 
 998 | - `address`: The shipping address.
 999 | 
1000 | ---
```

--------------------------------------------------------------------------------
/docs/TopLevel/Date.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Package: TopLevel
  2 | 
  3 | # Class Date
  4 | 
  5 | ## Inheritance Hierarchy
  6 | 
  7 | - Object
  8 |   - Date
  9 | 
 10 | ## Description
 11 | 
 12 | A Date object contains a number indicating a particular instant in time to within a millisecond. The number may also be NaN, indicating that the Date object does not represent a specific instant of time.
 13 | 
 14 | ## Constructor Summary
 15 | 
 16 | Date() Constructs the Date instance using the current date and time.
 17 | 
 18 | Date(millis : Number) Constructs the Date instance using the specified milliseconds.
 19 | 
 20 | Date(year : Number, month : Number, args : Number...) Constructs the Date instance using the specified year and month.
 21 | 
 22 | Date(dateString : String) Constructs the Date instance by parsing the specified String.
 23 | 
 24 | ## Method Summary
 25 | 
 26 | ### getDate
 27 | 
 28 | **Signature:** `getDate() : Number`
 29 | 
 30 | Returns the day of the month where the value is a Number from 1 to 31.
 31 | 
 32 | ### getDay
 33 | 
 34 | **Signature:** `getDay() : Number`
 35 | 
 36 | Returns the day of the week where the value is a Number from 0 to 6.
 37 | 
 38 | ### getFullYear
 39 | 
 40 | **Signature:** `getFullYear() : Number`
 41 | 
 42 | Returns the year of the Date in four-digit format.
 43 | 
 44 | ### getHours
 45 | 
 46 | **Signature:** `getHours() : Number`
 47 | 
 48 | Return the hours field of the Date where the value is a Number from 0 (midnight) to 23 (11 PM).
 49 | 
 50 | ### getMilliseconds
 51 | 
 52 | **Signature:** `getMilliseconds() : Number`
 53 | 
 54 | Returns the milliseconds field of the Date.
 55 | 
 56 | ### getMinutes
 57 | 
 58 | **Signature:** `getMinutes() : Number`
 59 | 
 60 | Return the minutes field of the Date where the value is a Number from 0 to 59.
 61 | 
 62 | ### getMonth
 63 | 
 64 | **Signature:** `getMonth() : Number`
 65 | 
 66 | Returns the month of the year as a value between 0 and 11.
 67 | 
 68 | ### getSeconds
 69 | 
 70 | **Signature:** `getSeconds() : Number`
 71 | 
 72 | Return the seconds field of the Date where the value is a Number from 0 to 59.
 73 | 
 74 | ### getTime
 75 | 
 76 | **Signature:** `getTime() : Number`
 77 | 
 78 | Returns the internal, millisecond representation of the Date object.
 79 | 
 80 | ### getTimezoneOffset
 81 | 
 82 | **Signature:** `getTimezoneOffset() : Number`
 83 | 
 84 | Returns the difference between local time and Greenwich Mean Time (GMT) in minutes.
 85 | 
 86 | ### getUTCDate
 87 | 
 88 | **Signature:** `getUTCDate() : Number`
 89 | 
 90 | Returns the day of the month where the value is a Number from 1 to 31 when date is expressed in universal time.
 91 | 
 92 | ### getUTCDay
 93 | 
 94 | **Signature:** `getUTCDay() : Number`
 95 | 
 96 | Returns the day of the week where the value is a Number from 0 to 6 when date is expressed in universal time.
 97 | 
 98 | ### getUTCFullYear
 99 | 
100 | **Signature:** `getUTCFullYear() : Number`
101 | 
102 | Returns the year when the Date is expressed in universal time.
103 | 
104 | ### getUTCHours
105 | 
106 | **Signature:** `getUTCHours() : Number`
107 | 
108 | Return the hours field, expressed in universal time, of the Date where the value is a Number from 0 (midnight) to 23 (11 PM).
109 | 
110 | ### getUTCMilliseconds
111 | 
112 | **Signature:** `getUTCMilliseconds() : Number`
113 | 
114 | Returns the milliseconds field, expressed in universal time, of the Date.
115 | 
116 | ### getUTCMinutes
117 | 
118 | **Signature:** `getUTCMinutes() : Number`
119 | 
120 | Return the minutes field, expressed in universal time, of the Date where the value is a Number from 0 to 59.
121 | 
122 | ### getUTCMonth
123 | 
124 | **Signature:** `getUTCMonth() : Number`
125 | 
126 | Returns the month of the year that results when the Date is expressed in universal time.
127 | 
128 | ### getUTCSeconds
129 | 
130 | **Signature:** `getUTCSeconds() : Number`
131 | 
132 | Return the seconds field, expressed in universal time, of the Date where the value is a Number from 0 to 59.
133 | 
134 | ### now
135 | 
136 | **Signature:** `static now() : Number`
137 | 
138 | Returns the number of milliseconds since midnight of January 1, 1970 up until now.
139 | 
140 | ### parse
141 | 
142 | **Signature:** `static parse(dateString : String) : Number`
143 | 
144 | Takes a date string and returns the number of milliseconds since midnight of January 1, 1970.
145 | 
146 | ### setDate
147 | 
148 | **Signature:** `setDate(date : Number) : Number`
149 | 
150 | Sets the day of the month where the value is a Number from 1 to 31.
151 | 
152 | ### setFullYear
153 | 
154 | **Signature:** `setFullYear(year : Number, args : Number...) : Number`
155 | 
156 | Sets the full year of Date where the value must be a four-digit Number.
157 | 
158 | ### setHours
159 | 
160 | **Signature:** `setHours(hours : Number, args : Number...) : Number`
161 | 
162 | Sets the hours field of this Date instance.
163 | 
164 | ### setMilliseconds
165 | 
166 | **Signature:** `setMilliseconds(millis : Number) : Number`
167 | 
168 | Sets the milliseconds field of this Date instance.
169 | 
170 | ### setMinutes
171 | 
172 | **Signature:** `setMinutes(minutes : Number, args : Number...) : Number`
173 | 
174 | Sets the minutes field of this Date instance.
175 | 
176 | ### setMonth
177 | 
178 | **Signature:** `setMonth(month : Number, date : Number...) : Number`
179 | 
180 | Sets the month of the year where the value is a Number from 0 to 11.
181 | 
182 | ### setSeconds
183 | 
184 | **Signature:** `setSeconds(seconds : Number, millis : Number...) : Number`
185 | 
186 | Sets the seconds field of this Date instance.
187 | 
188 | ### setTime
189 | 
190 | **Signature:** `setTime(millis : Number) : Number`
191 | 
192 | Sets the number of milliseconds between the desired date and time and January 1, 1970.
193 | 
194 | ### setUTCDate
195 | 
196 | **Signature:** `setUTCDate(date : Number) : Number`
197 | 
198 | Sets the day of the month, expressed in universal time, where the value is a Number from 1 to 31.
199 | 
200 | ### setUTCFullYear
201 | 
202 | **Signature:** `setUTCFullYear(year : Number, args : Number...) : Number`
203 | 
204 | Sets the full year, expressed in universal time, of Date where the value must be a four-digit Number.
205 | 
206 | ### setUTCHours
207 | 
208 | **Signature:** `setUTCHours(hours : Number, args : Number...) : Number`
209 | 
210 | Sets the hours field, expressed in universal time, of this Date instance.
211 | 
212 | ### setUTCMilliseconds
213 | 
214 | **Signature:** `setUTCMilliseconds(millis : Number) : Number`
215 | 
216 | Sets the milliseconds field, expressed in universal time, of this Date instance.
217 | 
218 | ### setUTCMinutes
219 | 
220 | **Signature:** `setUTCMinutes(minutes : Number, args : Number...) : Number`
221 | 
222 | Sets the minutes field, expressed in universal time, of this Date instance.
223 | 
224 | ### setUTCMonth
225 | 
226 | **Signature:** `setUTCMonth(month : Number, date : Number...) : Number`
227 | 
228 | Sets the month of the year, expressed in universal time, where the value is a Number from 0 to 11.
229 | 
230 | ### setUTCSeconds
231 | 
232 | **Signature:** `setUTCSeconds(seconds : Number, millis : Number...) : Number`
233 | 
234 | Sets the seconds field, expressed in universal time, of this Date instance.
235 | 
236 | ### toDateString
237 | 
238 | **Signature:** `toDateString() : String`
239 | 
240 | Returns the Date as a String value where the value represents the date portion of the Date in the default locale (en_US).
241 | 
242 | ### toISOString
243 | 
244 | **Signature:** `toISOString() : String`
245 | 
246 | This function returns a string value represent the instance in time represented by this Date object.
247 | 
248 | ### toJSON
249 | 
250 | **Signature:** `toJSON(key : String) : Object`
251 | 
252 | This function returns the same string as Date.prototype.toISOString().
253 | 
254 | ### toLocaleDateString
255 | 
256 | **Signature:** `toLocaleDateString() : String`
257 | 
258 | Returns the Date as a String value where the value represents the date portion of the Date in the default locale (en_US).
259 | 
260 | ### toLocaleString
261 | 
262 | **Signature:** `toLocaleString() : String`
263 | 
264 | Returns the Date as a String using the default locale (en_US).
265 | 
266 | ### toLocaleTimeString
267 | 
268 | **Signature:** `toLocaleTimeString() : String`
269 | 
270 | Returns the Date as a String value where the value represents the time portion of the Date in the default locale (en_US).
271 | 
272 | ### toTimeString
273 | 
274 | **Signature:** `toTimeString() : String`
275 | 
276 | Returns the Date as a String value where the value represents the time portion of the Date in the default locale (en_US).
277 | 
278 | ### toUTCString
279 | 
280 | **Signature:** `toUTCString() : String`
281 | 
282 | Returns a String representation of this Date, expressed in universal time.
283 | 
284 | ### UTC
285 | 
286 | **Signature:** `static UTC(year : Number, month : Number, args : Number...) : Number`
287 | 
288 | Returns the number of milliseconds since midnight of January 1, 1970 according to universal time.
289 | 
290 | ### valueOf
291 | 
292 | **Signature:** `valueOf() : Object`
293 | 
294 | Returns the value of this Date represented in milliseconds.
295 | 
296 | ## Constructor Detail
297 | 
298 | ## Method Detail
299 | 
300 | ## Method Details
301 | 
302 | ### getDate
303 | 
304 | **Signature:** `getDate() : Number`
305 | 
306 | **Description:** Returns the day of the month where the value is a Number from 1 to 31.
307 | 
308 | **Returns:**
309 | 
310 | the day of the month where the value is a Number from 1 to 31.
311 | 
312 | ---
313 | 
314 | ### getDay
315 | 
316 | **Signature:** `getDay() : Number`
317 | 
318 | **Description:** Returns the day of the week where the value is a Number from 0 to 6.
319 | 
320 | **Returns:**
321 | 
322 | the day of the month where the value is a Number from 0 to 6.
323 | 
324 | ---
325 | 
326 | ### getFullYear
327 | 
328 | **Signature:** `getFullYear() : Number`
329 | 
330 | **Description:** Returns the year of the Date in four-digit format.
331 | 
332 | **Returns:**
333 | 
334 | the year of the Date in four-digit format.
335 | 
336 | ---
337 | 
338 | ### getHours
339 | 
340 | **Signature:** `getHours() : Number`
341 | 
342 | **Description:** Return the hours field of the Date where the value is a Number from 0 (midnight) to 23 (11 PM).
343 | 
344 | **Returns:**
345 | 
346 | the hours field of the Date where the value is a Number from 0 (midnight) to 23 (11 PM).
347 | 
348 | ---
349 | 
350 | ### getMilliseconds
351 | 
352 | **Signature:** `getMilliseconds() : Number`
353 | 
354 | **Description:** Returns the milliseconds field of the Date.
355 | 
356 | **Returns:**
357 | 
358 | the milliseconds field of the Date.
359 | 
360 | ---
361 | 
362 | ### getMinutes
363 | 
364 | **Signature:** `getMinutes() : Number`
365 | 
366 | **Description:** Return the minutes field of the Date where the value is a Number from 0 to 59.
367 | 
368 | **Returns:**
369 | 
370 | the minutes field of the Date where the value is a Number from 0 to 59.
371 | 
372 | ---
373 | 
374 | ### getMonth
375 | 
376 | **Signature:** `getMonth() : Number`
377 | 
378 | **Description:** Returns the month of the year as a value between 0 and 11.
379 | 
380 | **Returns:**
381 | 
382 | the month of the year as a value between 0 and 11.
383 | 
384 | ---
385 | 
386 | ### getSeconds
387 | 
388 | **Signature:** `getSeconds() : Number`
389 | 
390 | **Description:** Return the seconds field of the Date where the value is a Number from 0 to 59.
391 | 
392 | **Returns:**
393 | 
394 | the seconds field of the Date where the value is a Number from 0 to 59.
395 | 
396 | ---
397 | 
398 | ### getTime
399 | 
400 | **Signature:** `getTime() : Number`
401 | 
402 | **Description:** Returns the internal, millisecond representation of the Date object. This value is independent of time zone.
403 | 
404 | **Returns:**
405 | 
406 | the internal, millisecond representation of the Date object.
407 | 
408 | ---
409 | 
410 | ### getTimezoneOffset
411 | 
412 | **Signature:** `getTimezoneOffset() : Number`
413 | 
414 | **Description:** Returns the difference between local time and Greenwich Mean Time (GMT) in minutes.
415 | 
416 | **Returns:**
417 | 
418 | the difference between local time and Greenwich Mean Time (GMT) in minutes.
419 | 
420 | ---
421 | 
422 | ### getUTCDate
423 | 
424 | **Signature:** `getUTCDate() : Number`
425 | 
426 | **Description:** Returns the day of the month where the value is a Number from 1 to 31 when date is expressed in universal time.
427 | 
428 | **Returns:**
429 | 
430 | the day of the month where the value is a Number from 1 to 31 when date is expressed in universal time.
431 | 
432 | ---
433 | 
434 | ### getUTCDay
435 | 
436 | **Signature:** `getUTCDay() : Number`
437 | 
438 | **Description:** Returns the day of the week where the value is a Number from 0 to 6 when date is expressed in universal time.
439 | 
440 | **Returns:**
441 | 
442 | the day of the week where the value is a Number from 0 to 6 when date is expressed in universal time.
443 | 
444 | ---
445 | 
446 | ### getUTCFullYear
447 | 
448 | **Signature:** `getUTCFullYear() : Number`
449 | 
450 | **Description:** Returns the year when the Date is expressed in universal time. The return value is a four-digit format.
451 | 
452 | **Returns:**
453 | 
454 | the year of the Date in four-digit form.
455 | 
456 | ---
457 | 
458 | ### getUTCHours
459 | 
460 | **Signature:** `getUTCHours() : Number`
461 | 
462 | **Description:** Return the hours field, expressed in universal time, of the Date where the value is a Number from 0 (midnight) to 23 (11 PM).
463 | 
464 | **Returns:**
465 | 
466 | the hours field, expressed in universal time, of the Date where the value is a Number from 0 (midnight) to 23 (11 PM).
467 | 
468 | ---
469 | 
470 | ### getUTCMilliseconds
471 | 
472 | **Signature:** `getUTCMilliseconds() : Number`
473 | 
474 | **Description:** Returns the milliseconds field, expressed in universal time, of the Date.
475 | 
476 | **Returns:**
477 | 
478 | the milliseconds field, expressed in universal time, of the Date.
479 | 
480 | ---
481 | 
482 | ### getUTCMinutes
483 | 
484 | **Signature:** `getUTCMinutes() : Number`
485 | 
486 | **Description:** Return the minutes field, expressed in universal time, of the Date where the value is a Number from 0 to 59.
487 | 
488 | **Returns:**
489 | 
490 | the minutes field, expressed in universal time, of the Date where the value is a Number from 0 to 59.
491 | 
492 | ---
493 | 
494 | ### getUTCMonth
495 | 
496 | **Signature:** `getUTCMonth() : Number`
497 | 
498 | **Description:** Returns the month of the year that results when the Date is expressed in universal time. The return value is a Number betwee 0 and 11.
499 | 
500 | **Returns:**
501 | 
502 | the month of the year as a value between 0 and 11.
503 | 
504 | ---
505 | 
506 | ### getUTCSeconds
507 | 
508 | **Signature:** `getUTCSeconds() : Number`
509 | 
510 | **Description:** Return the seconds field, expressed in universal time, of the Date where the value is a Number from 0 to 59.
511 | 
512 | **Returns:**
513 | 
514 | the seconds field, expressed in universal time, of the Date where the value is a Number from 0 to 59.
515 | 
516 | ---
517 | 
518 | ### now
519 | 
520 | **Signature:** `static now() : Number`
521 | 
522 | **Description:** Returns the number of milliseconds since midnight of January 1, 1970 up until now.
523 | 
524 | **Returns:**
525 | 
526 | the number of milliseconds since midnight of January 1, 1970.
527 | 
528 | ---
529 | 
530 | ### parse
531 | 
532 | **Signature:** `static parse(dateString : String) : Number`
533 | 
534 | **Description:** Takes a date string and returns the number of milliseconds since midnight of January 1, 1970. Supports: RFC2822 date strings strings matching the exact ISO 8601 format 'YYYY-MM-DDTHH:mm:ss.sssZ'
535 | 
536 | **Parameters:**
537 | 
538 | - `dateString`: represents a Date in a valid date format.
539 | 
540 | **Returns:**
541 | 
542 | the number of milliseconds since midnight of January 1, 1970 or NaN if no date could be recognized.
543 | 
544 | ---
545 | 
546 | ### setDate
547 | 
548 | **Signature:** `setDate(date : Number) : Number`
549 | 
550 | **Description:** Sets the day of the month where the value is a Number from 1 to 31.
551 | 
552 | **Parameters:**
553 | 
554 | - `date`: the day of the month.
555 | 
556 | **Returns:**
557 | 
558 | the millisecond representation of the adjusted date.
559 | 
560 | ---
561 | 
562 | ### setFullYear
563 | 
564 | **Signature:** `setFullYear(year : Number, args : Number...) : Number`
565 | 
566 | **Description:** Sets the full year of Date where the value must be a four-digit Number. Optionally, you can set the month and date.
567 | 
568 | **Parameters:**
569 | 
570 | - `year`: the year as a four-digit Number.
571 | - `args`: the month and day of the month.
572 | 
573 | **Returns:**
574 | 
575 | the millisecond representation of the adjusted date.
576 | 
577 | ---
578 | 
579 | ### setHours
580 | 
581 | **Signature:** `setHours(hours : Number, args : Number...) : Number`
582 | 
583 | **Description:** Sets the hours field of this Date instance. The minutes value should be a Number from 0 to 23. Optionally, hours, seconds and milliseconds can also be provided.
584 | 
585 | **Parameters:**
586 | 
587 | - `hours`: the minutes field of this Date instance.
588 | - `args`: the hours, seconds and milliseconds values for this Date instance.
589 | 
590 | **Returns:**
591 | 
592 | the millisecond representation of the adjusted date.
593 | 
594 | ---
595 | 
596 | ### setMilliseconds
597 | 
598 | **Signature:** `setMilliseconds(millis : Number) : Number`
599 | 
600 | **Description:** Sets the milliseconds field of this Date instance.
601 | 
602 | **Parameters:**
603 | 
604 | - `millis`: the milliseconds field of this Date instance.
605 | 
606 | **Returns:**
607 | 
608 | the millisecond representation of the adjusted date.
609 | 
610 | ---
611 | 
612 | ### setMinutes
613 | 
614 | **Signature:** `setMinutes(minutes : Number, args : Number...) : Number`
615 | 
616 | **Description:** Sets the minutes field of this Date instance. The minutes value should be a Number from 0 to 59. Optionally, seconds and milliseconds can also be provided.
617 | 
618 | **Parameters:**
619 | 
620 | - `minutes`: the minutes field of this Date instance.
621 | - `args`: the seconds and milliseconds value for this Date instance.
622 | 
623 | **Returns:**
624 | 
625 | the millisecond representation of the adjusted date.
626 | 
627 | ---
628 | 
629 | ### setMonth
630 | 
631 | **Signature:** `setMonth(month : Number, date : Number...) : Number`
632 | 
633 | **Description:** Sets the month of the year where the value is a Number from 0 to 11. Optionally, you can set the day of the month.
634 | 
635 | **Parameters:**
636 | 
637 | - `month`: the month of the year.
638 | - `date`: the day of the month.
639 | 
640 | **Returns:**
641 | 
642 | the millisecond representation of the adjusted date.
643 | 
644 | ---
645 | 
646 | ### setSeconds
647 | 
648 | **Signature:** `setSeconds(seconds : Number, millis : Number...) : Number`
649 | 
650 | **Description:** Sets the seconds field of this Date instance. The seconds value should be a Number from 0 to 59. Optionally, milliseconds can also be provided.
651 | 
652 | **Parameters:**
653 | 
654 | - `seconds`: the seconds field of this Date instance.
655 | - `millis`: the milliseconds field of this Date instance.
656 | 
657 | **Returns:**
658 | 
659 | the millisecond representation of the adjusted date.
660 | 
661 | ---
662 | 
663 | ### setTime
664 | 
665 | **Signature:** `setTime(millis : Number) : Number`
666 | 
667 | **Description:** Sets the number of milliseconds between the desired date and time and January 1, 1970.
668 | 
669 | **Parameters:**
670 | 
671 | - `millis`: the number of milliseconds between the desired date and time and January 1, 1970.
672 | 
673 | **Returns:**
674 | 
675 | the millisecond representation of the adjusted date.
676 | 
677 | ---
678 | 
679 | ### setUTCDate
680 | 
681 | **Signature:** `setUTCDate(date : Number) : Number`
682 | 
683 | **Description:** Sets the day of the month, expressed in universal time, where the value is a Number from 1 to 31.
684 | 
685 | **Parameters:**
686 | 
687 | - `date`: the day of the month, expressed in universal time.
688 | 
689 | **Returns:**
690 | 
691 | the millisecond representation of the adjusted date.
692 | 
693 | ---
694 | 
695 | ### setUTCFullYear
696 | 
697 | **Signature:** `setUTCFullYear(year : Number, args : Number...) : Number`
698 | 
699 | **Description:** Sets the full year, expressed in universal time, of Date where the value must be a four-digit Number. Optionally, you can set the month and date.
700 | 
701 | **Parameters:**
702 | 
703 | - `year`: the year as a four-digit Number, expressed in universal time.
704 | - `args`: the month and day of the month.
705 | 
706 | **Returns:**
707 | 
708 | the millisecond representation of the adjusted date.
709 | 
710 | ---
711 | 
712 | ### setUTCHours
713 | 
714 | **Signature:** `setUTCHours(hours : Number, args : Number...) : Number`
715 | 
716 | **Description:** Sets the hours field, expressed in universal time, of this Date instance. The minutes value should be a Number from 0 to 23. Optionally, seconds and milliseconds can also be provided.
717 | 
718 | **Parameters:**
719 | 
720 | - `hours`: the minutes field, expressed in universal time, of this Date instance.
721 | - `args`: the seconds and milliseconds value, expressed in universal time, for this Date instance.
722 | 
723 | **Returns:**
724 | 
725 | the millisecond representation of the adjusted date.
726 | 
727 | ---
728 | 
729 | ### setUTCMilliseconds
730 | 
731 | **Signature:** `setUTCMilliseconds(millis : Number) : Number`
732 | 
733 | **Description:** Sets the milliseconds field, expressed in universal time, of this Date instance.
734 | 
735 | **Parameters:**
736 | 
737 | - `millis`: the milliseconds field, expressed in universal time, of this Date instance.
738 | 
739 | **Returns:**
740 | 
741 | the millisecond representation of the adjusted date.
742 | 
743 | ---
744 | 
745 | ### setUTCMinutes
746 | 
747 | **Signature:** `setUTCMinutes(minutes : Number, args : Number...) : Number`
748 | 
749 | **Description:** Sets the minutes field, expressed in universal time, of this Date instance. The minutes value should be a Number from 0 to 59. Optionally, seconds and milliseconds can also be provided.
750 | 
751 | **Parameters:**
752 | 
753 | - `minutes`: the minutes field, expressed in universal time, of this Date instance.
754 | - `args`: the seconds and milliseconds values, expressed in universal time, for this Date instance.
755 | 
756 | **Returns:**
757 | 
758 | the millisecond representation of the adjusted date.
759 | 
760 | ---
761 | 
762 | ### setUTCMonth
763 | 
764 | **Signature:** `setUTCMonth(month : Number, date : Number...) : Number`
765 | 
766 | **Description:** Sets the month of the year, expressed in universal time, where the value is a Number from 0 to 11. Optionally, you can set the day of the month.
767 | 
768 | **Parameters:**
769 | 
770 | - `month`: the month of the year, expressed in universal time.
771 | - `date`: the day of the month.
772 | 
773 | **Returns:**
774 | 
775 | the millisecond representation of the adjusted date.
776 | 
777 | ---
778 | 
779 | ### setUTCSeconds
780 | 
781 | **Signature:** `setUTCSeconds(seconds : Number, millis : Number...) : Number`
782 | 
783 | **Description:** Sets the seconds field, expressed in universal time, of this Date instance. The seconds value should be a Number from 0 to 59. Optionally, milliseconds can also be provided.
784 | 
785 | **Parameters:**
786 | 
787 | - `seconds`: the seconds field, expressed in universal time, of this Date instance.
788 | - `millis`: the milliseconds field, expressed in universal time, of this Date instance.
789 | 
790 | **Returns:**
791 | 
792 | the millisecond representation of the adjusted date.
793 | 
794 | ---
795 | 
796 | ### toDateString
797 | 
798 | **Signature:** `toDateString() : String`
799 | 
800 | **Description:** Returns the Date as a String value where the value represents the date portion of the Date in the default locale (en_US). To format a calendar object in an alternate format use the dw.util.StringUtils.formatCalendar() functions instead.
801 | 
802 | **Returns:**
803 | 
804 | the Date as a String value.
805 | 
806 | ---
807 | 
808 | ### toISOString
809 | 
810 | **Signature:** `toISOString() : String`
811 | 
812 | **Description:** This function returns a string value represent the instance in time represented by this Date object. The date is formatted with the Simplified ISO 8601 format as follows: YYYY-MM-DDTHH:mm:ss.sssTZ. The time zone is always UTC, denoted by the suffix Z.
813 | 
814 | **Returns:**
815 | 
816 | string representation of this date
817 | 
818 | ---
819 | 
820 | ### toJSON
821 | 
822 | **Signature:** `toJSON(key : String) : Object`
823 | 
824 | **Description:** This function returns the same string as Date.prototype.toISOString(). The method is called when a Date object is stringified.
825 | 
826 | **Parameters:**
827 | 
828 | - `key`: the name of the key, which is stringified
829 | 
830 | **Returns:**
831 | 
832 | JSON string representation of this date
833 | 
834 | ---
835 | 
836 | ### toLocaleDateString
837 | 
838 | **Signature:** `toLocaleDateString() : String`
839 | 
840 | **Description:** Returns the Date as a String value where the value represents the date portion of the Date in the default locale (en_US). To format a calendar object in an alternate format use the dw.util.StringUtils.formatCalendar() functions instead.
841 | 
842 | **Returns:**
843 | 
844 | returns the date portion of the Date as a String.
845 | 
846 | ---
847 | 
848 | ### toLocaleString
849 | 
850 | **Signature:** `toLocaleString() : String`
851 | 
852 | **Description:** Returns the Date as a String using the default locale (en_US). To format a calendar object in an alternate format use the dw.util.StringUtils.formatCalendar() functions instead.
853 | 
854 | **Returns:**
855 | 
856 | the Date as a String using the default locale en_US
857 | 
858 | ---
859 | 
860 | ### toLocaleTimeString
861 | 
862 | **Signature:** `toLocaleTimeString() : String`
863 | 
864 | **Description:** Returns the Date as a String value where the value represents the time portion of the Date in the default locale (en_US). To format a calendar object in an alternate format use the dw.util.StringUtils.formatCalendar() functions instead.
865 | 
866 | **Returns:**
867 | 
868 | returns the time time's portion of the Date as a String.
869 | 
870 | ---
871 | 
872 | ### toTimeString
873 | 
874 | **Signature:** `toTimeString() : String`
875 | 
876 | **Description:** Returns the Date as a String value where the value represents the time portion of the Date in the default locale (en_US). To format a calendar object in an alternate format use the dw.util.StringUtils.formatCalendar() functions instead.
877 | 
878 | **Returns:**
879 | 
880 | the Date's time.
881 | 
882 | ---
883 | 
884 | ### toUTCString
885 | 
886 | **Signature:** `toUTCString() : String`
887 | 
888 | **Description:** Returns a String representation of this Date, expressed in universal time.
889 | 
890 | **Returns:**
891 | 
892 | a String representation of this Date, expressed in universal time.
893 | 
894 | ---
895 | 
896 | ### UTC
897 | 
898 | **Signature:** `static UTC(year : Number, month : Number, args : Number...) : Number`
899 | 
900 | **Description:** Returns the number of milliseconds since midnight of January 1, 1970 according to universal time. Optionally, you can pass up to five additional arguments representing date, hours, minutes, seconds, and milliseconds.
901 | 
902 | **Parameters:**
903 | 
904 | - `year`: a number representing the year.
905 | - `month`: a number representing the month.
906 | - `args`: a set of numbers representing the date, hours, minutes, seconds, and milliseconds.
907 | 
908 | **Returns:**
909 | 
910 | the number of milliseconds since midnight of January 1, 1970 according to universal time.
911 | 
912 | ---
913 | 
914 | ### valueOf
915 | 
916 | **Signature:** `valueOf() : Object`
917 | 
918 | **Description:** Returns the value of this Date represented in milliseconds.
919 | 
920 | **Returns:**
921 | 
922 | the value of this Date represented in milliseconds.
923 | 
924 | ---
```

--------------------------------------------------------------------------------
/tests/oauth-token.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { TokenManager } from '../src/clients/base/oauth-token.js';
  2 | import { OAuthTokenResponse } from '../src/types/types';
  3 | 
  4 | describe('TokenManager', () => {
  5 |   let tokenManager: TokenManager;
  6 |   const testHostname = 'test-instance.demandware.net';
  7 |   const testClientId = 'test-client-id';
  8 |   const testClientId2 = 'test-client-id-2';
  9 |   const testHostname2 = 'test-instance-2.demandware.net';
 10 | 
 11 |   beforeEach(() => {
 12 |     // Get a fresh instance and clear all tokens
 13 |     tokenManager = TokenManager.getInstance();
 14 |     tokenManager.clearAllTokens();
 15 |   });
 16 | 
 17 |   afterEach(() => {
 18 |     // Clean up after each test
 19 |     tokenManager.clearAllTokens();
 20 |   });
 21 | 
 22 |   describe('Singleton pattern', () => {
 23 |     it('should return the same instance when called multiple times', () => {
 24 |       const instance1 = TokenManager.getInstance();
 25 |       const instance2 = TokenManager.getInstance();
 26 |       const instance3 = TokenManager.getInstance();
 27 | 
 28 |       expect(instance1).toBe(instance2);
 29 |       expect(instance2).toBe(instance3);
 30 |       expect(instance1).toBe(tokenManager);
 31 |     });
 32 | 
 33 |     it('should maintain state across getInstance calls', () => {
 34 |       const tokenResponse: OAuthTokenResponse = {
 35 |         access_token: 'test-token',
 36 |         token_type: 'bearer',
 37 |         expires_in: 3600,
 38 |       };
 39 | 
 40 |       const instance1 = TokenManager.getInstance();
 41 |       instance1.storeToken(testHostname, testClientId, tokenResponse);
 42 | 
 43 |       const instance2 = TokenManager.getInstance();
 44 |       expect(instance2.getValidToken(testHostname, testClientId)).toBe('test-token');
 45 |     });
 46 |   });
 47 | 
 48 |   describe('storeToken()', () => {
 49 |     it('should store a token correctly', () => {
 50 |       const tokenResponse: OAuthTokenResponse = {
 51 |         access_token: 'stored-token',
 52 |         token_type: 'bearer',
 53 |         expires_in: 3600,
 54 |       };
 55 | 
 56 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
 57 | 
 58 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('stored-token');
 59 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
 60 |     });
 61 | 
 62 |     it('should calculate expiration time correctly', () => {
 63 |       const tokenResponse: OAuthTokenResponse = {
 64 |         access_token: 'expiration-test-token',
 65 |         token_type: 'bearer',
 66 |         expires_in: 3600, // 1 hour
 67 |       };
 68 | 
 69 |       const beforeStore = Date.now();
 70 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
 71 |       const afterStore = Date.now();
 72 | 
 73 |       const expiration = tokenManager.getTokenExpiration(testHostname, testClientId);
 74 |       expect(expiration).toBeInstanceOf(Date);
 75 | 
 76 |       if (expiration) {
 77 |         const expirationTime = expiration.getTime();
 78 |         const expectedMinExpiration = beforeStore + (3600 * 1000);
 79 |         const expectedMaxExpiration = afterStore + (3600 * 1000);
 80 | 
 81 |         expect(expirationTime).toBeGreaterThanOrEqual(expectedMinExpiration);
 82 |         expect(expirationTime).toBeLessThanOrEqual(expectedMaxExpiration);
 83 |       }
 84 |     });
 85 | 
 86 |     it('should overwrite existing tokens for the same hostname/clientId', () => {
 87 |       const firstToken: OAuthTokenResponse = {
 88 |         access_token: 'first-token',
 89 |         token_type: 'bearer',
 90 |         expires_in: 3600,
 91 |       };
 92 | 
 93 |       const secondToken: OAuthTokenResponse = {
 94 |         access_token: 'second-token',
 95 |         token_type: 'bearer',
 96 |         expires_in: 7200,
 97 |       };
 98 | 
 99 |       tokenManager.storeToken(testHostname, testClientId, firstToken);
100 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('first-token');
101 | 
102 |       tokenManager.storeToken(testHostname, testClientId, secondToken);
103 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('second-token');
104 |     });
105 | 
106 |     it('should store different tokens for different hostname/clientId combinations', () => {
107 |       const token1: OAuthTokenResponse = {
108 |         access_token: 'token-1',
109 |         token_type: 'bearer',
110 |         expires_in: 3600,
111 |       };
112 | 
113 |       const token2: OAuthTokenResponse = {
114 |         access_token: 'token-2',
115 |         token_type: 'bearer',
116 |         expires_in: 3600,
117 |       };
118 | 
119 |       const token3: OAuthTokenResponse = {
120 |         access_token: 'token-3',
121 |         token_type: 'bearer',
122 |         expires_in: 3600,
123 |       };
124 | 
125 |       // Same hostname, different clientId
126 |       tokenManager.storeToken(testHostname, testClientId, token1);
127 |       tokenManager.storeToken(testHostname, testClientId2, token2);
128 | 
129 |       // Different hostname, same clientId
130 |       tokenManager.storeToken(testHostname2, testClientId, token3);
131 | 
132 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('token-1');
133 |       expect(tokenManager.getValidToken(testHostname, testClientId2)).toBe('token-2');
134 |       expect(tokenManager.getValidToken(testHostname2, testClientId)).toBe('token-3');
135 |     });
136 |   });
137 | 
138 |   describe('getValidToken()', () => {
139 |     it('should return null for non-existent tokens', () => {
140 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
141 |       expect(tokenManager.getValidToken('nonexistent.host', 'nonexistent-client')).toBeNull();
142 |     });
143 | 
144 |     it('should return valid tokens', () => {
145 |       const tokenResponse: OAuthTokenResponse = {
146 |         access_token: 'valid-token',
147 |         token_type: 'bearer',
148 |         expires_in: 3600,
149 |       };
150 | 
151 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
152 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('valid-token');
153 |     });
154 | 
155 |     it('should return null for expired tokens', async () => {
156 |       const shortLivedToken: OAuthTokenResponse = {
157 |         access_token: 'short-lived-token',
158 |         token_type: 'bearer',
159 |         expires_in: 0.001, // Very short expiration (1ms)
160 |       };
161 | 
162 |       tokenManager.storeToken(testHostname, testClientId, shortLivedToken);
163 | 
164 |       // Wait for token to expire
165 |       await new Promise(resolve => setTimeout(resolve, 100));
166 | 
167 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
168 |     });
169 | 
170 |     it('should return null for tokens that expire within 60-second buffer', () => {
171 |       // Create a token that expires in 30 seconds (within the 60-second buffer)
172 |       const soonToExpireToken: OAuthTokenResponse = {
173 |         access_token: 'soon-to-expire-token',
174 |         token_type: 'bearer',
175 |         expires_in: 30, // 30 seconds
176 |       };
177 | 
178 |       tokenManager.storeToken(testHostname, testClientId, soonToExpireToken);
179 | 
180 |       // Should return null because it's within the 60-second buffer
181 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
182 |     });
183 | 
184 |     it('should return tokens that expire beyond the 60-second buffer', () => {
185 |       // Create a token that expires in 120 seconds (beyond the 60-second buffer)
186 |       const validToken: OAuthTokenResponse = {
187 |         access_token: 'valid-token-beyond-buffer',
188 |         token_type: 'bearer',
189 |         expires_in: 120, // 2 minutes
190 |       };
191 | 
192 |       tokenManager.storeToken(testHostname, testClientId, validToken);
193 | 
194 |       // Should return the token because it expires beyond the 60-second buffer
195 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('valid-token-beyond-buffer');
196 |     });
197 |   });
198 | 
199 |   describe('isTokenValid()', () => {
200 |     it('should return false for non-existent tokens', () => {
201 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
202 |       expect(tokenManager.isTokenValid('nonexistent.host', 'nonexistent-client')).toBe(false);
203 |     });
204 | 
205 |     it('should return true for valid tokens', () => {
206 |       const tokenResponse: OAuthTokenResponse = {
207 |         access_token: 'valid-token',
208 |         token_type: 'bearer',
209 |         expires_in: 3600,
210 |       };
211 | 
212 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
213 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
214 |     });
215 | 
216 |     it('should return false for expired tokens', async () => {
217 |       const shortLivedToken: OAuthTokenResponse = {
218 |         access_token: 'expired-token',
219 |         token_type: 'bearer',
220 |         expires_in: 0.001, // Very short expiration
221 |       };
222 | 
223 |       tokenManager.storeToken(testHostname, testClientId, shortLivedToken);
224 | 
225 |       // Wait for expiration
226 |       await new Promise(resolve => setTimeout(resolve, 100));
227 | 
228 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
229 |     });
230 | 
231 |     it('should return false for tokens within 60-second expiration buffer', () => {
232 |       const soonToExpireToken: OAuthTokenResponse = {
233 |         access_token: 'buffer-test-token',
234 |         token_type: 'bearer',
235 |         expires_in: 30, // 30 seconds - within buffer
236 |       };
237 | 
238 |       tokenManager.storeToken(testHostname, testClientId, soonToExpireToken);
239 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
240 |     });
241 | 
242 |     it('should return true for tokens beyond 60-second expiration buffer', () => {
243 |       const validToken: OAuthTokenResponse = {
244 |         access_token: 'buffer-safe-token',
245 |         token_type: 'bearer',
246 |         expires_in: 120, // 2 minutes - beyond buffer
247 |       };
248 | 
249 |       tokenManager.storeToken(testHostname, testClientId, validToken);
250 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
251 |     });
252 | 
253 |     it('should handle edge case of exactly 60-second expiration', () => {
254 |       const exactBufferToken: OAuthTokenResponse = {
255 |         access_token: 'exact-buffer-token',
256 |         token_type: 'bearer',
257 |         expires_in: 60, // Exactly 60 seconds
258 |       };
259 | 
260 |       tokenManager.storeToken(testHostname, testClientId, exactBufferToken);
261 |       // Should be false because 60 seconds is not > 60 seconds (buffer)
262 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
263 |     });
264 | 
265 |     it('should handle edge case just beyond 60-second buffer', () => {
266 |       const justBeyondBufferToken: OAuthTokenResponse = {
267 |         access_token: 'just-beyond-buffer-token',
268 |         token_type: 'bearer',
269 |         expires_in: 61, // 61 seconds - just beyond buffer
270 |       };
271 | 
272 |       tokenManager.storeToken(testHostname, testClientId, justBeyondBufferToken);
273 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
274 |     });
275 |   });
276 | 
277 |   describe('clearToken()', () => {
278 |     it('should remove specific tokens', () => {
279 |       const tokenResponse: OAuthTokenResponse = {
280 |         access_token: 'to-be-cleared',
281 |         token_type: 'bearer',
282 |         expires_in: 3600,
283 |       };
284 | 
285 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
286 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('to-be-cleared');
287 | 
288 |       tokenManager.clearToken(testHostname, testClientId);
289 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
290 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
291 |     });
292 | 
293 |     it('should not affect other tokens when clearing specific token', () => {
294 |       const token1: OAuthTokenResponse = {
295 |         access_token: 'token-1',
296 |         token_type: 'bearer',
297 |         expires_in: 3600,
298 |       };
299 | 
300 |       const token2: OAuthTokenResponse = {
301 |         access_token: 'token-2',
302 |         token_type: 'bearer',
303 |         expires_in: 3600,
304 |       };
305 | 
306 |       tokenManager.storeToken(testHostname, testClientId, token1);
307 |       tokenManager.storeToken(testHostname, testClientId2, token2);
308 | 
309 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('token-1');
310 |       expect(tokenManager.getValidToken(testHostname, testClientId2)).toBe('token-2');
311 | 
312 |       tokenManager.clearToken(testHostname, testClientId);
313 | 
314 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
315 |       expect(tokenManager.getValidToken(testHostname, testClientId2)).toBe('token-2');
316 |     });
317 | 
318 |     it('should handle clearing non-existent tokens gracefully', () => {
319 |       // Should not throw when clearing non-existent token
320 |       expect(() => {
321 |         tokenManager.clearToken(testHostname, testClientId);
322 |       }).not.toThrow();
323 | 
324 |       expect(() => {
325 |         tokenManager.clearToken('nonexistent.host', 'nonexistent-client');
326 |       }).not.toThrow();
327 |     });
328 |   });
329 | 
330 |   describe('clearAllTokens()', () => {
331 |     it('should remove all stored tokens', () => {
332 |       const token1: OAuthTokenResponse = {
333 |         access_token: 'token-1',
334 |         token_type: 'bearer',
335 |         expires_in: 3600,
336 |       };
337 | 
338 |       const token2: OAuthTokenResponse = {
339 |         access_token: 'token-2',
340 |         token_type: 'bearer',
341 |         expires_in: 3600,
342 |       };
343 | 
344 |       const token3: OAuthTokenResponse = {
345 |         access_token: 'token-3',
346 |         token_type: 'bearer',
347 |         expires_in: 3600,
348 |       };
349 | 
350 |       tokenManager.storeToken(testHostname, testClientId, token1);
351 |       tokenManager.storeToken(testHostname, testClientId2, token2);
352 |       tokenManager.storeToken(testHostname2, testClientId, token3);
353 | 
354 |       // Verify all tokens are stored
355 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('token-1');
356 |       expect(tokenManager.getValidToken(testHostname, testClientId2)).toBe('token-2');
357 |       expect(tokenManager.getValidToken(testHostname2, testClientId)).toBe('token-3');
358 | 
359 |       tokenManager.clearAllTokens();
360 | 
361 |       // Verify all tokens are cleared
362 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
363 |       expect(tokenManager.getValidToken(testHostname, testClientId2)).toBeNull();
364 |       expect(tokenManager.getValidToken(testHostname2, testClientId)).toBeNull();
365 |     });
366 | 
367 |     it('should handle clearing when no tokens exist', () => {
368 |       expect(() => {
369 |         tokenManager.clearAllTokens();
370 |       }).not.toThrow();
371 | 
372 |       // Should still work normally after clearing empty storage
373 |       const tokenResponse: OAuthTokenResponse = {
374 |         access_token: 'after-clear-all',
375 |         token_type: 'bearer',
376 |         expires_in: 3600,
377 |       };
378 | 
379 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
380 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('after-clear-all');
381 |     });
382 |   });
383 | 
384 |   describe('getTokenExpiration()', () => {
385 |     it('should return null for non-existent tokens', () => {
386 |       expect(tokenManager.getTokenExpiration(testHostname, testClientId)).toBeNull();
387 |       expect(tokenManager.getTokenExpiration('nonexistent.host', 'nonexistent-client')).toBeNull();
388 |     });
389 | 
390 |     it('should return correct expiration date for existing tokens', () => {
391 |       const tokenResponse: OAuthTokenResponse = {
392 |         access_token: 'expiration-date-token',
393 |         token_type: 'bearer',
394 |         expires_in: 3600, // 1 hour
395 |       };
396 | 
397 |       const beforeStore = Date.now();
398 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
399 |       const afterStore = Date.now();
400 | 
401 |       const expiration = tokenManager.getTokenExpiration(testHostname, testClientId);
402 | 
403 |       expect(expiration).toBeInstanceOf(Date);
404 |       if (expiration) {
405 |         const expirationTime = expiration.getTime();
406 |         const expectedMinExpiration = beforeStore + (3600 * 1000);
407 |         const expectedMaxExpiration = afterStore + (3600 * 1000);
408 | 
409 |         expect(expirationTime).toBeGreaterThanOrEqual(expectedMinExpiration);
410 |         expect(expirationTime).toBeLessThanOrEqual(expectedMaxExpiration);
411 |       }
412 |     });
413 | 
414 |     it('should return different expiration times for different tokens', () => {
415 |       const shortToken: OAuthTokenResponse = {
416 |         access_token: 'short-token',
417 |         token_type: 'bearer',
418 |         expires_in: 1800, // 30 minutes
419 |       };
420 | 
421 |       const longToken: OAuthTokenResponse = {
422 |         access_token: 'long-token',
423 |         token_type: 'bearer',
424 |         expires_in: 7200, // 2 hours
425 |       };
426 | 
427 |       tokenManager.storeToken(testHostname, testClientId, shortToken);
428 |       tokenManager.storeToken(testHostname, testClientId2, longToken);
429 | 
430 |       const shortExpiration = tokenManager.getTokenExpiration(testHostname, testClientId);
431 |       const longExpiration = tokenManager.getTokenExpiration(testHostname, testClientId2);
432 | 
433 |       expect(shortExpiration).toBeInstanceOf(Date);
434 |       expect(longExpiration).toBeInstanceOf(Date);
435 | 
436 |       if (shortExpiration && longExpiration) {
437 |         expect(longExpiration.getTime()).toBeGreaterThan(shortExpiration.getTime());
438 |       }
439 |     });
440 | 
441 |     it('should return null after token is cleared', () => {
442 |       const tokenResponse: OAuthTokenResponse = {
443 |         access_token: 'cleared-token',
444 |         token_type: 'bearer',
445 |         expires_in: 3600,
446 |       };
447 | 
448 |       tokenManager.storeToken(testHostname, testClientId, tokenResponse);
449 |       expect(tokenManager.getTokenExpiration(testHostname, testClientId)).toBeInstanceOf(Date);
450 | 
451 |       tokenManager.clearToken(testHostname, testClientId);
452 |       expect(tokenManager.getTokenExpiration(testHostname, testClientId)).toBeNull();
453 |     });
454 |   });
455 | 
456 |   describe('Token key generation', () => {
457 |     it('should create unique keys for different hostname/clientId combinations', () => {
458 |       // We can't directly test the private getTokenKey method, but we can test its behavior
459 |       const token1: OAuthTokenResponse = {
460 |         access_token: 'unique-test-1',
461 |         token_type: 'bearer',
462 |         expires_in: 3600,
463 |       };
464 | 
465 |       const token2: OAuthTokenResponse = {
466 |         access_token: 'unique-test-2',
467 |         token_type: 'bearer',
468 |         expires_in: 3600,
469 |       };
470 | 
471 |       // Store tokens with similar but different keys
472 |       tokenManager.storeToken('host1.com', 'client1', token1);
473 |       tokenManager.storeToken('host1.com', 'client2', token2);
474 | 
475 |       expect(tokenManager.getValidToken('host1.com', 'client1')).toBe('unique-test-1');
476 |       expect(tokenManager.getValidToken('host1.com', 'client2')).toBe('unique-test-2');
477 | 
478 |       // Different hostname, same client
479 |       tokenManager.storeToken('host2.com', 'client1', token1);
480 |       expect(tokenManager.getValidToken('host2.com', 'client1')).toBe('unique-test-1');
481 |       expect(tokenManager.getValidToken('host1.com', 'client1')).toBe('unique-test-1'); // Should still exist
482 |     });
483 | 
484 |     it('should handle special characters in hostname and clientId', () => {
485 |       const tokenResponse: OAuthTokenResponse = {
486 |         access_token: 'special-chars-token',
487 |         token_type: 'bearer',
488 |         expires_in: 3600,
489 |       };
490 | 
491 |       const specialHostname = 'test-instance_with.special-chars.demandware.net';
492 |       const specialClientId = 'client-id_with.special:chars';
493 | 
494 |       tokenManager.storeToken(specialHostname, specialClientId, tokenResponse);
495 |       expect(tokenManager.getValidToken(specialHostname, specialClientId)).toBe('special-chars-token');
496 |       expect(tokenManager.isTokenValid(specialHostname, specialClientId)).toBe(true);
497 |     });
498 |   });
499 | 
500 |   describe('Edge cases and error handling', () => {
501 |     it('should handle zero expiration time', () => {
502 |       const zeroExpirationToken: OAuthTokenResponse = {
503 |         access_token: 'zero-expiration-token',
504 |         token_type: 'bearer',
505 |         expires_in: 0,
506 |       };
507 | 
508 |       tokenManager.storeToken(testHostname, testClientId, zeroExpirationToken);
509 | 
510 |       // Should be invalid immediately due to buffer
511 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
512 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
513 |     });
514 | 
515 |     it('should handle negative expiration time', () => {
516 |       const negativeExpirationToken: OAuthTokenResponse = {
517 |         access_token: 'negative-expiration-token',
518 |         token_type: 'bearer',
519 |         expires_in: -100,
520 |       };
521 | 
522 |       tokenManager.storeToken(testHostname, testClientId, negativeExpirationToken);
523 | 
524 |       // Should be invalid immediately
525 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
526 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBeNull();
527 |     });
528 | 
529 |     it('should handle very large expiration times', () => {
530 |       const largeExpirationToken: OAuthTokenResponse = {
531 |         access_token: 'large-expiration-token',
532 |         token_type: 'bearer',
533 |         expires_in: 86400 * 365, // 1 year in seconds
534 |       };
535 | 
536 |       tokenManager.storeToken(testHostname, testClientId, largeExpirationToken);
537 | 
538 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
539 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('large-expiration-token');
540 | 
541 |       const expiration = tokenManager.getTokenExpiration(testHostname, testClientId);
542 |       expect(expiration).toBeInstanceOf(Date);
543 |     });
544 | 
545 |     it('should handle empty strings for hostname and clientId', () => {
546 |       const tokenResponse: OAuthTokenResponse = {
547 |         access_token: 'empty-string-token',
548 |         token_type: 'bearer',
549 |         expires_in: 3600,
550 |       };
551 | 
552 |       // Should not throw errors with empty strings
553 |       expect(() => {
554 |         tokenManager.storeToken('', '', tokenResponse);
555 |       }).not.toThrow();
556 | 
557 |       expect(() => {
558 |         tokenManager.getValidToken('', '');
559 |       }).not.toThrow();
560 | 
561 |       expect(() => {
562 |         tokenManager.isTokenValid('', '');
563 |       }).not.toThrow();
564 | 
565 |       expect(() => {
566 |         tokenManager.clearToken('', '');
567 |       }).not.toThrow();
568 | 
569 |       expect(() => {
570 |         tokenManager.getTokenExpiration('', '');
571 |       }).not.toThrow();
572 |     });
573 | 
574 |     it('should handle fractional expiration times', () => {
575 |       const fractionalExpirationToken: OAuthTokenResponse = {
576 |         access_token: 'fractional-expiration-token',
577 |         token_type: 'bearer',
578 |         expires_in: 3600.5, // 1 hour and 30 minutes
579 |       };
580 | 
581 |       tokenManager.storeToken(testHostname, testClientId, fractionalExpirationToken);
582 | 
583 |       expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
584 |       expect(tokenManager.getValidToken(testHostname, testClientId)).toBe('fractional-expiration-token');
585 |     });
586 |   });
587 | 
588 |   describe('Concurrency and state consistency', () => {
589 |     it('should maintain consistent state with rapid operations', () => {
590 |       const tokenResponse: OAuthTokenResponse = {
591 |         access_token: 'rapid-ops-token',
592 |         token_type: 'bearer',
593 |         expires_in: 3600,
594 |       };
595 | 
596 |       // Rapid store/clear/check operations
597 |       for (let i = 0; i < 100; i++) {
598 |         tokenManager.storeToken(testHostname, testClientId, tokenResponse);
599 |         expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(true);
600 |         tokenManager.clearToken(testHostname, testClientId);
601 |         expect(tokenManager.isTokenValid(testHostname, testClientId)).toBe(false);
602 |       }
603 |     });
604 | 
605 |     it('should handle multiple token storage operations correctly', () => {
606 |       const baseToken: OAuthTokenResponse = {
607 |         access_token: '',
608 |         token_type: 'bearer',
609 |         expires_in: 3600,
610 |       };
611 | 
612 |       // Store multiple tokens rapidly
613 |       for (let i = 0; i < 50; i++) {
614 |         const token = { ...baseToken, access_token: `token-${i}` };
615 |         tokenManager.storeToken(testHostname, `client-${i}`, token);
616 |       }
617 | 
618 |       // Verify all tokens are stored correctly
619 |       for (let i = 0; i < 50; i++) {
620 |         expect(tokenManager.getValidToken(testHostname, `client-${i}`)).toBe(`token-${i}`);
621 |       }
622 | 
623 |       // Clear all and verify
624 |       tokenManager.clearAllTokens();
625 |       for (let i = 0; i < 50; i++) {
626 |         expect(tokenManager.getValidToken(testHostname, `client-${i}`)).toBeNull();
627 |       }
628 |     });
629 |   });
630 | });
631 | 
```

--------------------------------------------------------------------------------
/scripts/convert-docs.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import axios from 'axios';
  4 | import * as cheerio from 'cheerio';
  5 | import fs from 'fs/promises';
  6 | import path from 'path';
  7 | import { fileURLToPath } from 'url';
  8 | 
  9 | const __filename = fileURLToPath(import.meta.url);
 10 | const __dirname = path.dirname(__filename);
 11 | 
 12 | const BASE_URL = 'https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/scriptapi/html/api';
 13 | const OUTPUT_DIR = path.join(__dirname, '..', 'docs');
 14 | 
 15 | // Configuration options
 16 | const CONFIG = {
 17 |     // Limit number of classes per package (0 = no limit)
 18 |     maxClassesPerPackage: 0,
 19 |     // Limit number of packages (0 = no limit)
 20 |     maxPackages: 0,
 21 |     // Enable debug logging
 22 |     debug: false,
 23 |     // Rate limiting settings
 24 |     rateLimit: {
 25 |         // Delay between requests in milliseconds
 26 |         requestDelay: 1000,
 27 |         // Maximum requests per minute
 28 |         maxRequestsPerMinute: 30,
 29 |         // Delay between processing packages in milliseconds
 30 |         packageDelay: 2000,
 31 |         // Random jitter to add (0-1 multiplier)
 32 |         jitter: 0.3
 33 |     }
 34 | };
 35 | 
 36 | // Override config from command line args
 37 | if (process.argv.includes('--test')) {
 38 |     CONFIG.maxClassesPerPackage = 3;
 39 |     CONFIG.maxPackages = 2;
 40 |     CONFIG.debug = true;
 41 | }
 42 | 
 43 | if (process.argv.includes('--limit')) {
 44 |     const limitIndex = process.argv.indexOf('--limit');
 45 |     if (limitIndex >= 0 && process.argv[limitIndex + 1]) {
 46 |         CONFIG.maxClassesPerPackage = parseInt(process.argv[limitIndex + 1]) || 5;
 47 |     }
 48 | }
 49 | 
 50 | if (process.argv.includes('--fast')) {
 51 |     CONFIG.rateLimit.requestDelay = 500;
 52 |     CONFIG.rateLimit.maxRequestsPerMinute = 45;
 53 |     CONFIG.rateLimit.packageDelay = 1000;
 54 |     console.log('Using fast mode (less conservative rate limiting)');
 55 | }
 56 | 
 57 | if (process.argv.includes('--slow')) {
 58 |     CONFIG.rateLimit.requestDelay = 2000;
 59 |     CONFIG.rateLimit.maxRequestsPerMinute = 15;
 60 |     CONFIG.rateLimit.packageDelay = 5000;
 61 |     console.log('Using slow mode (very conservative rate limiting)');
 62 | }
 63 | 
 64 | // Ensure output directory exists
 65 | async function ensureDir(dir) {
 66 |     try {
 67 |         await fs.access(dir);
 68 |     } catch {
 69 |         await fs.mkdir(dir, { recursive: true });
 70 |     }
 71 | }
 72 | 
 73 | // Fetch HTML content from URL with rate limiting
 74 | async function fetchHTML(url) {
 75 |     // Apply rate limiting before making request
 76 |     await rateLimiter.waitForRateLimit();
 77 | 
 78 |     try {
 79 |         console.log(`Fetching: ${url}`);
 80 | 
 81 |         const response = await axios.get(url, {
 82 |             headers: {
 83 |                 'User-Agent': rateLimiter.getRandomUserAgent(),
 84 |                 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
 85 |                 'Accept-Language': 'en-US,en;q=0.5',
 86 |                 'Accept-Encoding': 'gzip, deflate, br',
 87 |                 'Connection': 'keep-alive',
 88 |                 'Upgrade-Insecure-Requests': '1',
 89 |                 'Sec-Fetch-Dest': 'document',
 90 |                 'Sec-Fetch-Mode': 'navigate',
 91 |                 'Sec-Fetch-Site': 'none',
 92 |                 'Cache-Control': 'max-age=0'
 93 |             },
 94 |             timeout: 30000, // 30 second timeout
 95 |             validateStatus: function (status) {
 96 |                 return status >= 200 && status < 300; // Accept only 2xx status codes
 97 |             }
 98 |         });
 99 | 
100 |         return response.data;
101 |     } catch (error) {
102 |         if (error.response && error.response.status === 429) {
103 |             console.warn(`Rate limited by server. Waiting 60 seconds before retry...`);
104 |             // eslint-disable-next-line no-undef
105 |             await new Promise(resolve => setTimeout(resolve, 60000));
106 |             // Retry once after rate limit
107 |             try {
108 |                 const retryResponse = await axios.get(url, {
109 |                     headers: {
110 |                         'User-Agent': rateLimiter.getRandomUserAgent(),
111 |                         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
112 |                         'Accept-Language': 'en-US,en;q=0.5',
113 |                         'Accept-Encoding': 'gzip, deflate, br',
114 |                         'Connection': 'keep-alive',
115 |                         'Upgrade-Insecure-Requests': '1',
116 |                         'Cache-Control': 'max-age=0'
117 |                     },
118 |                     timeout: 30000
119 |                 });
120 |                 return retryResponse.data;
121 |             } catch (retryError) {
122 |                 console.error(`Retry failed for ${url}:`, retryError.message);
123 |                 return null;
124 |             }
125 |         }
126 | 
127 |         console.error(`Error fetching ${url}:`, error.message);
128 |         return null;
129 |     }
130 | }
131 | 
132 | // Helper function to clean and normalize text
133 | function cleanText(text) {
134 |     return text
135 |         .replace(/\s+/g, ' ') // Replace multiple whitespace with single space
136 |         .replace(/\n\s*\n/g, '\n') // Remove empty lines
137 |         .replace(/\t/g, ' ') // Replace tabs with spaces
138 |         .trim();
139 | }
140 | 
141 | // Helper function to extract method signature cleanly
142 | function cleanMethodSignature(htmlContent) {
143 |     const $ = cheerio.load(`<div>${htmlContent}</div>`);
144 | 
145 |     // Extract static keyword if present
146 |     let signature = '';
147 |     if (htmlContent.includes('static')) {
148 |         signature += 'static ';
149 |     }
150 | 
151 |     // Extract method name from emphasis span
152 |     const methodName = $('.emphasis').text().trim();
153 |     if (methodName) {
154 |         signature += methodName;
155 |     }
156 | 
157 |     // Get the full text and clean it up
158 |     const fullText = $.text().replace(/\s+/g, ' ').trim();
159 | 
160 |     // Extract parameters and return type using regex
161 |     const methodPattern = /(\w+)\s*\(\s*([^)]*)\s*\)\s*:\s*(.+?)$/;
162 |     const match = fullText.match(methodPattern);
163 | 
164 |     if (match) {
165 |         const params = match[2].trim();
166 |         const returnType = match[3].trim();
167 | 
168 |         // Clean up parameters - remove excessive whitespace and normalize format
169 |         let cleanParams = '';
170 |         if (params) {
171 |             cleanParams = params
172 |                 .replace(/\s*:\s*/g, ' : ')  // Normalize colons
173 |                 .replace(/,\s+/g, ', ')      // Normalize commas
174 |                 .replace(/\s+/g, ' ')        // Single spaces
175 |                 .trim();
176 |         }
177 | 
178 |         signature += `(${cleanParams}) : ${returnType}`;
179 |     } else {
180 |         // Fallback - try to extract just the method call part
181 |         const simpleMatch = fullText.match(/(\w+\s*\([^)]*\))/);
182 |         if (simpleMatch) {
183 |             const methodCall = simpleMatch[1].replace(/\s+/g, ' ').trim();
184 |             signature += methodCall;
185 |         }
186 |     }
187 | 
188 |     return signature.replace(/\s+/g, ' ').trim();
189 | }
190 | 
191 | // Convert HTML to Markdown
192 | function htmlToMarkdown(html) {
193 |     const $ = cheerio.load(html);
194 | 
195 |     // Remove script tags and other unwanted elements
196 |     $('script, style, .banner, .site-footer, header').remove();
197 | 
198 |     let markdown = '';
199 | 
200 |     // Find the main class content
201 |     const classDiv = $('[id^="class_"]').first();
202 | 
203 |     if (classDiv.length === 0) {
204 |         console.warn('No class content found');
205 |         return '';
206 |     }
207 | 
208 |     // Extract package name
209 |     const packageName = classDiv.find('.packageName').text().trim();
210 |     if (packageName) {
211 |         markdown += `## Package: ${packageName}\n\n`;
212 |     }
213 | 
214 |     // Extract class name
215 |     const className = cleanText(classDiv.find('.className').text());
216 |     if (className) {
217 |         markdown += `# ${className}\n\n`;
218 |     }
219 | 
220 |     // Extract inheritance hierarchy
221 |     const hierarchy = classDiv.find('.hierarchy');
222 |     if (hierarchy.length > 0) {
223 |         markdown += '## Inheritance Hierarchy\n\n';
224 |         hierarchy.find('div').each((i, div) => {
225 |             const $div = $(div);
226 |             const text = cleanText($div.text());
227 |             if (text) {
228 |                 // Calculate indentation based on left position
229 |                 const leftStyle = $div.attr('style') || '';
230 |                 const leftMatch = leftStyle.match(/left:\s*(\d+)%/);
231 |                 const level = leftMatch ? Math.floor(parseInt(leftMatch[1]) / 3) : 0;
232 |                 const indent = '  '.repeat(level);
233 |                 markdown += `${indent}- ${text}\n`;
234 |             }
235 |         });
236 |         markdown += '\n';
237 |     }
238 | 
239 |     // Extract class description
240 |     const description = cleanText(classDiv.find('.classSummary .description').text());
241 |     if (description) {
242 |         markdown += '## Description\n\n';
243 |         markdown += `${description}\n\n`;
244 |     }
245 | 
246 |     // Process sections (Properties, Methods, etc.)
247 |     classDiv.find('.section').each((i, section) => {
248 |         const $section = $(section);
249 |         const header = cleanText($section.find('.header').first().text());
250 | 
251 |         if (!header) return;
252 | 
253 |         markdown += `## ${header}\n\n`;
254 | 
255 |         // Handle different section types
256 |         if (header === 'Constants') {
257 |             $section.find('.summaryItem').each((j, item) => {
258 |                 const $item = $(item);
259 | 
260 |                 // Extract constant name from the text content before the colon
261 |                 const spanContent = $item.find('span').first();
262 |                 const fullText = spanContent.text();
263 |                 
264 |                 // Parse the constant line: "CONSTANT_NAME : Type = value"
265 |                 const constMatch = fullText.match(/^([A-Z_][A-Z0-9_]*)\s*:/);
266 |                 const constName = constMatch ? constMatch[1].trim() : '';
267 | 
268 |                 // Extract type - look for the type link after the colon
269 |                 const typeLink = spanContent.find('a span').first();
270 |                 const constType = typeLink.text().trim();
271 | 
272 |                 // Extract value if present (after the = sign)
273 |                 const valueMatch = fullText.match(/=\s*([^]+?)(?=\s|$)/);
274 |                 const constValue = valueMatch ? valueMatch[1].trim() : '';
275 | 
276 |                 const desc = cleanText($item.find('.description').text());
277 | 
278 |                 if (constName) {
279 |                     markdown += `### ${constName}\n\n`;
280 |                     if (constType) {
281 |                         let typeInfo = `**Type:** ${constType}`;
282 |                         if (constValue) {
283 |                             typeInfo += ` = ${constValue}`;
284 |                         }
285 |                         markdown += `${typeInfo}\n\n`;
286 |                     }
287 |                     if (desc) {
288 |                         markdown += `${desc}\n\n`;
289 |                     }
290 |                 }
291 |             });
292 |         } else if (header === 'Properties') {
293 |             $section.find('.summaryItem').each((j, item) => {
294 |                 const $item = $(item);
295 | 
296 |                 // For properties, parse the span content more carefully
297 |                 const spanContent = $item.find('span').first();
298 |                 const fullText = spanContent.text();
299 | 
300 |                 // Extract property name (text before the first colon)
301 |                 const nameMatch = fullText.match(/^([^\s:]+)\s*:/);
302 |                 const propName = nameMatch ? nameMatch[1].trim() : '';
303 | 
304 |                 // Extract type - look for linked type names
305 |                 const typeLinks = spanContent.find('a');
306 |                 let propType = '';
307 |                 typeLinks.each((idx, link) => {
308 |                     const $link = $(link);
309 |                     const linkText = $link.text().trim();
310 |                     // Skip if it's an anchor link (starts with #)
311 |                     if (!$link.attr('href')?.startsWith('#') && linkText) {
312 |                         propType = linkText;
313 |                         return false; // break the loop
314 |                     }
315 |                 });
316 | 
317 |                 // If no type found in links, try to extract from text pattern
318 |                 if (!propType) {
319 |                     const typeMatch = fullText.match(/:\s*([A-Za-z][A-Za-z0-9]*)/);
320 |                     if (typeMatch) {
321 |                         propType = typeMatch[1];
322 |                     }
323 |                 }
324 | 
325 |                 // Check for modifiers
326 |                 const isStatic = fullText.includes('static');
327 |                 const isReadOnly = fullText.includes('(Read Only)');
328 | 
329 |                 const desc = $item.find('.description').text().trim();
330 | 
331 |                 if (propName) {
332 |                     markdown += `### ${propName}\n\n`;
333 |                     if (propType) {
334 |                         let typeInfo = `**Type:** ${propType}`;
335 |                         if (isStatic) typeInfo += ' (Static)';
336 |                         if (isReadOnly) typeInfo += ' (Read Only)';
337 |                         markdown += `${typeInfo}\n\n`;
338 |                     }
339 |                     if (desc) {
340 |                         markdown += `${desc}\n\n`;
341 |                     }
342 |                 }
343 |             });
344 |         } else if (header === 'Method Summary' || header.includes('Method')) {
345 |             $section.find('.summaryItem').each((j, item) => {
346 |                 const $item = $(item);
347 | 
348 |                 // Extract method name from emphasis link
349 |                 const methodLink = $item.find('.emphasis a').first();
350 |                 const methodName = methodLink.text().trim();
351 | 
352 |                 // Get clean signature
353 |                 const signature = cleanMethodSignature($item.find('span').first().html());
354 | 
355 |                 const desc = cleanText($item.find('.description').text());
356 | 
357 |                 if (methodName && !desc.startsWith('This class does not have')) {
358 |                     markdown += `### ${methodName}\n\n`;
359 |                     if (signature) {
360 |                         markdown += `**Signature:** \`${signature}\`\n\n`;
361 |                     }
362 |                     if (desc) {
363 |                         markdown += `${desc}\n\n`;
364 |                     }
365 |                 }
366 |             });
367 |         } else if (header === 'Constructor Summary') {
368 |             $section.find('.summaryItem').each((j, item) => {
369 |                 const $item = $(item);
370 |                 const constructorText = cleanText($item.text());
371 |                 if (constructorText && !constructorText.includes('This class does not have')) {
372 |                     markdown += `${constructorText}\n\n`;
373 |                 }
374 |             });
375 |         } else {
376 |             // Handle inherited methods and other sections
377 |             const content = cleanText($section.find('.summaryItem').text());
378 |             if (content && !content.includes('This class does not have')) {
379 |                 markdown += `${content}\n\n`;
380 |             }
381 |         }
382 |     });
383 | 
384 |     // Process detailed method descriptions
385 |     const methodDetails = classDiv.find('.section').filter((i, el) => {
386 |         return $(el).find('.header').text().trim() === 'Method Detail';
387 |     });
388 | 
389 |     if (methodDetails.length > 0) {
390 |         markdown += '## Method Details\n\n';
391 | 
392 |         methodDetails.find('.detailItem').each((i, item) => {
393 |             const $item = $(item);
394 | 
395 |             const methodName = cleanText($item.find('.detailName').text());
396 |             const signature = cleanText($item.find('.detailSignature').text());
397 |             const description = cleanText($item.find('.description').first().text());
398 | 
399 |             if (methodName) {
400 |                 markdown += `### ${methodName}\n\n`;
401 | 
402 |                 if (signature) {
403 |                     markdown += `**Signature:** \`${signature}\`\n\n`;
404 |                 }
405 | 
406 |                 if (description) {
407 |                     markdown += `**Description:** ${description}\n\n`;
408 |                 }
409 | 
410 |                 // Process parameters
411 |                 $item.find('.parameters').each((j, param) => {
412 |                     const $param = $(param);
413 |                     const title = cleanText($param.find('.parameterTitle').text());
414 | 
415 |                     if (title) {
416 |                         markdown += `**${title}**\n\n`;
417 | 
418 |                         $param.find('.parameterDetail').each((k, detail) => {
419 |                             const $detail = $(detail);
420 |                             const paramName = cleanText($detail.find('.parameterName').text());
421 |                             const paramDesc = cleanText($detail.find('.parameterDesc').text());
422 | 
423 |                             if (paramName && paramDesc) {
424 |                                 markdown += `- \`${paramName}\`: ${paramDesc}\n`;
425 |                             } else {
426 |                                 const detailText = cleanText($detail.text());
427 |                                 if (detailText) {
428 |                                     markdown += `${detailText}\n`;
429 |                                 }
430 |                             }
431 |                         });
432 | 
433 |                         markdown += '\n';
434 |                     }
435 |                 });
436 | 
437 |                 markdown += '---\n\n';
438 |             }
439 |         });
440 |     }
441 | 
442 |     // Clean up the final markdown
443 |     return markdown
444 |         .replace(/\n{3,}/g, '\n\n') // Replace 3+ newlines with 2
445 |         .trim();
446 | }
447 | 
448 | // Rate limiting utilities
449 | class RateLimiter {
450 |     constructor(config) {
451 |         this.config = config;
452 |         this.requestTimes = [];
453 |         this.userAgents = [
454 |             'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
455 |             'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
456 |             'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
457 |             'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0'
458 |         ];
459 |         this.currentUserAgentIndex = 0;
460 |     }
461 | 
462 |     async waitForRateLimit() {
463 |         const now = Date.now();
464 | 
465 |         // Remove requests older than 1 minute
466 |         this.requestTimes = this.requestTimes.filter(time => now - time < 60000);
467 | 
468 |         // Check if we're at the rate limit
469 |         if (this.requestTimes.length >= this.config.maxRequestsPerMinute) {
470 |             const oldestRequest = Math.min(...this.requestTimes);
471 |             const waitTime = 60000 - (now - oldestRequest) + 1000; // Add 1 second buffer
472 | 
473 |             if (waitTime > 0) {
474 |                 console.log(`Rate limit reached. Waiting ${Math.ceil(waitTime / 1000)} seconds...`);
475 |                 // eslint-disable-next-line no-undef
476 |                 await new Promise(resolve => setTimeout(resolve, waitTime));
477 |             }
478 |         }
479 | 
480 |         // Apply base delay with jitter
481 |         const baseDelay = this.config.requestDelay;
482 |         const jitter = Math.random() * this.config.jitter * baseDelay;
483 |         const totalDelay = baseDelay + jitter;
484 | 
485 |         if (CONFIG.debug) {
486 |             console.log(`Applying delay: ${Math.ceil(totalDelay)}ms`);
487 |         }
488 | 
489 |         // eslint-disable-next-line no-undef
490 |         await new Promise(resolve => setTimeout(resolve, totalDelay));
491 | 
492 |         // Record this request
493 |         this.requestTimes.push(Date.now());
494 |     }
495 | 
496 |     getRandomUserAgent() {
497 |         const userAgent = this.userAgents[this.currentUserAgentIndex];
498 |         this.currentUserAgentIndex = (this.currentUserAgentIndex + 1) % this.userAgents.length;
499 |         return userAgent;
500 |     }
501 | }
502 | 
503 | // Initialize rate limiter
504 | const rateLimiter = new RateLimiter(CONFIG.rateLimit);
505 | 
506 | // Get all packages from overview page
507 | async function getPackages() {
508 |     const overviewHTML = await fetchHTML(`${BASE_URL}/overview.html`);
509 |     if (!overviewHTML) return [];
510 | 
511 |     const $ = cheerio.load(overviewHTML);
512 |     const packages = [];
513 | 
514 |     $('.summaryItem a').each((i, element) => {
515 |         const href = $(element).attr('href');
516 |         const name = $(element).text().trim();
517 |         if (href && href.startsWith('package_')) {
518 |             packages.push({
519 |                 name,
520 |                 href,
521 |                 id: href.replace('.html', '').replace('package_', '')
522 |             });
523 |         }
524 |     });
525 | 
526 |     return packages;
527 | }
528 | 
529 | // Get all classes from a package page
530 | async function getClassesFromPackage(packageHref) {
531 |     const packageHTML = await fetchHTML(`${BASE_URL}/${packageHref}`);
532 |     if (!packageHTML) return [];
533 | 
534 |     const $ = cheerio.load(packageHTML);
535 |     const classes = [];
536 | 
537 |     $('.classesName a').each((i, element) => {
538 |         const href = $(element).attr('href');
539 |         const name = $(element).text().trim();
540 |         if (href && href.startsWith('class_')) {
541 |             classes.push({
542 |                 name,
543 |                 href,
544 |                 id: href.replace('.html', '').replace('class_', '')
545 |             });
546 |         }
547 |     });
548 | 
549 |     return classes;
550 | }
551 | 
552 | // Convert a single class to markdown
553 | async function convertClass(classInfo, packageName) {
554 |     const classHTML = await fetchHTML(`${BASE_URL}/${classInfo.href}`);
555 |     if (!classHTML) return;
556 | 
557 |     const markdown = htmlToMarkdown(classHTML);
558 |     if (!markdown.trim()) {
559 |         console.warn(`No content generated for ${classInfo.name}`);
560 |         return;
561 |     }
562 | 
563 |     // Create package directory
564 |     const packageDir = path.join(OUTPUT_DIR, packageName.replace('.', '_'));
565 |     await ensureDir(packageDir);
566 | 
567 |     // Write markdown file
568 |     const filename = `${classInfo.name}.md`;
569 |     const filepath = path.join(packageDir, filename);
570 | 
571 |     await fs.writeFile(filepath, markdown, 'utf8');
572 |     console.log(`✓ Converted: ${packageName}.${classInfo.name} -> ${filepath}`);
573 | }
574 | 
575 | // Main function
576 | async function main() {
577 |     console.log('Starting SFCC documentation conversion...');
578 | 
579 |     if (CONFIG.maxPackages > 0 || CONFIG.maxClassesPerPackage > 0) {
580 |         console.log('Running in limited mode:');
581 |         if (CONFIG.maxPackages > 0) console.log(`- Max packages: ${CONFIG.maxPackages}`);
582 |         if (CONFIG.maxClassesPerPackage > 0) console.log(`- Max classes per package: ${CONFIG.maxClassesPerPackage}`);
583 |     }
584 | 
585 |     await ensureDir(OUTPUT_DIR);
586 | 
587 |     // Get all packages
588 |     const packages = await getPackages();
589 |     console.log(`Found ${packages.length} packages`);
590 | 
591 |     // Limit packages if configured
592 |     const packageLimit = CONFIG.maxPackages > 0 ? CONFIG.maxPackages : packages.length;
593 |     const packagesToProcess = packages.slice(0, packageLimit);
594 | 
595 |     for (const pkg of packagesToProcess) {
596 |         console.log(`\nProcessing package: ${pkg.name}`);
597 | 
598 |         // Get classes in this package
599 |         const classes = await getClassesFromPackage(pkg.href);
600 |         console.log(`Found ${classes.length} classes in ${pkg.name}`);
601 | 
602 |         // Limit classes if configured
603 |         const classLimit = CONFIG.maxClassesPerPackage > 0 ? CONFIG.maxClassesPerPackage : classes.length;
604 |         const classesToProcess = classes.slice(0, classLimit);
605 | 
606 |         if (classLimit < classes.length) {
607 |             console.log(`Processing first ${classLimit} classes only...`);
608 |         }
609 | 
610 |         // Convert each class
611 |         for (const cls of classesToProcess) {
612 |             await convertClass(cls, pkg.name);
613 |         }
614 | 
615 |         // Delay between processing packages
616 |         if (pkg !== packagesToProcess[packagesToProcess.length - 1]) {
617 |             const packageDelay = CONFIG.rateLimit.packageDelay;
618 |             const jitter = Math.random() * CONFIG.rateLimit.jitter * packageDelay;
619 |             console.log(`Waiting ${Math.ceil((packageDelay + jitter) / 1000)}s before next package...`);
620 |             // eslint-disable-next-line no-undef
621 |             await new Promise(resolve => setTimeout(resolve, packageDelay + jitter));
622 |         }
623 |     }
624 | 
625 |     console.log('\n✓ Documentation conversion completed!');
626 | }
627 | 
628 | // Handle command line execution
629 | if (import.meta.url === `file://${process.argv[1]}`) {
630 |     main().catch(console.error);
631 | }
632 | 
633 | export { main, convertClass, getPackages, getClassesFromPackage };
634 | 
```
Page 38/61FirstPrevNextLast