diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42ff68521..f28d7dcb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,6 @@ jobs: - run: yarn run lint test: - needs: [lint] runs-on: ${{ matrix.os }} strategy: fail-fast: true diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b030f81..9e5a8727f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package.json b/package.json index 3c6124abb..e16f675ab 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/constants.test.ts b/src/constants.test.ts index 522acfb10..c4bc3096c 100644 --- a/src/constants.test.ts +++ b/src/constants.test.ts @@ -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', () => { @@ -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 = {} + 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 = { + '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') + }) + }) }) }) diff --git a/src/constants.ts b/src/constants.ts index 38f4c7630..cab1e5ba8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -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'