feat(salesforce): initial scaffold port from deco-cx/apps#71
Merged
Merged
Conversation
Ports the Salesforce Marketing Cloud Personalization (Evergage) campaign personalization API as a new `@decocms/apps/salesforce` package, mirroring the algolia scaffold pattern (#66). Scope: read-path loaders for homepage shelves, PDP recommendations, and cart-aware cross-sell. No actions, no analytics. The legacy Deno loaders are stateless — config (`baseUrl` / `dataset` / `campaignId` / `cookieName`) comes in via loader props rather than a global `configureSalesforce`, since real sites typically run multiple Evergage datasets per worker. Layout: - `types.ts` — `SalesforceProduct` (open via index signature), `PersonalizationBody`, response shapes. - `utils/parseUserCookie.ts` — decode the URL-encoded JSON cookie Evergage drops on the browser; falls back to anonymous fallback. - `utils/httpClient.ts` — runtime-agnostic Proxy client supporting the legacy `client["POST /api2/event/:dataset"]` indexed-route syntax used by every Deno-era loader. - `utils/transform.ts` — `createProductTransformer({ propertyMapper? })` so site-side wrappers can project dataset-specific Evergage columns (`Marca`, `Volume`, `Linha`, …) without forking the schema.org map. - `loaders/products/{list,listRecomended,listCart}.ts` — the three campaign endpoints granadobr-tanstack consumes today. Cookie access goes through `getCookies()` from `@tanstack/react-start/server` (parameterless, ALS-backed) because the framework's `commerceLoader(resolvedProps)` path drops the `req` argument before reaching the loader. Mirrors the workaround already used in helsinki's site-side packs. Tests: 48 new tests (parseUserCookie, transform, httpClient) lock the contract against the deco-cx/apps Deno baseline — all mock fetch, no Evergage credentials needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
🎉 This PR is included in version 3.1.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the Salesforce Marketing Cloud Personalization (Evergage) campaign API as a new
@decocms/apps/salesforcepackage, mirroring the algolia scaffold pattern (#66).Scope: read-path loaders only — homepage shelves, PDP recommendations, cart-aware cross-sell. No actions, no analytics sections. Follow-up PRs will add coverage in line with the algolia series (#67/#68/#69).
Layout
Key design choices vs algolia
configureSalesforceglobal — every loader takesbaseUrl/dataset/campaignId/cookieNamevia props. Real sites run multiple Evergage datasets per worker (homepage uses dataset A, PDP uses dataset B), so a single module-global config doesn't fit.getCookies()from@tanstack/react-start/server(parameterless, ALS-backed) instead ofgetCookies(req.headers). The framework'scommerceLoader(resolvedProps)path drops thereqargument before reaching the loader, so the deferred-section call sites can't read cookies through the explicit-headers approach magento uses. The dynamic import is wrapped in try/catch so unit tests don't blow up outside a TanStack request boundary.propertyMapperhook on the transformer — Evergage datasets expose dataset-specific columns (Marca,Volume,Linha,tag__phebo,freeShipping, …). The default mapper outputs only the always-present fields (itemType,category); site-side wrappers pass a mapper to project their custom columns intoschema.org/PropertyValue[]without forking the schema.org map.SalesforceProductkeeps an[customField: string]: unknownindex signature so the mapper can read raw extras safely.Error handling
Each loader wraps its POST in
try/catchand logs toconsole.error("[salesforce/products/...] failed:", err.message)before returningnull. The legacy Deno loaders returned silentnullon error — we keep the same return shape (callers don't need to handle rejections) but surface the error so API outages and CORS regressions don't hide behind an empty shelf during parity validation.Package wiring
package.json— adds./salesforce,./salesforce/types,./salesforce/utils/*,./salesforce/loaders/products/*exports +salesforce/tofiles+@tanstack/react-start >=1topeerDependencies(transitively present via@decocms/start, now explicit so knip stays happy).biome.json— includessalesforce/**.knip.json— adds entry globs.Test plan
bun run typecheckcleanbun run test— 49 files / 595 tests pass (48 new undersalesforce/__tests__/)bun run lintonsalesforce/— clean except 1noExplicitAnywarning on the Proxy return type (matches the same suppression already inmagento/client.ts:112)bun run lint:unused— no salesforce-specific complaintsgranadobr-tanstackonce published (Fase B of plan)🤖 Generated with Claude Code
Summary by cubic
Add
@decocms/apps/salesforce, a port of the Evergage campaign API for homepage shelves, PDP recommendations, and cart cross-sell. Loaders are stateless, read-only, and include a flexiblepropertyMappertransformer hook.New Features
salesforce/loaders/products/list.ts,listRecomended.ts, andlistCart.ts.baseUrl,dataset,campaignId,cookieName); no globalconfigureSalesforce.getCookies()from@tanstack/react-start/serverwith safe anonymous fallback (utils/parseUserCookie.ts).utils/httpClient.ts).createProductTransformer({ propertyMapper })(utils/transform.ts).nullto match existing call sites.Dependencies
filesentry for./salesforce/*inpackage.json.@tanstack/react-start >=1.biome.jsonandknip.jsonglobs to includesalesforce/**.Written for commit e9f8e94. Summary will update on new commits.