Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:
- run: yarn run lint

test:
needs: [lint]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [7.2.4] - 2025-10-15
### Fix
- Restores backward compatibility for individual header constant exports

## [7.2.3] - 2025-10-08
### Fix
- Separate console and diagnostics client logging formats
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vtex/api",
"version": "7.2.3",
"version": "7.2.4",
"description": "VTEX I/O API client",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
244 changes: 243 additions & 1 deletion src/constants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,36 @@ import {
PRODUCTION,
INSPECT_DEBUGGER_PORT,
cancellableMethods,
LOG_CLIENT_INIT_TIMEOUT_MS
LOG_CLIENT_INIT_TIMEOUT_MS,
// Backward-compatible individual header constants
CACHE_CONTROL_HEADER,
SEGMENT_HEADER,
SESSION_HEADER,
PRODUCT_HEADER,
LOCALE_HEADER,
FORWARDED_HOST_HEADER,
TENANT_HEADER,
BINDING_HEADER,
META_HEADER,
META_HEADER_BUCKET,
ETAG_HEADER,
ACCOUNT_HEADER,
CREDENTIAL_HEADER,
REQUEST_ID_HEADER,
ROUTER_CACHE_HEADER,
OPERATION_ID_HEADER,
PLATFORM_HEADER,
WORKSPACE_IS_PRODUCTION_HEADER,
WORKSPACE_HEADER,
EVENT_KEY_HEADER,
EVENT_SENDER_HEADER,
EVENT_SUBJECT_HEADER,
EVENT_HANDLER_ID_HEADER,
COLOSSUS_ROUTE_DECLARER_HEADER,
COLOSSUS_ROUTE_ID_HEADER,
COLOSSUS_PARAMS_HEADER,
TRACE_ID_HEADER,
PROVIDER_HEADER
} from './constants'

describe('constants', () => {
Expand Down Expand Up @@ -426,5 +455,218 @@ describe('constants', () => {
expect(APP).toHaveProperty(prop)
})
})

