vtex-io-session-apps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVTEX IO session transform apps
VTEX IO 会话转换应用
When this skill applies
本指南适用场景
Use this skill when your VTEX IO app integrates with the VTEX session system () to derive, compute, or propagate state that downstream transforms, the storefront, or checkout depend on.
vtex.session- Building a session transform that computes custom fields from upstream session state (e.g. pricing context from an external backend, regionalization from org data)
- Declaring input/output fields in
vtex.session/configuration.json - Deciding which namespace your app should own and which it should read from
- Propagating values into inputs so native transforms (profile, search, checkout) re-run
public.* - Debugging stale session fields, race conditions, or namespace collisions between apps
- Designing B2B session flows where , custom transforms, and checkout interact
storefront-permissions
Do not use this skill for:
- General IO backend patterns (use )
vtex-io-service-apps - Performance patterns outside session transforms (use )
vtex-io-application-performance - GraphQL schema or resolver design (use )
vtex-io-graphql-api
当你的VTEX IO应用需要集成VTEX会话系统(),来派生、计算或传播下游转换逻辑、店面或结账流程依赖的状态时,可以使用本指南:
vtex.session- 构建会话转换逻辑,基于上游会话状态计算自定义字段(例如从外部后端获取定价上下文、从组织数据计算区域化规则)
- 在中声明输入/输出字段
vtex.session/configuration.json - 确定你的应用应该拥有哪个命名空间,以及可以读取哪些其他命名空间的内容
- 把值传播到****输入字段中,触发原生转换逻辑(用户资料、搜索、结账)重新运行
public.* - 调试会话字段过期、竞态条件或应用间的命名空间冲突问题
- 设计B2B会话流程,协调、自定义转换逻辑和结账流程的交互
storefront-permissions
本指南不适用于以下场景:
- 通用IO后端模式(请参考)
vtex-io-service-apps - 会话转换之外的性能优化模式(请参考)
vtex-io-application-performance - GraphQL schema或解析器设计(请参考)
vtex-io-graphql-api
Decision rules
决策规则
Namespace ownership
命名空间所有权
- Every session app owns exactly one output namespace (or a small set of fields within one). The namespace name typically matches the app concept (e.g. ,
rona,myapp).storefront-permissions - Never write to another app's output namespace. If owns
storefront-permissions, your transform must not overwrite it—read it as an input instead.storefront-permissions.organization - Never duplicate VTEX-owned fields (org, cost center, postal, country) into your namespace when they already exist in ,
storefront-permissions,profile, orcheckout. Your namespace should contain only data that comes from your backend or computation.store
- 每个会话应用仅拥有一个输出命名空间(或一个命名空间下的少量字段)。命名空间名称通常和应用功能匹配(例如、
rona、myapp)。storefront-permissions - 绝对不要写入其他应用的输出命名空间。 如果拥有
storefront-permissions字段,你的转换逻辑不能覆盖它,而是应该把它作为输入读取。storefront-permissions.organization - 绝对不要复制VTEX官方拥有的字段(组织、成本中心、邮编、国家)到你的命名空间,这些字段已经存在于、
storefront-permissions、profile或checkout中。你的命名空间应该仅包含来自你的后端或计算逻辑的数据。store
public
is input, private is read model
publicpublic
是输入层,private是读取模型
public- fields are an input surface: values the shopper or a flow sets so session transforms can run (e.g. geolocation, flags, UTMs, user intent). Do not treat
public.*as the canonical read model in storefront code.public.* - Private namespaces (,
profile,checkout,store,search, your custom namespace) are the read model: computed outputs derived from inputs. Frontend components should read private namespace fields for business rules and display.storefront-permissions - If your transform must influence native apps (e.g. set a postal code derived from a cost center address), update input fields that native apps declare as inputs—so the platform re-runs those upstream transforms and private outputs stay consistent. This is input propagation, not duplicating truth.
public.*
- 字段是输入层:是消费者或流程设置的值,供会话转换逻辑运行使用(例如地理位置、标记位、UTM参数、用户意图)。不要在店面代码中把
public.*当做权威读取模型。public.* - 私有命名空间(、
profile、checkout、store、search、你的自定义命名空间)是读取模型:是基于输入派生的计算输出。前端组件应该读取私有命名空间的字段来实现业务规则和展示逻辑。storefront-permissions - 如果你的转换逻辑需要影响原生应用(例如设置基于成本中心地址派生的邮编),请更新原生应用声明为输入的字段,这样平台会重新运行上游转换逻辑,保证私有输出的一致性。这是输入传播,不是重复存储真值。
public.*
Transform ordering (DAG)
转换执行顺序(DAG)
- VTEX session runs transforms in a directed acyclic graph (DAG) based on declared input/output dependencies in each app's .
vtex.session/configuration.json - A transform runs when any of its declared input fields change. If you depend on , your transform runs after
storefront-permissions.costcenteroutputs that field.storefront-permissions - Order your dependencies carefully: if your transform needs both outputs and
storefront-permissionsoutputs, declare both as inputs so the platform schedules you after both.profile
- VTEX会话系统会基于每个应用的中声明的输入/输出依赖,按照**有向无环图(DAG)**的顺序运行转换逻辑。
vtex.session/configuration.json - 当任意一个声明的输入字段变更时,转换逻辑就会运行。如果你依赖字段,你的转换逻辑会在
storefront-permissions.costcenter输出该字段之后运行。storefront-permissions - 仔细规划你的依赖顺序:如果你的转换逻辑同时需要和
storefront-permissions的输出,需要把两者都声明为输入,这样平台会在两者都运行完成后再调度你的转换逻辑。profile
Caching inside transforms
转换逻辑内部的缓存
- Session transforms execute on every session change that touches a declared input. They must be fast.
- Use LRU (in-process, per-worker) for hot lookups (org data, configuration, tokens) with short TTLs.
- Use VBase stale-while-revalidate for data that can tolerate brief staleness (external backend responses, computed mappings). Return stale immediately; revalidate in the background.
- Follow the same tenant-keying rules as any IO service: in-memory cache keys must include and
account(seeworkspace).vtex-io-application-performance
- 会话转换逻辑会在每次会话变更触及声明的输入字段时执行,必须保证运行速度够快。
- 热点查询(组织数据、配置、令牌)可以使用LRU(进程内、单工作进程级别)缓存,设置较短的TTL。
- 可以容忍短暂过期的数据(外部后端响应、计算映射关系)使用VBase stale-while-revalidate缓存:立即返回过期内容,后台异步更新。
- 遵循所有IO服务通用的租户键规则:内存缓存键必须包含**和
account**(参考workspace)。vtex-io-application-performance
Frontend session consumption
前端会话消费
- Storefront components should request specific session items via the query parameter (e.g.
items=).items=rona.storeNumber,storefront-permissions.costcenter - Read from the relevant private namespaces (,
rona.*,storefront-permissions.*, etc.) for canonical state.profile.* - Write to only when setting user intent (e.g. selecting a location, switching a flag). Never write to
public.*as a "cache" for values that private namespaces already provide.public.*
- 店面组件应该通过查询参数请求特定的会话字段(例如
items=)。items=rona.storeNumber,storefront-permissions.costcenter - 从对应的私有命名空间(、
rona.*、storefront-permissions.*等)读取权威状态。profile.* - 仅在设置用户意图时(例如选择位置、切换标记位)才写入字段。绝对不要把
public.*当做私有命名空间已有值的“缓存”使用。public.*
Hard constraints
强制约束
Constraint: Do not duplicate another app's output namespace fields into your namespace
约束:不要把其他应用的输出命名空间字段复制到你的命名空间
Your session transform must output only fields that come from your computation or backend. Copying identity, address, or org fields that , , or already own creates two sources of truth that diverge on partial failures.
storefront-permissionsprofilecheckoutWhy this matters — When two namespaces contain the same fact (e.g. in both your namespace and ), consumers read inconsistent values after a session that partially updated. Debug time skyrockets and race conditions appear.
costCenterIdstorefront-permissionsDetection — Your transform's output includes fields like , , , that mirror or outputs. Or frontend reads the same logical field from two different namespaces.
organizationcostcenterpostalCodecountrystorefront-permissions.*profile.*Correct — Read as an input; use it to compute your backend-specific fields (e.g. , ); output only those derived fields.
storefront-permissions.costcentermyapp.priceTablemyapp.storeNumberjson
{
"my-session-app": {
"input": {
"storefront-permissions": ["costcenter", "organization"]
},
"output": {
"myapp": ["priceTable", "storeNumber"]
}
}
}Wrong — Output duplicates of VTEX-owned fields.
json
{
"my-session-app": {
"output": {
"myapp": ["costcenter", "organization", "postalCode", "priceTable", "storeNumber"]
}
}
}你的会话转换逻辑必须仅输出来自你的计算逻辑或后端的字段。复制、或已经拥有的身份、地址或组织字段会产生两个真值来源,在部分失败的场景下会出现数据不一致。
storefront-permissionsprofilecheckout为什么重要 — 当两个命名空间包含同一个事实数据时(例如你的命名空间和都有),会话部分更新后消费者会读取到不一致的值,调试成本大幅提升,还会出现竞态条件。
storefront-permissionscostCenterId检测方式 — 你的转换逻辑输出包含、、、等和或输出重复的字段。或者前端从两个不同的命名空间读取同一个逻辑字段。
organizationcostcenterpostalCodecountrystorefront-permissions.*profile.*正确写法 — 把作为输入读取,用它计算你的后端专属字段(例如、),仅输出这些派生字段。
storefront-permissions.costcentermyapp.priceTablemyapp.storeNumberjson
{
"my-session-app": {
"input": {
"storefront-permissions": ["costcenter", "organization"]
},
"output": {
"myapp": ["priceTable", "storeNumber"]
}
}
}错误写法 — 输出VTEX官方拥有的字段的副本。
json
{
"my-session-app": {
"output": {
"myapp": ["costcenter", "organization", "postalCode", "priceTable", "storeNumber"]
}
}
}Constraint: Use input propagation to influence native transforms, not direct overwrites
约束:使用输入传播影响原生转换逻辑,不要直接覆盖
When your transform derives a value (e.g. postal code from a cost center address) that native apps consume, set it as an input field those apps declare (typically , )—not by writing directly to or .
public.postalCodepublic.countrycheckout.postalCodesearch.postalCodeWhy this matters — Native transforms expect their input fields to change so they can recompute their output fields. Writing directly to their output namespaces bypasses recomputation and leaves stale derived state (e.g. not updated, checkout address inconsistent).
regionIdDetection — Your transform declares output fields in namespaces owned by other apps (e.g. or ). Or you PATCH session with values in a namespace you don't own.
output: { checkout: [...] }output: { search: [...] }Correct — Declare output in for fields that native apps consume as inputs, verified against each native app's .
publicvtex.session/configuration.jsonjson
{
"my-session-app": {
"output": {
"myapp": ["storeNumber", "priceTable"],
"public": ["postalCode", "country", "state"]
}
}
}Wrong — Writing to search or checkout output namespaces directly.
json
{
"my-session-app": {
"output": {
"myapp": ["storeNumber", "priceTable"],
"checkout": ["postalCode", "country"],
"search": ["facets"]
}
}
}当你的转换逻辑派生了原生应用需要消费的值(例如从成本中心地址得到的邮编),把它设置为原生应用声明的输入字段(通常是、),不要直接写入或。
public.postalCodepublic.countrycheckout.postalCodesearch.postalCode为什么重要 — 原生转换逻辑期望输入字段变更时触发重新计算输出字段。直接写入它们的输出命名空间会跳过重新计算流程,导致派生状态过期(例如没有更新、结账地址不一致)。
regionId检测方式 — 你的转换逻辑在其他应用拥有的命名空间中声明输出字段(例如或)。或者你PATCH会话时写入了你不拥有的命名空间的值。
output: { checkout: [...] }output: { search: [...] }正确写法 — 在中声明原生应用作为输入消费的字段输出,需要和每个原生应用的核对一致。
publicvtex.session/configuration.jsonjson
{
"my-session-app": {
"output": {
"myapp": ["storeNumber", "priceTable"],
"public": ["postalCode", "country", "state"]
}
}
}错误写法 — 直接写入search或checkout输出命名空间。
json
{
"my-session-app": {
"output": {
"myapp": ["storeNumber", "priceTable"],
"checkout": ["postalCode", "country"],
"search": ["facets"]
}
}
}Constraint: Frontend must read private namespaces, not public
, for canonical business state
public约束:前端必须读取私有命名空间获取权威业务状态,不要读取public
publicStorefront components and middleware must read session data from the authoritative private namespace (e.g. , , ), not from fields.
storefront-permissions.organizationprofile.emailmyapp.priceTablepublic.*Why this matters — fields are inputs that may be stale, user-set, or partial. Private namespace fields are the computed truth after all transforms have run. Reading instead of the profile- or checkout-derived value leads to displaying stale or inconsistent data.
public.*public.postalCodeDetection — React components or middleware that read , , or for display or business logic instead of the corresponding private field.
public.storeNumberpublic.organizationpublic.costCenterCorrect
typescript
// Read from the authoritative namespace
const { data } = useSessionItems([
'myapp.storeNumber',
'myapp.priceTable',
'storefront-permissions.costcenter',
'storefront-permissions.organization',
])Wrong
typescript
// Reading from public as if it were the source of truth
const { data } = useSessionItems([
'public.storeNumber',
'public.organization',
'public.costCenter',
])店面组件和中间件必须从权威私有命名空间读取会话数据(例如、、),不要读取字段。
storefront-permissions.organizationprofile.emailmyapp.priceTablepublic.*为什么重要 — 字段是输入,可能是过期的、用户设置的或者不完整的。私有命名空间字段是所有转换逻辑运行完成后的计算真值。读取而不是profile或checkout派生的值会导致展示过期或不一致的数据。
public.*public.postalCode检测方式 — React组件或中间件读取、或用于展示或业务逻辑,而不是读取对应的私有字段。
public.storeNumberpublic.organizationpublic.costCenter正确写法
typescript
// 从权威命名空间读取
const { data } = useSessionItems([
'myapp.storeNumber',
'myapp.priceTable',
'storefront-permissions.costcenter',
'storefront-permissions.organization',
])错误写法
typescript
// 把public当做真值来源读取
const { data } = useSessionItems([
'public.storeNumber',
'public.organization',
'public.costCenter',
])Preferred pattern
推荐模式
vtex.session/configuration.json
vtex.session/configuration.jsonvtex.session/configuration.json
vtex.session/configuration.jsonDeclare your transform's input dependencies and output fields:
json
{
"my-session-app": {
"input": {
"storefront-permissions": ["costcenter", "organization", "costCenterAddressId"]
},
"output": {
"myapp": ["storeNumber", "priceTable"]
}
}
}声明你的转换逻辑的输入依赖和输出字段:
json
{
"my-session-app": {
"input": {
"storefront-permissions": ["costcenter", "organization", "costCenterAddressId"]
},
"output": {
"myapp": ["storeNumber", "priceTable"]
}
}
}Transform handler
转换处理函数
typescript
// node/handlers/transform.ts
export async function transform(ctx: Context) {
const { costcenter, organization } = parseSfpInputs(ctx.request.body)
if (!costcenter) {
ctx.body = { myapp: {} }
return
}
const costCenterData = await getCostCenterCached(ctx, costcenter)
const pricing = await resolvePricing(ctx, costCenterData)
ctx.body = {
myapp: {
storeNumber: pricing.storeNumber,
priceTable: pricing.priceTable,
},
}
}typescript
// node/handlers/transform.ts
export async function transform(ctx: Context) {
const { costcenter, organization } = parseSfpInputs(ctx.request.body)
if (!costcenter) {
ctx.body = { myapp: {} }
return
}
const costCenterData = await getCostCenterCached(ctx, costcenter)
const pricing = await resolvePricing(ctx, costCenterData)
ctx.body = {
myapp: {
storeNumber: pricing.storeNumber,
priceTable: pricing.priceTable,
},
}
}Caching inside the transform
转换逻辑内部的缓存
typescript
// Two-layer cache: LRU (sub-ms) -> VBase (persistent, SWR) -> API
const costCenterLRU = new LRU<string, CostCenterData>({ max: 1000, ttl: 600_000 })
async function getCostCenterCached(ctx: Context, costCenterId: string) {
const { account, workspace } = ctx.vtex
const key = `${account}:${workspace}:${costCenterId}`
const lruHit = costCenterLRU.get(key)
if (lruHit) return lruHit
const result = await staleFromVBaseWhileRevalidate(
ctx.clients.vbase,
'cost-centers',
costCenterId,
() => fetchCostCenterFromAPI(ctx, costCenterId),
{ ttlMs: 1_800_000 }
)
costCenterLRU.set(key, result)
return result
}typescript
// 两层缓存:LRU(亚毫秒级) -> VBase(持久化,SWR) -> API
const costCenterLRU = new LRU<string, CostCenterData>({ max: 1000, ttl: 600_000 })
async function getCostCenterCached(ctx: Context, costCenterId: string) {
const { account, workspace } = ctx.vtex
const key = `${account}:${workspace}:${costCenterId}`
const lruHit = costCenterLRU.get(key)
if (lruHit) return lruHit
const result = await staleFromVBaseWhileRevalidate(
ctx.clients.vbase,
'cost-centers',
costCenterId,
() => fetchCostCenterFromAPI(ctx, costCenterId),
{ ttlMs: 1_800_000 }
)
costCenterLRU.set(key, result)
return result
}service.json
route
service.jsonservice.json
路由
service.jsonjson
{
"routes": {
"transform": {
"path": "/_v/my-session-app/session/transform",
"public": true
}
}
}json
{
"routes": {
"transform": {
"path": "/_v/my-session-app/session/transform",
"public": true
}
}
}Session ecosystem awareness
会话生态感知
When building a transform, map out the transform DAG for your store:
text
authentication-session → impersonate-session → profile-session
profile-session → store-session → checkout-session
profile-session → search-session
authentication-session + checkout-session + impersonate-session → storefront-permissions
storefront-permissions → YOUR-TRANSFORM (reads SFP outputs)Your transform sits at the end of whatever dependency chain it requires. Declaring inputs correctly ensures the platform schedules you after all upstream transforms.
构建转换逻辑时,梳理清楚你的店铺的转换DAG:
text
authentication-session → impersonate-session → profile-session
profile-session → store-session → checkout-session
profile-session → search-session
authentication-session + checkout-session + impersonate-session → storefront-permissions
storefront-permissions → YOUR-TRANSFORM (reads SFP outputs)你的转换逻辑会位于它所需的依赖链的末端。正确声明输入可以保证平台在所有上游转换逻辑运行完成后再调度你的逻辑。
Common failure modes
常见失败场景
- Frontend writes B2B state via — Instead of letting
updateSession+ your transform compute B2B session fields, the frontend PATCHes them directly. This creates race conditions, partial state, and duplicated sources of truth.storefront-permissions - Duplicating VTEX-owned fields — Copying ,
costcenter, ororganizationinto your namespace when they already live inpostalCodeorstorefront-permissions.profile - Slow transforms without caching — Calling external APIs on every transform invocation without LRU + VBase SWR. Transforms run on every session change that touches a declared input; they must be fast.
- Reading as source of truth — Frontend components reading
public.*orpublic.organizationinstead of the private namespace field, leading to stale or inconsistent display.public.storeNumber - Writing to other apps' output namespaces — Declaring output fields in ,
checkout, orsearchnamespaces you don't own, bypassing native transform recomputation.storefront-permissions - Missing tenant keys in LRU — In-memory cache for org or pricing data keyed only by entity ID without , unsafe on multi-tenant shared pods.
account:workspace
- 前端通过写入B2B状态 — 前端直接PATCH B2B会话字段,而不是让
updateSession和你的转换逻辑计算这些字段。这会导致竞态条件、部分状态和重复的真值来源。storefront-permissions - 重复VTEX官方拥有的字段 — 把已经存在于或
storefront-permissions中的profile、costcenter或organization复制到你的命名空间。postalCode - 没有缓存的慢转换逻辑 — 每次转换逻辑调用都请求外部API,没有使用LRU + VBase SWR缓存。转换逻辑会在每次会话变更触及声明的输入时运行,必须保证速度够快。
- 把当做真值来源读取 — 前端组件读取
public.*或public.organization而不是私有命名空间字段,导致展示过期或不一致的内容。public.storeNumber - 写入其他应用的输出命名空间 — 在你不拥有的、
checkout或search命名空间中声明输出字段,跳过了原生转换逻辑的重新计算。storefront-permissions - LRU中缺少租户键 — 组织或定价数据的内存缓存仅使用实体ID作为键,没有加上,在多租户共享Pod的场景下会有安全问题。
account:workspace
Review checklist
评审检查清单
- Does the transform output only fields from its own computation/backend, not duplicates of other namespaces?
- Are input dependencies declared correctly in ?
vtex.session/configuration.json - Are output fields limited to your own namespace (plus inputs when propagation is needed)?
public.* - Is used only for input propagation, not as a second read model?
public.* - Do frontend components read from private namespaces, not , for business state?
public.* - Are upstream API calls in the transform cached (LRU + VBase SWR) to keep transform latency low?
- Are in-memory cache keys scoped with for multi-tenant safety?
account:workspace - Is the transform order (DAG) correct—does it run after all its dependency transforms?
- Has been removed from frontend code for fields the transform computes?
updateSession
- 转换逻辑是否仅输出来自自身计算/后端的字段,没有复制其他命名空间的字段?
- 中是否正确声明了输入依赖?
vtex.session/configuration.json - 输出字段是否仅限制在你自己的命名空间(需要传播时可以加上输入)?
public.* - 是否仅用于输入传播,没有被当做第二个读取模型使用?
public.* - 前端组件是否读取私有命名空间获取业务状态,而不是读取?
public.* - 转换逻辑中的上游API调用是否做了缓存(LRU + VBase SWR)来保证转换延迟足够低?
- 内存缓存键是否加上了作用域来保证多租户安全?
account:workspace - 转换顺序(DAG)是否正确——是否在所有依赖的转换逻辑运行完成后才执行?
- 前端代码中是否移除了针对转换逻辑计算字段的调用?
updateSession
Related skills
相关指南
- vtex-io-application-performance — Caching layers and parallel I/O applicable inside transforms
- vtex-io-service-paths-and-cdn — Route prefix for the transform endpoint
- vtex-io-service-apps — Service class, clients, and middleware basics
- vtex-io-app-structure — Manifest, builders, policies
- vtex-io-application-performance — 适用于转换逻辑内部的缓存层和并行I/O优化
- vtex-io-service-paths-and-cdn — 转换端点的路由前缀规则
- vtex-io-service-apps — 服务类、客户端和中间件基础
- vtex-io-app-structure — 清单、构建器、权限规则
Reference
参考资料
- VTEX Session System — Session manager overview and API
- App Development — VTEX IO app development hub
- Clients — VBase, MasterData, and custom clients
- Engineering Guidelines — Scalability and IO development practices
- VTEX Session System — 会话管理器概述和API
- App Development — VTEX IO应用开发中心
- Clients — VBase、MasterData和自定义客户端
- Engineering Guidelines — 可扩展性和IO开发实践