describe('Individual header constants (deprecated)', () => {
test('all individual header constants should be defined', () => {
expect(CACHE_CONTROL_HEADER).toBeDefined()
expect(SEGMENT_HEADER).toBeDefined()
expect(SESSION_HEADER).toBeDefined()
expect(PRODUCT_HEADER).toBeDefined()
expect(LOCALE_HEADER).toBeDefined()
expect(FORWARDED_HOST_HEADER).toBeDefined()
expect(TENANT_HEADER).toBeDefined()
expect(BINDING_HEADER).toBeDefined()
expect(META_HEADER).toBeDefined()
expect(META_HEADER_BUCKET).toBeDefined()
expect(ETAG_HEADER).toBeDefined()
expect(ACCOUNT_HEADER).toBeDefined()
expect(CREDENTIAL_HEADER).toBeDefined()
expect(REQUEST_ID_HEADER).toBeDefined()
expect(ROUTER_CACHE_HEADER).toBeDefined()
expect(OPERATION_ID_HEADER).toBeDefined()
expect(PLATFORM_HEADER).toBeDefined()
expect(WORKSPACE_IS_PRODUCTION_HEADER).toBeDefined()
expect(WORKSPACE_HEADER).toBeDefined()
expect(EVENT_KEY_HEADER).toBeDefined()
expect(EVENT_SENDER_HEADER).toBeDefined()
expect(EVENT_SUBJECT_HEADER).toBeDefined()
expect(EVENT_HANDLER_ID_HEADER).toBeDefined()
expect(COLOSSUS_ROUTE_DECLARER_HEADER).toBeDefined()
expect(COLOSSUS_ROUTE_ID_HEADER).toBeDefined()
expect(COLOSSUS_PARAMS_HEADER).toBeDefined()
expect(TRACE_ID_HEADER).toBeDefined()
expect(PROVIDER_HEADER).toBeDefined()
})

test('individual constants should equal HeaderKeys values', () => {
expect(CACHE_CONTROL_HEADER).toBe(HeaderKeys.CACHE_CONTROL)
expect(SEGMENT_HEADER).toBe(HeaderKeys.SEGMENT)
expect(SESSION_HEADER).toBe(HeaderKeys.SESSION)
expect(PRODUCT_HEADER).toBe(HeaderKeys.PRODUCT)
expect(LOCALE_HEADER).toBe(HeaderKeys.LOCALE)
expect(FORWARDED_HOST_HEADER).toBe(HeaderKeys.FORWARDED_HOST)
expect(TENANT_HEADER).toBe(HeaderKeys.TENANT)
expect(BINDING_HEADER).toBe(HeaderKeys.BINDING)
expect(META_HEADER).toBe(HeaderKeys.META)
expect(META_HEADER_BUCKET).toBe(HeaderKeys.META_BUCKET)
expect(ETAG_HEADER).toBe(HeaderKeys.ETAG)
expect(ACCOUNT_HEADER).toBe(HeaderKeys.ACCOUNT)
expect(CREDENTIAL_HEADER).toBe(HeaderKeys.CREDENTIAL)
expect(REQUEST_ID_HEADER).toBe(HeaderKeys.REQUEST_ID)
expect(ROUTER_CACHE_HEADER).toBe(HeaderKeys.ROUTER_CACHE)
expect(OPERATION_ID_HEADER).toBe(HeaderKeys.OPERATION_ID)
expect(PLATFORM_HEADER).toBe(HeaderKeys.PLATFORM)
expect(WORKSPACE_IS_PRODUCTION_HEADER).toBe(HeaderKeys.WORKSPACE_IS_PRODUCTION)
expect(WORKSPACE_HEADER).toBe(HeaderKeys.WORKSPACE)
expect(EVENT_KEY_HEADER).toBe(HeaderKeys.EVENT_KEY)
expect(EVENT_SENDER_HEADER).toBe(HeaderKeys.EVENT_SENDER)
expect(EVENT_SUBJECT_HEADER).toBe(HeaderKeys.EVENT_SUBJECT)
expect(EVENT_HANDLER_ID_HEADER).toBe(HeaderKeys.EVENT_HANDLER_ID)
expect(COLOSSUS_ROUTE_DECLARER_HEADER).toBe(HeaderKeys.COLOSSUS_ROUTE_DECLARER)
expect(COLOSSUS_ROUTE_ID_HEADER).toBe(HeaderKeys.COLOSSUS_ROUTE_ID)
expect(COLOSSUS_PARAMS_HEADER).toBe(HeaderKeys.COLOSSUS_PARAMS)
expect(TRACE_ID_HEADER).toBe(HeaderKeys.TRACE_ID)
expect(PROVIDER_HEADER).toBe(HeaderKeys.PROVIDER)
})

test('critical individual constants should have expected string values', () => {
expect(TENANT_HEADER).toBe('x-vtex-tenant')
expect(BINDING_HEADER).toBe('x-vtex-binding')
expect(LOCALE_HEADER).toBe('x-vtex-locale')
expect(SEGMENT_HEADER).toBe('x-vtex-segment')
expect(SESSION_HEADER).toBe('x-vtex-session')
expect(ACCOUNT_HEADER).toBe('x-vtex-account')
expect(WORKSPACE_HEADER).toBe('x-vtex-workspace')
})

test('individual constants should be strings', () => {
expect(typeof TENANT_HEADER).toBe('string')
expect(typeof BINDING_HEADER).toBe('string')
expect(typeof LOCALE_HEADER).toBe('string')
expect(typeof SEGMENT_HEADER).toBe('string')
})

test('constants can be used as object keys without runtime errors', () => {
// This is how IO apps use them in practice
const headers = {
[TENANT_HEADER]: 'example-value',
[BINDING_HEADER]: 'example-binding',
[LOCALE_HEADER]: 'en-US',
[SEGMENT_HEADER]: 'segment-token',
[SESSION_HEADER]: 'session-token',
[ACCOUNT_HEADER]: 'account-name',
[WORKSPACE_HEADER]: 'master'
}

expect(headers['x-vtex-tenant']).toBe('example-value')
expect(headers['x-vtex-binding']).toBe('example-binding')
expect(headers['x-vtex-locale']).toBe('en-US')
expect(headers['x-vtex-segment']).toBe('segment-token')
expect(Object.keys(headers)).toHaveLength(7)

// Verify no undefined keys were created
Object.keys(headers).forEach(key => {
expect(key).not.toBe('undefined')
expect(headers[key]).toBeDefined()
})
})

test('constants can be destructured from module exports', () => {
// Simulates: import { TENANT_HEADER, BINDING_HEADER } from '@vtex/api'
const constants = require('./constants')
const {
TENANT_HEADER: tenant,
BINDING_HEADER: binding,
LOCALE_HEADER: locale,
SEGMENT_HEADER: segment
} = constants

expect(tenant).toBeDefined()
expect(binding).toBeDefined()
expect(locale).toBeDefined()
expect(segment).toBeDefined()

expect(tenant).toBe('x-vtex-tenant')
expect(binding).toBe('x-vtex-binding')
expect(locale).toBe('x-vtex-locale')
expect(segment).toBe('x-vtex-segment')

// Ensure they're not undefined
expect(tenant).not.toBe(undefined)
expect(binding).not.toBe(undefined)
})

test('individual constants are compatible with VaryHeaders type', () => {
// VaryHeaders type uses HeaderKeys internally, but should accept old constants
const varyHeaderValues: string[] = [SEGMENT_HEADER, SESSION_HEADER, PRODUCT_HEADER, LOCALE_HEADER]

varyHeaderValues.forEach(header => {
expect(typeof header).toBe('string')
expect(header.length).toBeGreaterThan(0)
// VTEX headers follow x-vtex- pattern, except standard headers like cache-control
expect(header).toMatch(/^x-vtex-|^cache-control$|^etag$/)
})

// Verify they match the type definition (HeaderKeys values)
const expectedVaryHeaders = [
HeaderKeys.SEGMENT,
HeaderKeys.SESSION,
HeaderKeys.PRODUCT,
HeaderKeys.LOCALE
]

expect(varyHeaderValues).toEqual(expectedVaryHeaders)

// Ensure VaryHeaders type inference works
expect(SEGMENT_HEADER).toBe(HeaderKeys.SEGMENT)
expect(SESSION_HEADER).toBe(HeaderKeys.SESSION)
expect(PRODUCT_HEADER).toBe(HeaderKeys.PRODUCT)
expect(LOCALE_HEADER).toBe(HeaderKeys.LOCALE)
})

test('constants work correctly as header keys in realistic scenarios', () => {
// Simulates IO apps usage patterns
const mockBinding = { locale: 'en-US', currency: 'USD' }
const mockTenant = { locale: 'pt-BR' }
const mockSegmentToken = 'eyJjYW1wYWlnbnMiOm51bGx9'
const mockSessionToken = 'session-abc-123'

// Pattern 1: Building headers object
const requestHeaders = {
[BINDING_HEADER]: JSON.stringify(mockBinding),
[TENANT_HEADER]: mockTenant.locale,
[LOCALE_HEADER]: 'en-US',
[SEGMENT_HEADER]: mockSegmentToken,
[SESSION_HEADER]: mockSessionToken,
[ACCOUNT_HEADER]: 'vtexstore',
[WORKSPACE_HEADER]: 'master'
}

expect(requestHeaders['x-vtex-binding']).toBe(JSON.stringify(mockBinding))
expect(requestHeaders['x-vtex-tenant']).toBe('pt-BR')
expect(requestHeaders['x-vtex-locale']).toBe('en-US')
expect(requestHeaders['x-vtex-segment']).toBe(mockSegmentToken)
expect(requestHeaders['x-vtex-session']).toBe(mockSessionToken)

// Pattern 2: Conditional header setting
const conditionalHeaders: Record<string, string> = {}
if (mockSegmentToken) {
conditionalHeaders[SEGMENT_HEADER] = mockSegmentToken
}
if (mockSessionToken) {
conditionalHeaders[SESSION_HEADER] = mockSessionToken
}

expect(conditionalHeaders['x-vtex-segment']).toBe(mockSegmentToken)
expect(conditionalHeaders['x-vtex-session']).toBe(mockSessionToken)
expect(Object.keys(conditionalHeaders)).toHaveLength(2)

// Pattern 3: Reading from headers object
const incomingHeaders: Record<string, string> = {
'x-vtex-tenant': 'es-AR',
'x-vtex-binding': '{"locale":"es-AR"}',
'x-vtex-account': 'mystore'
}

expect(incomingHeaders[TENANT_HEADER]).toBe('es-AR')
expect(incomingHeaders[BINDING_HEADER]).toBe('{"locale":"es-AR"}')
expect(incomingHeaders[ACCOUNT_HEADER]).toBe('mystore')

// Verify no undefined keys in any pattern
expect(TENANT_HEADER).not.toBe('undefined')
expect(BINDING_HEADER).not.toBe('undefined')
expect(SEGMENT_HEADER).not.toBe('undefined')
})
})
})
})
84 changes: 84 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,90 @@ export const AttributeKeys = {
VTEX_IO_APP_AUTHOR_TYPE: ATTR_VTEX_IO_APP_AUTHOR_TYPE,
}

/** @deprecated Use HeaderKeys.CACHE_CONTROL instead */
export const CACHE_CONTROL_HEADER = HeaderKeys.CACHE_CONTROL

/** @deprecated Use HeaderKeys.SEGMENT instead */
export const SEGMENT_HEADER = HeaderKeys.SEGMENT

/** @deprecated Use HeaderKeys.SESSION instead */
export const SESSION_HEADER = HeaderKeys.SESSION

/** @deprecated Use HeaderKeys.PRODUCT instead */
export const PRODUCT_HEADER = HeaderKeys.PRODUCT

/** @deprecated Use HeaderKeys.LOCALE instead */
export const LOCALE_HEADER = HeaderKeys.LOCALE

/** @deprecated Use HeaderKeys.FORWARDED_HOST instead */
export const FORWARDED_HOST_HEADER = HeaderKeys.FORWARDED_HOST

/** @deprecated Use HeaderKeys.TENANT instead */
export const TENANT_HEADER = HeaderKeys.TENANT

/** @deprecated Use HeaderKeys.BINDING instead */
export const BINDING_HEADER = HeaderKeys.BINDING

/** @deprecated Use HeaderKeys.META instead */
export const META_HEADER = HeaderKeys.META

/** @deprecated Use HeaderKeys.META_BUCKET instead */
export const META_HEADER_BUCKET = HeaderKeys.META_BUCKET

/** @deprecated Use HeaderKeys.ETAG instead */
export const ETAG_HEADER = HeaderKeys.ETAG

/** @deprecated Use HeaderKeys.ACCOUNT instead */
export const ACCOUNT_HEADER = HeaderKeys.ACCOUNT

/** @deprecated Use HeaderKeys.CREDENTIAL instead */
export const CREDENTIAL_HEADER = HeaderKeys.CREDENTIAL

/** @deprecated Use HeaderKeys.REQUEST_ID instead */
export const REQUEST_ID_HEADER = HeaderKeys.REQUEST_ID

/** @deprecated Use HeaderKeys.ROUTER_CACHE instead */
export const ROUTER_CACHE_HEADER = HeaderKeys.ROUTER_CACHE

/** @deprecated Use HeaderKeys.OPERATION_ID instead */
export const OPERATION_ID_HEADER = HeaderKeys.OPERATION_ID

/** @deprecated Use HeaderKeys.PLATFORM instead */
export const PLATFORM_HEADER = HeaderKeys.PLATFORM

/** @deprecated Use HeaderKeys.WORKSPACE_IS_PRODUCTION instead */
export const WORKSPACE_IS_PRODUCTION_HEADER = HeaderKeys.WORKSPACE_IS_PRODUCTION

/** @deprecated Use HeaderKeys.WORKSPACE instead */
export const WORKSPACE_HEADER = HeaderKeys.WORKSPACE

/** @deprecated Use HeaderKeys.EVENT_KEY instead */
export const EVENT_KEY_HEADER = HeaderKeys.EVENT_KEY

/** @deprecated Use HeaderKeys.EVENT_SENDER instead */
export const EVENT_SENDER_HEADER = HeaderKeys.EVENT_SENDER

/** @deprecated Use HeaderKeys.EVENT_SUBJECT instead */
export const EVENT_SUBJECT_HEADER = HeaderKeys.EVENT_SUBJECT

/** @deprecated Use HeaderKeys.EVENT_HANDLER_ID instead */
export const EVENT_HANDLER_ID_HEADER = HeaderKeys.EVENT_HANDLER_ID

/** @deprecated Use HeaderKeys.COLOSSUS_ROUTE_DECLARER instead */
export const COLOSSUS_ROUTE_DECLARER_HEADER = HeaderKeys.COLOSSUS_ROUTE_DECLARER

/** @deprecated Use HeaderKeys.COLOSSUS_ROUTE_ID instead */
export const COLOSSUS_ROUTE_ID_HEADER = HeaderKeys.COLOSSUS_ROUTE_ID

/** @deprecated Use HeaderKeys.COLOSSUS_PARAMS instead */
export const COLOSSUS_PARAMS_HEADER = HeaderKeys.COLOSSUS_PARAMS

/** @deprecated Use HeaderKeys.TRACE_ID instead */
export const TRACE_ID_HEADER = HeaderKeys.TRACE_ID

/** @deprecated Use HeaderKeys.PROVIDER instead */
export const PROVIDER_HEADER = HeaderKeys.PROVIDER

export type VaryHeaders = typeof HeaderKeys.SEGMENT | typeof HeaderKeys.SESSION | typeof HeaderKeys.PRODUCT | typeof HeaderKeys.LOCALE

export const BODY_HASH = '__graphqlBodyHash'
Expand Down
Loading