[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/o11y/extractExtra.test.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { extractExtra } from './extractExtra'

describe('extractExtra', () => {
  test('should return an empty object when err is an empty object', () => {
    const err = {}
    const result = extractExtra(err)
    expect(result).toEqual({})
  })

  test('should extract simple key-value pairs', () => {
    const err = { a: 'value1', b: 'value2' }
    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_a0: 'value1',
      admin_extra_b0: 'value2',
    })
  })

  test('should extract nested objects', () => {
    const err = {
      a: 'value1',
      nested: {
        b: 'value2',
        deeper: {
          c: 'value3',
        },
      },
    }
    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_a0: 'value1',
      admin_extra_b1: 'value2',
      admin_extra_c2: 'value3',
    })
  })

  test('should ignore functions', () => {
    const err = {
      a: 'value1',
      func: () => {},
    }
    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_a0: 'value1',
    })
  })

  test('high fidelity example #1', () => {
    const err = {
      details: {
        graphQLErrors: '[Array]',
        message: 'messages',
        name: 'Error',
        stack:
          'Error: messages\n    at throwOnGraphQLErrors (/usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:10:15)\n    at /usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:23:15\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)\n    at MessagesGraphQL.translateWithDependencies (/usr/local/app/node_modules/@vtex/api/lib/clients/apps/MessagesGraphQL.js:66:26)\n    at dataloader_1.default.batch (/usr/local/app/node_modules/@vtex/api/lib/service/worker/runtime/graphql/schema/messagesLoaderV2.js:68:15)',
      },
      stack:
        'Error: messages\n    at throwOnGraphQLErrors (/usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:10:15)\n    at /usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:23:15\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)\n    at MessagesGraphQL.translateWithDependencies (/usr/local/app/node_modules/@vtex/api/lib/clients/apps/MessagesGraphQL.js:66:26)\n    at dataloader_1.default.batch (/usr/local/app/node_modules/@vtex/api/lib/service/worker/runtime/graphql/schema/messagesLoaderV2.js:68:15)',
    }

    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_gqlerrors1: '[Array]',
      admin_extra_msg1: 'messages',
      admin_extra_name1: 'Error',
      admin_extra_stack0_0:
        'Error: messages\n    at throwOnGraphQLErrors (/usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:10:15)\n    at /usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:',
      admin_extra_stack0_1:
        '23:15\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)\n    at MessagesGraphQL.translateWithDependencies (/usr/local/app/node_modules/@vtex/',
      admin_extra_stack0_2:
        'api/lib/clients/apps/MessagesGraphQL.js:66:26)\n    at dataloader_1.default.batch (/usr/local/app/node_modules/@vtex/api/lib/service/worker/runtime/graphql/schema/messagesLoaderV2.js:68:15)',
      admin_extra_stack1_0:
        'Error: messages\n    at throwOnGraphQLErrors (/usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:10:15)\n    at /usr/local/app/node_modules/@vtex/api/lib/HttpClient/GraphQLClient.js:',
      admin_extra_stack1_1:
        '23:15\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)\n    at MessagesGraphQL.translateWithDependencies (/usr/local/app/node_modules/@vtex/',
      admin_extra_stack1_2:
        'api/lib/clients/apps/MessagesGraphQL.js:66:26)\n    at dataloader_1.default.batch (/usr/local/app/node_modules/@vtex/api/lib/service/worker/runtime/graphql/schema/messagesLoaderV2.js:68:15)',
    })
  })

  test('high fidelity example #2', () => {
    const err = {
      details: {
        code: 'ECONNABORTED',
        config: '[Object]',
        isAxiosError: true,
        message: 'timeout of 4000ms exceeded',
        name: 'Error',
        request: '[Object]',
        stack:
          'Error: timeout of 4000ms exceeded\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at ClientRequest.handleRequestTimeout (/usr/local/app/node_modules/axios/lib/adapters/http.js:280:16)\n    at Object.onceWrapper (node:events:627:28)\n    at ClientRequest.emit (node:events:513:28)\n    at Socket.emitRequestTimeout (node:_http_client:839:9)\n    at Object.onceWrapper (node:events:627:28)\n    at Socket.emit (node:events:525:35)\n    at Socket._onTimeout (node:net:550:8)\n    at listOnTimeout (node:internal/timers:559:17)\n    at processTimers (node:internal/timers:502:7)',
      },
      stack:
        'Error: timeout of 4000ms exceeded\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at ClientRequest.handleRequestTimeout (/usr/local/app/node_modules/axios/lib/adapters/http.js:280:16)\n    at Object.onceWrapper (node:events:627:28)\n    at ClientRequest.emit (node:events:513:28)\n    at Socket.emitRequestTimeout (node:_http_client:839:9)\n    at Object.onceWrapper (node:events:627:28)\n    at Socket.emit (node:events:525:35)\n    at Socket._onTimeout (node:net:550:8)\n    at listOnTimeout (node:internal/timers:559:17)\n    at processTimers (node:internal/timers:502:7)',
    }
    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_code1: 'ECONNABORTED',
      admin_extra_axios1: true,
      admin_extra_msg1: 'timeout of 4000ms exceeded',
      admin_extra_name1: 'Error',
      admin_extra_req1: '[Object]',
      admin_extra_config1: '[Object]',
      admin_extra_stack0_0:
        'Error: timeout of 4000ms exceeded\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at ClientRequest.handleRequestTimeout (/usr/local/app/node_modules/axios/lib',
      admin_extra_stack0_1:
        '/adapters/http.js:280:16)\n    at Object.onceWrapper (node:events:627:28)\n    at ClientRequest.emit (node:events:513:28)\n    at Socket.emitRequestTimeout (node:_http_client:839:9)\n    at Object.onceWr',
      admin_extra_stack0_2:
        'apper (node:events:627:28)\n    at Socket.emit (node:events:525:35)\n    at Socket._onTimeout (node:net:550:8)\n    at listOnTimeout (node:internal/timers:559:17)\n    at processTimers (node:internal/tim',
      admin_extra_stack0_3: 'ers:502:7)',
      admin_extra_stack1_0:
        'Error: timeout of 4000ms exceeded\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at ClientRequest.handleRequestTimeout (/usr/local/app/node_modules/axios/lib',
      admin_extra_stack1_1:
        '/adapters/http.js:280:16)\n    at Object.onceWrapper (node:events:627:28)\n    at ClientRequest.emit (node:events:513:28)\n    at Socket.emitRequestTimeout (node:_http_client:839:9)\n    at Object.onceWr',
      admin_extra_stack1_2:
        'apper (node:events:627:28)\n    at Socket.emit (node:events:525:35)\n    at Socket._onTimeout (node:net:550:8)\n    at listOnTimeout (node:internal/timers:559:17)\n    at processTimers (node:internal/tim',
      admin_extra_stack1_3: 'ers:502:7)',
    })
  })

  test('high fidelity example #3', () => {
    const err = {
      details: {
        code: 'E_HTTP_500',
        config: '[Object]',
        isAxiosError: true,
        message: 'Request failed with status code 500',
        name: 'Error',
        request: '[Object]',
        response: '[Object]',
        stack:
          'Error: Request failed with status code 500\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at settle (/usr/local/app/node_modules/axios/lib/core/settle.js:17:12)\n    at IncomingMessage.handleStreamEnd (/usr/local/app/node_modules/axios/lib/adapters/http.js:260:11)\n    at IncomingMessage.emit (node:events:525:35)\n    at endReadableNT (node:internal/streams/readable:1358:12)\n    at processTicksAndRejections (node:internal/process/task_queues:83:21)',
      },
      stack:
        'Error: Request failed with status code 500\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at settle (/usr/local/app/node_modules/axios/lib/core/settle.js:17:12)\n    at IncomingMessage.handleStreamEnd (/usr/local/app/node_modules/axios/lib/adapters/http.js:260:11)\n    at IncomingMessage.emit (node:events:525:35)\n    at endReadableNT (node:internal/streams/readable:1358:12)\n    at processTicksAndRejections (node:internal/process/task_queues:83:21)',
    }

    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_code1: 'E_HTTP_500',
      admin_extra_axios1: true,
      admin_extra_msg1: 'Request failed with status code 500',
      admin_extra_name1: 'Error',
      admin_extra_req1: '[Object]',
      admin_extra_res1: '[Object]',
      admin_extra_config1: '[Object]',
      admin_extra_stack0_0:
        'Error: Request failed with status code 500\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at settle (/usr/local/app/node_modules/axios/lib/core/settle.js:17:',
      admin_extra_stack0_1:
        '12)\n    at IncomingMessage.handleStreamEnd (/usr/local/app/node_modules/axios/lib/adapters/http.js:260:11)\n    at IncomingMessage.emit (node:events:525:35)\n    at endReadableNT (node:internal/streams',
      admin_extra_stack0_2:
        '/readable:1358:12)\n    at processTicksAndRejections (node:internal/process/task_queues:83:21)',

      admin_extra_stack1_0:
        'Error: Request failed with status code 500\n    at createError (/usr/local/app/node_modules/axios/lib/core/createError.js:16:15)\n    at settle (/usr/local/app/node_modules/axios/lib/core/settle.js:17:',
      admin_extra_stack1_1:
        '12)\n    at IncomingMessage.handleStreamEnd (/usr/local/app/node_modules/axios/lib/adapters/http.js:260:11)\n    at IncomingMessage.emit (node:events:525:35)\n    at endReadableNT (node:internal/streams',
      admin_extra_stack1_2:
        '/readable:1358:12)\n    at processTicksAndRejections (node:internal/process/task_queues:83:21)',
    })
  })

  test('should return the original input when it is not an object (string)', () => {
    const err = 'error'
    const result = extractExtra(err)
    expect(result).toEqual(err)
  })

  test('should return the original input when it is not an object (boolean)', () => {
    const err = false
    const result = extractExtra(err)
    expect(result).toEqual(err)
  })

  test('should return the original input when it is not an object (undefined)', () => {
    const result = extractExtra(undefined)
    expect(result).toEqual(undefined)
  })

  test('circular references given as input returns an empty object', () => {
    class Validator {
      model: any

      constructor(model: any) {
        this.model = model
      }
    }

    class Model {
      _attributes: any
      validator: any

      constructor() {
        this._attributes = {}
        this.validator = new Validator(this)
      }
    }

    const model = new Model()

    const result = extractExtra(model)
    expect(result).toEqual({})
  })

  test('should not grab 32+ character key-value pairs', () => {
    const err = {
      superlongkeythathasmorethanthirtytwocharacters: 'value1',
      b: 'value2',
      nested: {
        c: 'value3',
        anothersuperlongkeythathasmorethanthirtytwocharacters: 'value4',
      },
    }
    const result = extractExtra(err)
    expect(result).toEqual({
      admin_extra_b0: 'value2',
      admin_extra_c1: 'value3',
    })
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/o11y/types.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
/**
 * Custom Admin tags logged by the Sentry SDK.
 */
export type CustomAdminTags = Record<string, any> & {
  /**
   * The Render Runtime page component that rendered an error.
   */
  admin_render_runtime_page: 'ErrorPage' | 'ErrorBoundary'
}

/**
 * Default Admin tags logged by the Sentry SDK.
 */
export interface AdminTags {
  /**
   * Account running the app.
   */
  admin_account: string
  /**
   * Workspace running the app.
   */
  admin_workspace: string
  /**
   * Locale running the app.
   */
  admin_locale: string
  /**
   * Path of the app.
   */
  admin_path: string
  /**
   * Block ID of the app.
   *
   * @example
   * "vtex.admin-home@3.x:admin.app.home"
   */
  admin_app_block: string
  /**
   * Device running the app.
   *
   * @example
   * ["desktop", "tablet"]
   */
  admin_device: string
  /**
   * Whether the app is running in production.
   * The values from this variable logged to
   * Sentry are always true OR null; never false.
   */
  admin_production: boolean
  /**
   * Whether the runtime information from the
   * global __RUNTIME__ object is available or not.
   */
  admin_runtime_available: boolean
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/__tests__/LRUCache.test.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import LRU from '../components/Prefetch/LRUCache'

const sleep = (time: number) =>
  new Promise((resolve) => setTimeout(resolve, time))

test('get and set works', () => {
  const lru = new LRU({ max: 1 })
  const a = 1
  lru.set('a', a)
  expect(lru.get('a')).toBe(a)
})

test('max options works', () => {
  const lru = new LRU({ max: 2 })
  const a = 1
  const b = 2
  lru.set('a', a)
  lru.set('b', b)
  expect(lru.get('a')).toBe(a)
  const c = 3
  lru.set('c', c)
  expect(lru.get('a')).toBe(a)
  expect(lru.get('c')).toBe(c)
  expect(lru.get('b')).toBe(undefined)
})

test('maxSize options works', () => {
  const lru = new LRU({ maxSize: 2 })
  const a = 1
  const b = 2
  lru.set('a', a)
  lru.set('b', b)
  expect(lru.get('a')).toBe(a)
  const c = 3
  lru.set('c', c)
  expect(lru.get('a')).toBe(a)
  expect(lru.get('c')).toBe(c)
  expect(lru.get('b')).toBe(undefined)
})

test('will remove the key in tail', () => {
  const lru = new LRU({ max: 3 })
  const a = 1
  const b = 2
  const c = 3
  const d = 4
  lru.set('a', a)
  lru.set('b', b)
  lru.set('c', c)
  lru.get('a')
  lru.get('b')
  lru.get('a')
  lru.get('c')
  lru.get('a')
  // cache is now a - c - b, b in tail

  lru.set('d', d)

  expect(lru.get('a')).toBe(a)
  expect(lru.get('c')).toBe(c)
  expect(lru.get('d')).toBe(d)
  expect(lru.get('b')).toBe(undefined)
})

test('dispose function is called when key is removed', () => {
  const dispose = jest.fn()
  const lru = new LRU({ max: 2, disposeFn: dispose })
  const a = 1
  const b = 2
  lru.set('a', a)
  lru.set('b', b)
  expect(lru.get('a')).toBe(a)
  const c = 3
  lru.set('c', c)

  expect(dispose).toBeCalledTimes(1)
  expect(dispose).toBeCalledWith('b')
  dispose.mockClear()
  lru.get('c')
  lru.get('c')
  lru.set('d', 4)
  expect(dispose).toBeCalledTimes(1)
  expect(dispose).toBeCalledWith('a')
})

test('max age parameter works', async () => {
  const dispose = jest.fn()
  const lru = new LRU({ max: 3, maxAge: 500, disposeFn: dispose })
  const a = 1
  const b = 2
  const c = 3
  lru.set('a', a)
  lru.set('b', b)

  await sleep(200)
  expect(lru.get('a')).toBe(a)
  expect(lru.get('b')).toBe(b)
  lru.set('c', c)

  await sleep(400)
  expect(lru.get('a')).toBe(undefined)
  expect(lru.get('b')).toBe(undefined)
  expect(lru.get('c')).toBe(c)

  expect(dispose).toBeCalledTimes(2)
  expect(dispose).toHaveBeenNthCalledWith(1, 'a')
  expect(dispose).toHaveBeenNthCalledWith(2, 'b')
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/__tests__/PQueue.test.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import PQueue from '../components/Prefetch/PQueue'

const sleep = (time: number) =>
  new Promise((resolve) => setTimeout(resolve, time))

beforeEach(() => {
  jest.resetAllMocks()
})
it('should start all promises', async () => {
  const mockFn = jest.fn()
  const myPromiseFn = () =>
    new Promise(async (resolve) => {
      await sleep(100)
      mockFn()
      resolve()
    })

  const queue = new PQueue({})
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)

  await sleep(500)
  expect(mockFn).toBeCalledTimes(3)
})

it('should respect the maximum concurrency', async () => {
  const mockFn = jest.fn()
  const myPromiseFn = () =>
    new Promise(async (resolve) => {
      await sleep(500)
      mockFn()
      resolve()
    })

  const queue = new PQueue({ concurrency: 2 })
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)

  await sleep(700)
  expect(mockFn).toBeCalledTimes(2)

  await sleep(400)
  expect(mockFn).toBeCalledTimes(4)
})

it('should add with priority', async () => {
  const counters = [0, 0, 0, 0, 0]
  const myPromiseFn = (count: number) =>
    new Promise(async (resolve) => {
      await sleep(300)
      counters[count]++
      resolve()
    })

  const queue = new PQueue({ concurrency: 2, autoStart: false })
  queue.add(() => myPromiseFn(0))
  queue.add(() => myPromiseFn(1), { priority: 1 })
  queue.add(() => myPromiseFn(2), { priority: 2 })
  queue.add(() => myPromiseFn(3), { priority: 3 })

  queue.start()
  await sleep(100)

  queue.add(() => myPromiseFn(4), { priority: 4 })
  queue.add(() => myPromiseFn(3), { priority: 3 })
  queue.add(() => myPromiseFn(0), { priority: 0 })
  await sleep(250)

  // Should have resolved the two promises with high priorities
  expect(counters).toStrictEqual([0, 0, 1, 1, 0])
  await sleep(300)
  // Should have resolved the two promises with higher priorities at the moment (4 and 3)
  expect(counters).toStrictEqual([0, 0, 1, 2, 1])

  await sleep(600)
  // All of them should have resolved
  expect(counters).toStrictEqual([2, 1, 1, 2, 1])
})

it('should respect autoStart false argument', async () => {
  const mockFn = jest.fn()
  const mockPriorityFn = jest.fn()
  const myPromiseFn = () =>
    new Promise(async (resolve) => {
      await sleep(100)
      mockFn()
      resolve()
    })

  const myPromisePriorityFn = () =>
    new Promise(async (resolve) => {
      await sleep(100)
      mockPriorityFn()
      resolve()
    })

  const queue = new PQueue({ autoStart: false })

  queue.add(myPromiseFn)
  queue.add(myPromiseFn)
  queue.add(myPromisePriorityFn, { priority: 1 })
  queue.add(myPromiseFn)
  queue.add(myPromiseFn)
  await sleep(200)
  expect(mockFn).toBeCalledTimes(0)
  expect(mockPriorityFn).toBeCalledTimes(0)

  queue.start()
  await sleep(800)
  expect(mockFn).toBeCalledTimes(4)
  expect(mockPriorityFn).toBeCalledTimes(1)
})

it('pause method should freeze queue', async () => {
  const mockFn = jest.fn()
  const myPromiseFn = () =>
    new Promise(async (resolve) => {
      await sleep(200)
      mockFn()
      resolve()
    })

  const queue = new PQueue({})

  queue.add(myPromiseFn)
  queue.add(myPromiseFn)

  queue.pause()

  queue.add(myPromiseFn)
  queue.add(myPromiseFn)

  await sleep(300)
  expect(mockFn).toBeCalledTimes(2)

  queue.start()
  await sleep(300)
  expect(mockFn).toBeCalledTimes(4)
})

it('clear method should empty the queue', async () => {
  const mockBeforeFn = jest.fn()
  const mockAfterFn = jest.fn()
  const myPromiseBeforeFn = () =>
    new Promise(async (resolve) => {
      await sleep(200)
      mockBeforeFn()
      resolve()
    })

  const myPromiseAfterFn = () =>
    new Promise(async (resolve) => {
      await sleep(200)
      mockAfterFn()
      resolve()
    })

  const queue = new PQueue({ autoStart: false })

  queue.add(myPromiseBeforeFn)
  queue.add(myPromiseBeforeFn)

  queue.clear()

  queue.add(myPromiseAfterFn)
  queue.add(myPromiseAfterFn)
  queue.start()

  await sleep(300)
  expect(mockBeforeFn).toBeCalledTimes(0)
  expect(mockAfterFn).toBeCalledTimes(2)
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/__tests__/RenderProvider.test.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useEffect } from 'react'
import RenderProvider from '../components/RenderProvider'
import { useRuntime } from '../components/RenderContext'
import { render, wait } from '@vtex/test-tools/react'

const renderFakeProvider = ({ children }: { children: JSX.Element }) => {
  window.fetch = () => Promise.resolve({} as Response)
  window.__RENDER_8_SESSION__ = {
    sessionPromise: Promise.resolve(),
    patchSession: () => Promise.resolve(),
  }

  const props = {
    runtime: {
      account: 'vtex',
      amp: false,
      emitter: {
        addListener: () => {},
        removeListener: () => {},
      },
      production: false,
      route: {
        canonicalPath: '',
      },
      culture: {
        locale: 'en-US',
      },
      components: {},
      blocks: {},
      pages: {
        page: {
          path: 'page/:id',
        },
      },
      hints: {
        mobile: false,
        desktop: true,
        tablet: false,
        phone: false,
        unknown: false,
      },
    },
    history: {
      location: {
        search: '',
        pathname: '',
      },
      replace: () => {},
      push: () => {},
      listen: () => {},
    },
  }
  return render(<RenderProvider {...(props as any)}>{children}</RenderProvider>)
}

it('if child component calls navigate, should return true', async () => {
  let navigateResult = false
  const Child = () => {
    const { navigate } = useRuntime()
    useEffect(() => {
      navigateResult = navigate({ to: 'product' })
    }, [navigate])
    return <div>children</div>
  }
  renderFakeProvider({ children: <Child /> })
  await wait()
  expect(navigateResult).toBe(true)
})

it('Calling navigates one after another with same payload, will make second one return false', async () => {
  let navigateResultFirst = undefined
  let navigateResultSecond = undefined
  const Child = () => {
    const { navigate } = useRuntime()
    useEffect(() => {
      navigateResultFirst = navigate({ to: 'product' })
      navigateResultSecond = navigate({ to: 'product' })
    }, [navigate])
    return <div>children</div>
  }
  renderFakeProvider({ children: <Child /> })
  await wait()
  expect(navigateResultFirst).toBe(true)
  expect(navigateResultSecond).toBe(false)
})

it('Calling navigates one after another with different payload, will make second one return true also', async () => {
  let navigateResultFirst = undefined
  let navigateResultSecond = undefined
  const Child = () => {
    const { navigate } = useRuntime()
    useEffect(() => {
      navigateResultFirst = navigate({ to: 'product' })
      navigateResultSecond = navigate({ to: 'other-product' })
    }, [navigate])
    return <div>children</div>
  }
  renderFakeProvider({ children: <Child /> })
  await wait()
  expect(navigateResultFirst).toBe(true)
  expect(navigateResultSecond).toBe(true)
})

const input: [any, any, any][] = [
  [
    { to: 'product', params: { id: 'a' } },
    { to: 'product', params: { id: 'a' } },
    false,
  ],
  [
    { to: 'product', params: { id: 'a' } },
    { to: 'product2', params: { id: 'a' } },
    true,
  ],
  [
    { to: 'product', params: { id: 'a' } },
    { to: 'product', params: { id: 'a', rest: 'b' } },
    true,
  ],
  [
    { page: 'page', params: { id: 'a' } },
    { page: 'page', params: { id: 'a' } },
    false,
  ],
  [
    { page: 'page', params: { id: 'a' } },
    { page: 'page', params: { id: 'b' } },
    true,
  ],
]

it.each(input)(
  'test if navigation is properly exiting early based on different repeated input',
  async (arg1: any, arg2: any, expected: boolean) => {
    let navigateResultFirst = undefined
    let navigateResultSecond = undefined
    const Child = () => {
      const { navigate } = useRuntime()
      useEffect(() => {
        navigateResultFirst = navigate(arg1)
        navigateResultSecond = navigate(arg2)
      }, [navigate])
      return <div>children</div>
    }
    renderFakeProvider({ children: <Child /> })
    await wait()
    expect(navigateResultFirst).toBe(true)
    expect(navigateResultSecond).toBe(expected)
  }
)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/hooks/viewDetection.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { useEffect, useRef, MutableRefObject } from 'react'

interface IntersectionEvent {
  entry: IntersectionObserverEntry
  unobserve: () => void
}

interface HookOptions {
  ref: MutableRefObject<HTMLElement | null>
  onView?: (event: IntersectionEvent) => any
  threshold?: number
  once?: boolean
  bailOut?: boolean
  initializeOnInteraction?: boolean
}

/** Hook for detecting when an element is inside the viewport.
 * Differs from react-intersection-observer (https://www.npmjs.com/package/react-intersection-observer)
 * in that this hook doesn't use setState, using a callback approach instead, to improve
 * performance by preventing re-rendering.
 */
const useOnView = ({
  ref,
  onView,
  threshold = 0,
  once = false,
  bailOut = false,
  initializeOnInteraction = false,
}: HookOptions) => {
  const isObserving = useRef(false)
  const didIntersect = useRef(false)

  useEffect(() => {
    const initializeObserver = () => {
      const element = ref.current

      if (
        bailOut ||
        isObserving.current ||
        !element ||
        !onView ||
        (once && didIntersect.current)
      ) {
        return () => {}
      }

      isObserving.current = true

      const unobserve = () => {
        if (isObserving.current) {
          observer.unobserve(element)
          isObserving.current = false
        }
      }

      const observer = new IntersectionObserver(([entry]) => {
        if (!entry.isIntersecting) {
          return
        }

        if (entry.intersectionRatio < threshold) {
          return
        }

        if (once) {
          unobserve()
        }

        didIntersect.current = true
        onView({ entry, unobserve })
      })

      observer.observe(element)

      return unobserve
    }

    if (initializeOnInteraction) {
      const cleanUpEvents = () => {
        window?.document?.removeEventListener('scroll', handleInteraction)
        window?.document?.removeEventListener('mouseover', handleInteraction)
      }

      let unobserve = () => {
        cleanUpEvents()
      }

      const handleInteraction = () => {
        cleanUpEvents()
        unobserve = initializeObserver()
      }

      window?.document?.addEventListener('scroll', handleInteraction)
      window?.document?.addEventListener('mouseover', handleInteraction)

      /** Triggers interaction event if the user has scrolled the
       * page before JS being initialized */
      if (window?.scrollY > 0) {
        handleInteraction()
      }

      return unobserve
    }

    return initializeObserver()
  }, [bailOut, initializeOnInteraction, onView, once, ref, threshold])
}

export { useOnView }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/hooks/prefetch.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  PrefetchState,
  usePrefetch,
  getCacheForPage,
} from '../components/Prefetch/PrefetchContext'
import {
  queryStringToMap,
  NavigateOptions,
  getNavigationRouteToNavigate,
} from '../utils/pages'
import {
  getPrefetchForPath,
  fetchRouteData,
  isPrefetchActive,
} from '../utils/routes'
import { fetchComponents } from '../utils/components'
import { useRuntime } from '../components/RenderContext'
import { useApolloClient } from 'react-apollo'
import { useRef, useEffect, useCallback, MutableRefObject } from 'react'
import { useOnView } from './viewDetection'
import { ApolloClientType } from '../typings/global'
import { RenderRuntime } from '../typings/runtime'

interface PrefetchRequestsArgs {
  canonicalBaseAddress?: string
  client: ApolloClientType
  navigationRoute: any
  page?: string
  pages: RenderRuntime['pages']
  prefetchState: PrefetchState
  hints: RenderRuntime['hints']
  renderMajor: RenderRuntime['renderMajor']
  validCache: {
    pathValid: boolean
    routeValid: boolean
  }
  storeSettings: Record<string, any>
}

const getPageToNavigate = (
  path: string,
  query: string,
  canonicalBaseAddress?: string
) => {
  return getPrefetchForPath({
    path: path,
    fetcher: fetch,
    query: {
      ...queryStringToMap(query),
      ...(canonicalBaseAddress && { __bindingAddress: canonicalBaseAddress }),
    },
  })
}

interface MaybeUpdatePathArgs {
  canonicalBaseAddress?: string
  prefetchState: PrefetchState
  navigationRoute: any
  validCache: {
    pathValid: boolean
    routeValid: boolean
  }
  page?: string
}

const maybeUpdatePathCache = async ({
  canonicalBaseAddress,
  prefetchState,
  navigationRoute,
  validCache,
  page,
}: MaybeUpdatePathArgs) => {
  const { pathsState } = prefetchState
  if (validCache.pathValid) {
    return pathsState[navigationRoute.path].page as string
  }

  const navigationData = await getPageToNavigate(
    navigationRoute.path,
    navigationRoute.query,
    canonicalBaseAddress
  )

  const navigationPage = page ?? navigationData?.page
  pathsState[navigationRoute.path] = { fetching: false, page: navigationPage }
  if (navigationData == null || navigationPage == null) {
    return null
  }

  const cacheObj = {
    routeId: navigationPage,
    matchingPage: navigationData.route,
    contentResponse: navigationData.contentResponse,
    queryData: navigationData.queryData,
  }

  const cache = getCacheForPage(navigationPage)

  cache.set(navigationRoute.path, cacheObj)
  return navigationPage
}

const prefetchRequests = async ({
  canonicalBaseAddress,
  client,
  navigationRoute,
  page,
  pages,
  prefetchState,
  hints,
  renderMajor,
  validCache,
  storeSettings,
}: PrefetchRequestsArgs) => {
  const { pathsState, routesCache, routePromise } = prefetchState
  if (!isPrefetchActive(storeSettings)) {
    return
  }

  const navigationPage = await maybeUpdatePathCache({
    canonicalBaseAddress,
    prefetchState,
    navigationRoute,
    validCache,
    page,
  })

  pathsState[navigationRoute.path] = { fetching: false, page: navigationPage }
  if (navigationPage == null) {
    return
  }

  const declarer = pages[navigationPage]?.declarer
  if (typeof declarer === 'undefined') {
    // Should not happen, but lets be safe
    return
  }

  let routeDataCache = routesCache.get(navigationPage)
  const routePromiseValue = routePromise[navigationPage]

  if (!routeDataCache && !routePromiseValue) {
    const newState = {
      promisePending: true,
      promise: fetchRouteData({
        apolloClient: client,
        routeId: navigationPage,
        declarer,
        hints,
        renderMajor,
      }),
    }
    newState.promise
      .then((data) => {
        routesCache.set(navigationPage, data)
      })
      .finally(() => {
        newState.promisePending = false
        newState.promise = null as any
        routePromise[navigationPage] = null
      })

    routePromise[navigationPage] = newState
  }
  const globalRouteDataState = routePromise[navigationPage]
  if (globalRouteDataState?.promisePending) {
    await globalRouteDataState.promise
    routeDataCache = routesCache.get(navigationPage)
  }

  if (!routeDataCache) {
    return
  }

  await fetchComponents(
    routeDataCache.components,
    window.__RUNTIME__,
    routeDataCache.extensions
  )
}

interface UsePrefetchArgs {
  ref: MutableRefObject<HTMLElement | null>
  page?: string
  href: string
  options: NavigateOptions
  waitToPrefetch?: number
}

// Returns an object that for a given path, tells us if the routes or path cache has valid data for the given input.
// We do this to know when to hit the server. Will return false (as invalid data) if cached data has expired its max age.
const getCacheValidData = (path: string, prefecthState: PrefetchState) => {
  const { pathsState, routesCache } = prefecthState
  const page = pathsState[path]?.page

  if (!page) {
    return {
      pathValid: false,
      routeValid: false,
    }
  }

  const cache = getCacheForPage(page)
  const validData = cache.has(path)
  const validRoutesData = routesCache.has(page)
  return {
    pathValid: validData,
    routeValid: validRoutesData,
  }
}

const getPriorityForPage = (page: string | undefined) =>
  page === 'store.product' ? 1 : 0

export const useIsPrefetchActive = () => {
  const { getSettings } = useRuntime()
  const storeSettings = getSettings('vtex.store')
  return isPrefetchActive(storeSettings)
}

export const usePrefetchAttempt = ({
  ref,
  page,
  href,
  options,
  waitToPrefetch,
}: UsePrefetchArgs) => {
  const runtime = useRuntime()
  const prefetchState = usePrefetch()
  const client = useApolloClient() as ApolloClientType
  const hasTried = useRef(false)
  const canPrefetch = useRef(!waitToPrefetch || waitToPrefetch === 0)

  useEffect(() => {
    if (!canPrefetch.current) {
      setTimeout(() => {
        canPrefetch.current = true
      }, waitToPrefetch)
    }
  }, [canPrefetch, waitToPrefetch])

  const {
    pages,
    navigationRouteModifiers,
    hints,
    query,
    renderMajor,
    getSettings,
  } = runtime

  const canonicalBaseAddress = query?.__bindingAddress
  const storeSettings = getSettings('vtex.store')

  const attemptPrefetch = useCallback(() => {
    if (
      hasTried.current ||
      !isPrefetchActive(storeSettings) ||
      !canPrefetch.current
    ) {
      return
    }

    const { pathsState, queue } = prefetchState
    if (href && href[0] !== '/') {
      // Should only work on relative paths
      return
    }

    options.modifiers = navigationRouteModifiers
    const navigationRoute = getNavigationRouteToNavigate(pages, options)
    navigationRoute.original = options.to

    if (pathsState[navigationRoute.path]?.fetching) {
      // already fetching, no need to go any further
      hasTried.current = true
      return
    }

    const validCache = getCacheValidData(navigationRoute.path, prefetchState)

    if (validCache.pathValid && validCache.routeValid) {
      // cache all valid, no need to fetch anything
      hasTried.current = true
      return
    }

    if (!validCache.pathValid) {
      pathsState[navigationRoute.path] = { fetching: true }
    }

    const priority = getPriorityForPage(page)
    hasTried.current = true

    queue.add(
      async () =>
        prefetchRequests({
          canonicalBaseAddress,
          client,
          navigationRoute,
          page,
          pages,
          prefetchState,
          hints,
          renderMajor,
          validCache,
          storeSettings,
        }),
      { priority }
    )
  }, [
    canonicalBaseAddress,
    client,
    hints,
    href,
    navigationRouteModifiers,
    options,
    page,
    pages,
    prefetchState,
    renderMajor,
    storeSettings,
  ])

  useOnView({
    ref,
    onView: ({ unobserve }) => {
      /** Uses a setTimeout to prevent creating huge tasks,
       * if prefetchs accumulate.
       * Google "Total Blocking Time" for more info.
       */
      setTimeout(() => {
        if (!canPrefetch.current) {
          /** If canPrefetch is false, might be able to retry
           * later, so doesn't "unobserve" yet, just return */
          return
        }

        attemptPrefetch()
        unobserve()
      }, 1)
    },
    bailOut: !hints.mobile,
    initializeOnInteraction: true,
    threshold: 0.75,
  })

  const executePrefetch = useCallback(() => {
    if (hints.desktop) {
      attemptPrefetch()
    }
  }, [attemptPrefetch, hints.desktop])

  return executePrefetch
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/hooks/hydration.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { useState } from 'react'

/** Gets HTML content from elements rendered on the server, before being
 * hydrated on the client (and whether they have been SSRendered at all). */
export const useDehydratedContent = (hydrationId: string) => {
  /** On both cases below, the use of useState is to guarantee that only the values
   * that it has been initialized with are being considered, to guarantee that it's
   * only taking into account the HTML that's coming from the server, instead of
   * eventual elements that might be inserted later.
   */
  const [dehydratedElement] = useState(() =>
    window?.document?.querySelector?.(`[data-hydration-id="${hydrationId}"]`)
  )

  const [hasDehydratedContent] = useState(
    !!dehydratedElement && dehydratedElement.childElementCount > 0
  )

  return {
    hasRenderedOnServer: !!dehydratedElement,
    hasDehydratedContent,
    dehydratedElement,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/hooks/extension.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { ReactElement } from 'react'

import { useRuntime } from '../components/RenderContext'
import { useTreePath } from '../utils/treePath'
import { Extension } from '../typings/runtime'

function mountTreePath(base: string, children: string[]) {
  return [base, ...children].filter((id) => !!id).join('/')
}

interface Options {
  children?: string[] | string
}

const useExtension = ({ children }: Options = {}): Extension | null => {
  const { extensions } = useRuntime()

  const { treePath: baseTreePath } = useTreePath()

  const treePath = children
    ? mountTreePath(
        baseTreePath,
        Array.isArray(children) ? children : [children]
      )
    : baseTreePath

  const extension = treePath && extensions[treePath]

  return extension || null
}

interface ExtensionContext {
  extension?: Extension | null
}

interface Props {
  children({ extension }: ExtensionContext): ReactElement<any> | null
}

const ExtensionConsumer = ({ children }: Props) => {
  const extension = useExtension()

  return children({ extension })
}

export { useExtension, ExtensionConsumer }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/typings/react-dom/experimental.d.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import ReactDOM from 'react-dom'

// stripped from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/7ec01838c4ba1066b3c456492473dcaa44ddf5cd/types/react-dom/experimental.d.ts
declare module 'react-dom' {
  export as namespace ReactDOM

  interface HydrationOptions {
    onHydrated?(suspenseInstance: Comment): void
    onDeleted?(suspenseInstance: Comment): void
  }

  interface RootOptions {
    hydrate?: boolean
    hydrationOptions?: HydrationOptions
  }

  interface Root {
    render(children: React.ReactChild | React.ReactNodeArray): void
    unmount(callback?: () => void): void
  }

  function unstable_createRoot(
    container: Element | Document | DocumentFragment | Comment,
    options?: RootOptions
  ): Root
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/typings/@formatjs/intl-pluralrules.d.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module '@formatjs/intl-pluralrules' {
  export const PluralRules: any
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/generateHash.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  ArgumentNode,
  BREAK,
  DocumentNode,
  StringValueNode,
  visit,
} from 'graphql'

interface HashedDocumentNode extends DocumentNode {
  documentId: string
}

const isHashedDocumentNode = (arg: any): arg is HashedDocumentNode => {
  return arg && arg.documentId
}

export const generateHash = (query: HashedDocumentNode | DocumentNode) => {
  if (isHashedDocumentNode(query)) {
    return query.documentId
  }

  const asset = { hash: '' }
  visit(query, {
    Argument(node: ArgumentNode) {
      if (node.name.value === 'hash') {
        asset.hash = (node.value as StringValueNode).value
        return BREAK
      }
    },
  })
  return asset.hash
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/index.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  InMemoryCache,
  IntrospectionFragmentMatcher,
  IntrospectionResultData,
} from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { ApolloLink } from 'apollo-link'
import { onError } from 'apollo-link-error'
import { createHttpLink } from 'apollo-link-http'
import { createPersistedQueryLink } from 'apollo-link-persisted-queries'
import { createUploadLink } from 'apollo-upload-client'
import { canUseDOM } from 'exenv'

import PageCacheControl from '../cacheControl'
import graphQLErrorsStore from '../graphQLErrorsStore'
import { generateHash } from './generateHash'
import { toBase64Link } from './links/base64Link'
import { cachingLink } from './links/cachingLink'
import { ensureSessionLink } from './links/ensureSessionLink'
import { createIOFetchLink } from './links/ioFetchLink'
import { omitTypenameLink } from './links/omitVariableTypenameLink'
import { createUriSwitchLink } from './links/uriSwitchLink'
import { versionSplitterLink } from './links/versionSplitterLink'
import { createRuntimeContextLink } from './links/runtimeContextLink'
import { createHydrationFn, QueryData } from '../apolloCache'
import { RenderProvider } from '../../components/RenderProvider'
import { promised } from '../promise'
import { ApolloClientType } from '../../typings/global'
import { RenderRuntime } from '../../typings/runtime'

interface ApolloClientsRegistry {
  [key: string]: ApolloClientType
}

export interface ApolloClientFunctions {
  getClient: (instance: RenderProvider) => ApolloClientType
  hydrate: (queryData: QueryData[] | undefined) => Promise<void>
}

const buildCacheId = (
  vendor: string | undefined,
  app: string | undefined,
  major: string | undefined,
  type: string,
  cacheId: string
) =>
  app && major
    ? `${vendor}.${app}@${major}.x:${type}:${cacheId}`
    : `${type}:${cacheId}`

const dataIdFromObject = (value: any) => {
  const { cacheId, __typename } = value || ({} as any)
  if (value && __typename && cacheId) {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const [vendor, app, major, ...type] = __typename.split('_')
    const typename = type.join('_') || __typename
    return buildCacheId(vendor, app, major, typename, cacheId)
  }
  return null
}

export const buildCacheLocator = (
  app: string,
  type: string,
  cacheId: string
) => {
  const [vendor, appAndMajor] = app.replace(/-/g, '').split('.')
  const [appName, major] = appAndMajor && appAndMajor.split('@')
  return buildCacheId(vendor, appName, major, type, cacheId)
}

const clientsByWorkspace: ApolloClientsRegistry = {}

export const getState = (runtime: RenderRuntime) => {
  const { account, workspace } = runtime
  const apolloClient = clientsByWorkspace[`${account}/${workspace}`]
  return apolloClient ? apolloClient.cache.extract() : {}
}

const getOrCreateClient = (
  runtime: RenderRuntime,
  baseURI: string,
  runtimeContextLink: ApolloLink,
  sessionPromise: Promise<any>,
  fetcher: GlobalFetch['fetch'],
  cacheControl?: PageCacheControl
) => {
  const {
    account,
    workspace,
    introspectionResult,
  }: {
    account: string
    workspace: string
    introspectionResult: IntrospectionResultData
  } = runtime

  if (!clientsByWorkspace[`${account}/${workspace}`]) {
    const cache = new InMemoryCache({
      addTypename: true,
      dataIdFromObject,
      fragmentMatcher: new IntrospectionFragmentMatcher({
        introspectionQueryResultData: introspectionResult,
      }),
    })

    const httpLink = ApolloLink.from([
      toBase64Link,
      createHttpLink({
        credentials: 'include',
        useGETForQueries: false,
        fetch: fetcher,
      }),
    ])

    const uploadLink = createUploadLink({
      credentials: 'include',
    })

    const fetcherLink = createIOFetchLink(httpLink, uploadLink)

    const persistedQueryLink = createPersistedQueryLink({
      disable: () => false,
      generateHash,
      useGETForHashedQueries: true,
    })

    const uriSwitchLink = createUriSwitchLink(baseURI, runtime)

    const cacheLink = cacheControl ? [cachingLink(cacheControl)] : []

    const errorLink = onError(({ graphQLErrors }) => {
      if (graphQLErrors) {
        graphQLErrorsStore.addOperationIds(graphQLErrors)
      }
    })

    const link = ApolloLink.from([
      errorLink,
      omitTypenameLink,
      versionSplitterLink,
      runtimeContextLink,
      persistedQueryLink,
      uriSwitchLink,
      ensureSessionLink(sessionPromise),
      ...cacheLink,
      fetcherLink, //this is a final link
    ])

    clientsByWorkspace[`${account}/${workspace}`] = new ApolloClient({
      cache: canUseDOM ? cache.restore(window.__STATE__) : cache,
      link,
      resolvers: {},
      ssrMode: !canUseDOM,
      /** TODO: The empty typedefs below fixes an issue with Apollo Devtools.
       * Should look into why it is needed in the first place.
       * https://github.com/apollographql/apollo-client-devtools/issues/238
       */
      typeDefs: [],
    })
  }

  return clientsByWorkspace[`${account}/${workspace}`]
}

export const createApolloClient = (
  runtime: RenderRuntime,
  baseURI: string,
  sessionPromise: Promise<void>,
  cacheControl?: PageCacheControl
): Promise<ApolloClientFunctions> => {
  return promised((resolve) => {
    const {
      setRenderProviderInstance,
      link: runtimeContextLink,
    } = createRuntimeContextLink(runtime)

    const client = getOrCreateClient(
      runtime,
      baseURI,
      runtimeContextLink,
      sessionPromise,
      fetch,
      cacheControl
    )

    resolve({
      getClient: (instance: RenderProvider) => {
        setRenderProviderInstance(instance)
        return client
      },
      hydrate: createHydrationFn(client),
    })
  })
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/base64Link.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import { Base64 } from 'js-base64'

export const toBase64Link = new ApolloLink(
  (operation: Operation, forward?: NextLink) => {
    const { extensions, variables } = operation
    if (variables && Object.keys(variables).length > 0) {
      operation.variables = {}
      operation.extensions = {
        ...extensions,
        variables: Base64.encode(JSON.stringify(variables)),
      }
    }
    return forward ? forward(operation) : null
  }
)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/uriSwitchLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import { canUseDOM } from 'exenv'
import {
  ASTNode,
  DirectiveNode,
  OperationDefinitionNode,
  StringValueNode,
  visit,
} from 'graphql'

import { generateHash } from '../generateHash'
import { appendLocationSearch } from '../../location'
import { RenderRuntime, CacheHints } from '../../../typings/runtime'

interface Assets {
  operationType: string
  queryScope?: string
}

const assetsFromQuery = (query: ASTNode) => {
  const assets: Assets = { operationType: 'mutation' }
  visit(query, {
    Directive(node: DirectiveNode) {
      if (node.name.value === 'context') {
        const scopeArg =
          node.arguments &&
          node.arguments.find((argNode) => argNode.name.value === 'scope')
        if (scopeArg) {
          assets.queryScope = (scopeArg.value as StringValueNode).value
        }
      }
    },
    OperationDefinition(node: OperationDefinitionNode) {
      assets.operationType = node.operation
    },
  })
  return assets
}

export interface OperationContext {
  fetchOptions: any
  runtime: Pick<
    RenderRuntime,
    | 'appsEtag'
    | 'cacheHints'
    | 'components'
    | 'culture'
    | 'extensions'
    | 'messages'
    | 'pages'
  >
}

const extractHints = (query: ASTNode, meta: CacheHints) => {
  const { operationType, queryScope } = assetsFromQuery(query)

  let hints
  if (operationType?.toLowerCase() === 'query') {
    hints = meta ? meta : { scope: queryScope }
  } else {
    hints = { ...meta, scope: 'private' }
  }

  const {
    maxAge = 'long',
    scope = 'private',
    version = 1,
    provider,
    sender,
  } = hints
  return {
    maxAge: maxAge.toLowerCase(),
    operationType,
    scope: scope.toLowerCase(),
    version,
    provider,
    sender,
  }
}

export const createUriSwitchLink = (
  baseURI: string,
  initialRuntime: RenderRuntime
) =>
  new ApolloLink((operation: Operation, forward?: NextLink) => {
    operation.setContext((oldContext: OperationContext) => {
      const {
        fetchOptions = {},
        // Fetches from context for not fetching a stale version of runtime
        runtime: {
          appsEtag,
          cacheHints,
          culture: { locale },
        },
      } = oldContext
      const { extensions } = operation
      const {
        binding,
        workspace,
        route: { domain },
        production,
      } = initialRuntime
      const hash = generateHash(operation.query)

      if (!production && !hash) {
        throw new Error(
          'Could not generate hash from query. Are you using graphql-tag ? Split your graphql queries in .graphql files and import them instead'
        )
      }

      const includeQuery = (oldContext as any).http?.includeQuery || !hash
      const { maxAge, scope, version, provider, sender } = extractHints(
        operation.query,
        cacheHints[hash]
      )

      const requiresAuthorization =
        initialRuntime.settings?.[`vtex.${domain}`]?.requiresAuthorization

      const isPrivateChannel = initialRuntime.channelPrivacy === 'private'

      const customScope =
        requiresAuthorization || isPrivateChannel ? 'private' : scope
      const oldMethod = includeQuery ? 'POST' : fetchOptions.method || 'POST'
      const protocol = canUseDOM ? 'https:' : 'http:'
      const method =
        customScope?.toLowerCase() === 'private' ? 'POST' : oldMethod
      extensions.persistedQuery = {
        ...extensions.persistedQuery,
        sender,
        provider,
      }

      let query = `?workspace=${workspace}&maxAge=${maxAge}&appsEtag=${appsEtag}&domain=${domain}&locale=${locale}`
      if (binding && binding.id) {
        query = appendLocationSearch(query, { __bindingId: binding.id })
      }

      return {
        ...oldContext,
        http: {
          ...(oldContext as any).http,
          includeQuery,
        },
        scope: customScope,
        fetchOptions: { ...fetchOptions, method },
        uri: `${protocol}//${baseURI}/_v/${customScope}/graphql/v${version}${query}`,
      }
    })
    return forward ? forward(operation) : null
  })

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/omitVariableTypenameLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import { canUseDOM } from 'exenv'

const deepCopyFiles = (src: any, dst: any) =>
  src &&
  typeof src === 'object' &&
  Object.keys(src).forEach((key) => {
    if (
      src[key] instanceof File ||
      src[key] instanceof Blob ||
      src[key] instanceof FileList
    ) {
      dst[key] = src[key]
    } else {
      deepCopyFiles(src[key], dst[key])
    }
  })

const omitTypename = (key: string, value: any) =>
  key === '__typename' ? undefined : value

export const omitTypenameLink = new ApolloLink(
  (operation: Operation, forward?: NextLink) => {
    const { variables } = operation
    if (variables && canUseDOM) {
      operation.variables = JSON.parse(
        JSON.stringify(operation.variables),
        omitTypename
      )
      deepCopyFiles(variables, operation.variables)
    }
    return forward ? forward(operation) : null
  }
)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/cachingLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import PageCacheControl from '../../cacheControl'

export const cachingLink = (cacheControl: PageCacheControl) => {
  return new ApolloLink((operation: Operation, forward?: NextLink) => {
    if (forward) {
      return forward(operation).map((data) => {
        const { response } = operation.getContext()
        cacheControl.evaluate(response)
        return data
      })
    }
    return null
  })
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/ioFetchLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import { canUseDOM } from 'exenv'

const isUploadable = (v: any) =>
  v instanceof File || v instanceof Blob || v instanceof FileList

const usesUpload = (value: any): boolean => {
  if (isUploadable(value)) {
    return true
  }

  // If it isn't an object we can't recurse anymore
  if (Object(value) !== value) {
    return false
  }

  return Object.values(value).some((v) => usesUpload(v))
}

const useHttpLink = (operation: Operation) => {
  const { variables } = operation

  // No file upload is allowed during SSR
  if (!canUseDOM) {
    return true
  }

  const doUseHttpLink = !usesUpload(variables)
  return doUseHttpLink
}

const passthrough = (operation: Operation, forward?: NextLink) =>
  forward ? forward(operation) : null

export const createIOFetchLink = (
  httpLink: ApolloLink,
  uploadLink: ApolloLink
) => new ApolloLink(passthrough).split(useHttpLink, httpLink, uploadLink)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/versionSplitterLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  ApolloLink,
  createOperation,
  FetchResult,
  GraphQLRequest,
  NextLink,
  Observable,
  Operation,
} from 'apollo-link'
import {
  transformOperation,
  validateOperation,
} from 'apollo-link/lib/linkUtils'
import {
  BREAK,
  DirectiveNode,
  DocumentNode,
  OperationDefinitionNode,
  parse,
  print,
  SelectionNode,
  VariableDefinitionNode,
  VariableNode,
  visit,
} from 'graphql'

interface Variables {
  [name: string]: VariableDefinitionNode
}

interface Node {
  selection: SelectionNode
  usedVariables: Variables
}

const pickAvailableVariables = (query: DocumentNode) => {
  const availableVariables: Variables = {}
  visit(query, {
    VariableDefinition(node: VariableDefinitionNode) {
      availableVariables[node.variable.name.value] = node
    },
  })
  return availableVariables
}

const isRuntimeMetaDirective = (node: DirectiveNode) =>
  node.name.value === 'runtimeMeta'

const pickUsedVariables = (
  selection: SelectionNode,
  availableVariables: Variables
) => {
  const usedVariables: Variables = {}
  visit(selection, {
    Variable(varNode: VariableNode) {
      usedVariables[varNode.name.value] = availableVariables[varNode.name.value]
    },
  })
  return usedVariables
}

const pickFields = (query: DocumentNode, availableVariables: Variables) => {
  const nodes: Node[] = []
  visit(query, {
    Field(selection: SelectionNode) {
      if (
        selection.directives &&
        selection.directives.find(isRuntimeMetaDirective)
      ) {
        nodes.push({
          selection,
          usedVariables: pickUsedVariables(selection, availableVariables),
        })
      }
    },
  })
  return nodes
}

const operationWhiteList = ['query', 'mutation', 'subscription']

const queryFromNodeCreator = (query: DocumentNode) => (node: Node) =>
  visit(parse(print(query)), {
    OperationDefinition(opNode: OperationDefinitionNode) {
      if (operationWhiteList.includes(opNode.operation)) {
        return {
          ...opNode,
          selectionSet: {
            ...opNode.selectionSet,
            selections: [node.selection],
          },
          variableDefinitions: Object.values(node.usedVariables),
        }
      }

      return null
    },
  }) as DocumentNode

const isTypeQuery = (docNode: DocumentNode) => {
  const asset = { isQuery: false }
  visit(docNode, {
    OperationDefinition(node: OperationDefinitionNode) {
      if (node.operation === 'query') {
        asset.isQuery = true
        return BREAK
      }
    },
  })
  return asset.isQuery
}

const assertSingleOperation = (query: DocumentNode) => {
  const ops = query.definitions.filter((definition) =>
    operationWhiteList.includes(
      (definition as OperationDefinitionNode).operation
    )
  )

  if (ops.length > 1) {
    throw new Error(
      'Only one operation definition is allowed per query. Please split your queries in two different files'
    )
  }
}

const queriesByRuntimeMetaDirective = (query: DocumentNode) => {
  const availableVariables: Variables = pickAvailableVariables(query)
  const nodes: Node[] = pickFields(query, availableVariables)

  assertSingleOperation(query)

  return nodes.map(queryFromNodeCreator(query))
}

const mergeRecursively = (accumulator: any, value: any) => {
  if (value) {
    Object.keys(value).forEach((key) => {
      if (accumulator[key] && typeof value[key] === 'object') {
        accumulator[key] = mergeRecursively(accumulator[key], value[key])
      } else {
        accumulator[key] = value[key]
      }
    })
  }

  return accumulator
}

const createOperationForQuery = (operation: Operation) => (
  query: DocumentNode
) => {
  const graphQLRequest: GraphQLRequest = {
    ...operation,
    query,
    extensions: { ...operation.extensions },
  }
  const op = validateOperation(transformOperation(graphQLRequest))
  return createOperation(operation.getContext(), op)
}

const operationByRuntimeMetaDirective = (operation: Operation) => {
  const queries = queriesByRuntimeMetaDirective(operation.query)
  return queries.map(createOperationForQuery(operation))
}

const observableFromOperations = (operations: Operation[], forward: NextLink) =>
  new Observable((observer) => {
    const reduced = {}
    let togo = 0

    operations.forEach((op: Operation) =>
      forward(op).subscribe({
        complete: () => {
          if (togo === operations.length) {
            togo++
            observer.next(reduced)
            observer.complete()
          }
        },
        error: (err) => {
          togo++
          observer.error(err)
        },
        next: (data) => {
          togo++
          mergeRecursively(reduced, data)
        },
      })
    )
  }) as Observable<
    FetchResult<
      { [key: string]: any },
      Record<string, any>,
      Record<string, any>
    >
  >

export const versionSplitterLink = new ApolloLink(
  (operation: Operation, forward?: NextLink): any => {
    if (forward) {
      const query = operation.query
      const operations = operationByRuntimeMetaDirective(operation)

      if (operations.length && isTypeQuery(query)) {
        return observableFromOperations(operations, forward)
      } else {
        return forward(operation)
      }
    }
    return null
  }
)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/runtimeContextLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import { OperationContext } from './uriSwitchLink'
import { RenderProvider } from '../../../components/RenderProvider'
import { RenderRuntime } from '../../../typings/runtime'

export function createRuntimeContextLink(initialRuntime: RenderRuntime) {
  let renderProviderInstance: undefined | RenderProvider

  return {
    setRenderProviderInstance: (instance: RenderProvider) => {
      renderProviderInstance = instance
    },
    link: new ApolloLink((operation: Operation, forward?: NextLink) => {
      const {
        appsEtag,
        cacheHints,
        components,
        culture,
        extensions,
        messages,
        pages,
      } = renderProviderInstance?.state ?? initialRuntime

      operation.setContext(
        (currentContext: OperationContext): OperationContext => {
          return {
            ...currentContext,
            runtime: {
              appsEtag,
              cacheHints,
              components,
              culture,
              extensions,
              messages,
              pages,
            },
          }
        }
      )
      return forward ? forward(operation) : null
    }),
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/utils/client/links/ensureSessionLink.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ApolloLink, NextLink, Observable, Operation } from 'apollo-link'
import { Subscription } from 'apollo-client/util/Observable'

const sessionRequiredForScope = new Set(['segment', 'private'])

export const ensureSessionLink = (sessionPromise: Promise<any>) => {
  return new ApolloLink(
    (operation: Operation, forward?: NextLink) =>
      new Observable((observer) => {
        let handle: Subscription | undefined

        const { scope } = operation.getContext()
        const promise = sessionRequiredForScope.has(scope)
          ? sessionPromise
          : Promise.resolve()

        promise
          .then(() => {
            handle =
              forward &&
              forward(operation).subscribe({
                complete: observer.complete.bind(observer),
                error: observer.error.bind(observer),
                next: observer.next.bind(observer),
              })
          })
          .catch(observer.error.bind(observer))

        return () => {
          if (handle) {
            handle.unsubscribe()
          }
        }
      })
  )
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Hydration/index.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FunctionComponent } from 'react'
import HydrateOnView from './HydrateOnView'
import { Extension } from '../../typings/runtime'

interface Props {
  treePath: string
  hydration: Extension['hydration']
}

const Hydration: FunctionComponent<Props> = ({
  hydration,
  treePath,
  children,
}) => {
  if (hydration === 'on-view') {
    return <HydrateOnView treePath={treePath}>{children}</HydrateOnView>
  }

  return <>{children}</>
}

export default Hydration

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Hydration/HydrateOnView.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState, FC, useRef, MutableRefObject } from 'react'
import PreventHydration from './PreventHydration'
import { useDehydratedContent } from '../../hooks/hydration'
import { useOnView } from '../../hooks/viewDetection'

interface Props {
  treePath: string
}

const HydrateOnView: FC<Props> = ({ children, treePath }) => {
  const containerRef = useRef<HTMLElement | null>(null)
  const [hasBeenViewed, setHasBeenViewed] = useState(false)

  const { hasRenderedOnServer } = useDehydratedContent(treePath)

  useOnView({
    ref: containerRef,
    once: true,
    initializeOnInteraction: true,
    bailOut: !hasRenderedOnServer || hasBeenViewed,
    onView: () => {
      setHasBeenViewed(true)
    },
  })

  return (
    <div ref={containerRef as MutableRefObject<HTMLDivElement>}>
      <PreventHydration
        shouldHydrate={hasBeenViewed || !hasRenderedOnServer}
        treePath={treePath}
      >
        {children}
      </PreventHydration>
    </div>
  )
}

export default HydrateOnView

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Hydration/PreventHydration.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  useRef,
  useEffect,
  useState,
  ReactNode,
  useLayoutEffect,
  FunctionComponent,
} from 'react'
import { traverseExtension } from '../../utils/components'
import { fetchAssets, getImplementation } from '../../utils/assets'
import { useDehydratedContent } from '../../hooks/hydration'
import { canUseDOM } from 'exenv'
import { useRuntime } from '../RenderContext'
import { RenderRuntime } from '../../typings/runtime'

interface Props {
  shouldHydrate?: boolean
  children: ReactNode
  treePath: string
}

/** TODO: sometimes, the assets seem to load successfuly, but the component
 * implementation is not ready yet. This function works as a guarantee, but
 * need to understand it fully how it works so this verification can be avoided
 */
async function verifyComponentImplementation(
  runtime: RenderRuntime,
  treePath: string,
  retries = 10
): Promise<boolean> {
  const component = runtime?.extensions?.[treePath]?.component

  const componentImplementation = component && getImplementation(component)

  if (componentImplementation) {
    return true
  }

  if (retries > 0) {
    const timeout = 1100 - retries * 100
    await new Promise((resolve) => setTimeout(resolve, timeout))
    const result = await verifyComponentImplementation(
      runtime,
      treePath,
      retries - 1
    )

    return result
  }
  throw new Error(`Unable to fetch component ${component}`)
}

function loadAssets(runtime: RenderRuntime, treePath: string) {
  if (canUseDOM) {
    const extensionAssets = traverseExtension(
      runtime.extensions,
      runtime.components,
      treePath,
      true
    )

    return new Promise((resolve) => {
      fetchAssets(runtime, extensionAssets).then(() => {
        verifyComponentImplementation(runtime, treePath).then(resolve)
      })
    })
  }
  return new Promise(() => {})
}

/** document.querySelector[All] throws if the selector is malformed.
 * Since we are dealing with dynamic selectors here, it's best to be safe.
 */
function safeQuerySelectorAll<T extends Element>(selector: string) {
  try {
    return document.querySelectorAll<T>(selector)
  } catch (e) {
    console.error(e)
    return []
  }
}

function safeQuerySelector<T extends Element>(selector: string) {
  try {
    return document.querySelector<T>(selector)
  } catch (e) {
    console.error(e)
    return null
  }
}

const useAssetLoading = (extensionPointId?: string, shouldLoad?: boolean) => {
  const runtime = useRuntime()
  const isSSR = !canUseDOM
  const [isLoaded, setIsLoaded] = useState(isSSR)

  useEffect(() => {
    if (!isLoaded && extensionPointId && shouldLoad) {
      loadAssets((runtime as unknown) as RenderRuntime, extensionPointId).then(
        () => {
          setIsLoaded(true)
        }
      )
    }
  }, [isLoaded, extensionPointId, shouldLoad, runtime])

  return { isLoaded }
}

/** Prevents images from turning lazy again after re-rendering the component,
 * to prevent them from flickering. */
const useEagerImages = (treePath: string, shouldMakeImagesEager?: boolean) => {
  const alreadyLoadedImages = useRef([] as HTMLImageElement[])
  if (shouldMakeImagesEager) {
    /**  Stores which descendant images were already lazyloaded.
     * Those images will be set to load eagerly down below on the useLayoutEffect hook.
     * This prevents them from blinking. */
    const images = safeQuerySelectorAll<HTMLImageElement>(
      `[data-hydration-id="${treePath}"] img.lazyloaded`
    )

    alreadyLoadedImages.current = Array.from(images)
  }

  useLayoutEffect(() => {
    if (!shouldMakeImagesEager) {
      return
    }

    const images = alreadyLoadedImages.current.map((image) =>
      safeQuerySelector<HTMLImageElement>(`img[data-src="${image.src}"]`)
    )

    if (!images) {
      return
    }

    for (const image of images) {
      if (!image) {
        continue
      }
      image.classList.remove('lazyload')
      image.setAttribute('loading', 'eager')
      const dataSrc = image.getAttribute('data-src')
      if (!image.src && dataSrc) {
        image.src = dataSrc
      }
    }
  }, [treePath, shouldMakeImagesEager])
}

const PreventHydration: FunctionComponent<Props> = ({
  children,
  shouldHydrate,
  treePath,
}) => {
  const initialShouldHydrate = useRef(shouldHydrate)

  const { hasRenderedOnServer } = useDehydratedContent(treePath)

  const shouldRenderImmediately =
    !hasRenderedOnServer ||
    (shouldHydrate && shouldHydrate === initialShouldHydrate.current)

  const { isLoaded } = useAssetLoading(
    treePath,
    shouldRenderImmediately || shouldHydrate
  )
  const shouldMakeImagesEager =
    shouldHydrate && !shouldRenderImmediately && isLoaded

  /** Prevents images from turning lazy again after re-rendering the component,
   * to prevent them from flickering. */
  useEagerImages(treePath, shouldMakeImagesEager)

  const containerProps = {
    'data-hydration-id': treePath,
    style: {
      display: 'contents',
    },
  }

  if (!isLoaded && !hasRenderedOnServer) {
    return <div {...containerProps} />
  }

  if (shouldRenderImmediately) {
    return <div {...containerProps}>{children}</div>
  }

  return shouldHydrate && isLoaded ? (
    <div {...containerProps}>{children}</div>
  ) : (
    /** This is the heart of this component (and it's a hack):
     * In order to "prevent hydration", it renders the same div as the other
     * return paths, but setting its innerHTML as an empty string. This makes
     * React not touch the div's content, as it's "out of its domain" since
     * it's a custom HTML it knows nothing about. So the effect is that the
     * HTML that comes from the server is kept.
     * This behaviour will (probably?) come from React itself in the future,
     * so this hack is supposed to be temporary.
     */
    <div
      {...containerProps}
      suppressHydrationWarning
      dangerouslySetInnerHTML={{
        __html: '',
      }}
    />
  )
}

PreventHydration.displayName = 'PreventHydration'

export default React.memo(PreventHydration)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/index.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { mergeDeepRight, reduce } from 'ramda'
import React, { FC, Fragment, Suspense, useMemo } from 'react'

import ComponentLoader from './ComponentLoader'
import Loading from '../Loading'
import { useRuntime } from '../RenderContext'
import type { RenderContext } from '../RenderContext'
import { useTreePath } from '../../utils/treePath'
import NoSSR from '../NoSSR'
import { withErrorBoundary } from '../ErrorBoundary'
import GenericPreview from '../Preview/GenericPreview'
import LoadingBar from '../LoadingBar'
import { LazyImages } from '../LazyImages'
import LazyRender from '../LazyRender'
import FoldableContainer from '../FoldableContainer'
import { Route } from '../../typings/runtime'

// TODO: Export components separately on @vtex/blocks-inspector, so this import can be simplified
const InspectBlockWrapper = React.lazy(
  () =>
    new Promise<{ default: any }>((resolve) => {
      import('@vtex/blocks-inspector').then((BlocksInspector) => {
        resolve({ default: BlocksInspector.default.ExtensionPointWrapper })
      })
    })
)

interface Props {
  id: string
  key?: string
  params?: any
  query?: any
  preview?: boolean
  treePath?: string
  blockProps?: object
  [key: string]: any
}

function mountTreePath(currentId: string, parentTreePath: string) {
  if (parentTreePath === currentId) {
    return parentTreePath
  }
  if (parentTreePath && currentId) {
    return `${parentTreePath}/${currentId}`
  }
  return parentTreePath || currentId
}

export function getChildExtensions(runtime: RenderContext, treePath: string) {
  const extension = runtime.extensions && runtime.extensions[treePath]

  if (!extension || !extension.blocks) {
    return
  }

  const childBlocks = extension.blocks.filter((block) => {
    /* This weird conditional check is for backwards compatibility.
     * Blocks that were built prior to https://github.com/vtex/builder-hub/pull/856
     * would not have the 'children' property (block.children === undefined).
     */
    const isChild =
      block.children === undefined ||
      block.children === true ||
      block.blockRole === 'children'
    const isNotSlot = block.blockRole !== 'slot'

    return isChild && isNotSlot
  })

  /** Rudimentary detection of __fold__ blocks. Shouldn't be a problem
   * because other types of fold blocks aren't used inside other blocks
   * anyway. Could be improved if necessary.
   */
  const foldIndex = childBlocks.findIndex((child) =>
    child.extensionPointId.includes('_fold_')
  )

  const childExtensions = childBlocks.map((child, i) => {
    const childTreePath = mountTreePath(child.extensionPointId, treePath)

    const childExtension = runtime?.extensions[childTreePath]
    const childProps = childExtension?.props ?? {}

    return (
      <ExtensionPoint
        key={`around-${treePath}-${i}`}
        id={child.extensionPointId}
        blockProps={childProps}
        treePath={treePath}
      />
    )
  })

  const queryString = runtime?.route?.queryString ?? {}

  if (Object.keys(queryString).includes('__siteEditor')) {
    return childExtensions
  }

  if (foldIndex > -1) {
    return (
      <FoldableContainer foldIndex={foldIndex}>
        {childExtensions}
      </FoldableContainer>
    )
  }
  return childExtensions
}

function withOuterExtensions(
  after: string[],
  around: string[],
  before: string[],
  treePath: string,
  props: any,
  element: JSX.Element,
  // TODO: these args are getting ridiculous, maybe group them in an object
  lazyFooter: boolean,
  route: Route
) {
  if (before.length === 0 && after.length === 0 && around.length === 0) {
    return element
  }

  const beforeElements = before.map((beforeId) => (
    <ExtensionPoint
      id={beforeId}
      key={beforeId}
      treePath={treePath}
      params={props.params}
      query={props.query}
    />
  ))

  const afterElements = after.map((afterId) => {
    const extension = (
      <ExtensionPoint
        id={afterId}
        key={afterId}
        treePath={treePath}
        params={props.params}
        query={props.query}
      />
    )

    const shouldLazyRender =
      lazyFooter &&
      afterId === '$after_footer' &&
      !route?.path.includes('__siteEditor')

    return shouldLazyRender ? (
      <LazyRender key={afterId}>{extension}</LazyRender>
    ) : (
      extension
    )
  })

  const isRootTreePath = treePath.indexOf('/') === -1

  const wrapped = (
    <Fragment key={`wrapped-${treePath}`}>
      <LazyImages>{beforeElements}</LazyImages>
      {element}
      {isRootTreePath && <div className="flex flex-grow-1 bg-below-element" />}
      <LazyImages>{afterElements}</LazyImages>
    </Fragment>
  )

  return around.reduce((acc, aroundId) => {
    return (
      <ExtensionPoint
        {...props}
        id={aroundId}
        key={aroundId}
        treePath={treePath}
        beforeElements={beforeElements}
        afterElements={afterElements}
      >
        {acc}
      </ExtensionPoint>
    )
  }, wrapped)
}

const ExtensionPoint: FC<Props> = (props) => {
  const runtime = useRuntime()

  const { inspect, getSettings } = runtime

  const treePathFromHook = useTreePath()

  const { children, params, query, id, blockProps, ...parentProps } = props

  const newTreePath = React.useMemo(
    () => mountTreePath(id, props.treePath || treePathFromHook.treePath),
    [id, props.treePath, treePathFromHook.treePath]
  )

  const extension = runtime.extensions && runtime.extensions[newTreePath]

  const {
    component = null,
    after = [],
    around = [],
    before = [],
    content = {},
    render: renderStrategy = null,
    hydration = 'always',
    props: extensionProps = {},
  } = extension || {}

  const appName = component?.substr(0, component.indexOf('@'))
  const appSettings = appName ? getSettings(appName) : {}

  const mergedProps = React.useMemo(() => {
    return reduce(mergeDeepRight, {} as any, [
      appSettings ? { appSettings } : {},
      /** Extra props passed to the ExtensionPoint component
       * e.g. <ExtensionPoint foo="bar" />
       */
      parentProps,
      /** Props that are read from runtime.extensions, that come from the blocks files
       */
      extensionProps,
      /** Props from the blockProps prop, used when the user wants to prevent overriding
       * the native ExtensionPoint props (such as `id`)
       */
      blockProps || {},
      content,
      { params, query },
    ])
  }, [
    parentProps,
    extensionProps,
    blockProps,
    content,
    params,
    query,
    appSettings,
  ])

  const componentChildren = useMemo(() => {
    const isCompositionChildren =
      extension && extension.composition === 'children'

    return isCompositionChildren && extension?.blocks
      ? getChildExtensions(runtime, newTreePath)
      : children
  }, [children, extension, newTreePath, runtime])

  if (
    /* Stops rendering if the extension is not found. Useful for optional ExtensionPoints */
    !extension
  ) {
    return null
  }

  const isRootTreePath = newTreePath.indexOf('/') === -1

  const componentLoader = (
    <ComponentLoader
      component={component}
      props={mergedProps}
      runtime={runtime}
      treePath={newTreePath}
      hydration={hydration}
    >
      {component ? (
        componentChildren
      ) : isRootTreePath ? (
        <GenericPreview />
      ) : (
        <Loading />
      )}
    </ComponentLoader>
  )

  const isLazyFooterEnabled = Boolean(
    getSettings('vtex.store')?.enableLazyFooter
  )

  const extensionPointComponent = withOuterExtensions(
    after,
    around,
    before,
    newTreePath,
    mergedProps,
    componentLoader,
    isLazyFooterEnabled,
    runtime?.route
  )

  /**
   * "client" component assets are sent to server side rendering,
   * but they should display a loading animation.
   * "lazy" components might never be used, so they don't necessarily
   * need a loading animation.
   */
  const maybeClientExtension = (
    <Fragment>
      {runtime.preview && isRootTreePath && <LoadingBar />}
      {renderStrategy === 'client' && !runtime.amp ? (
        <NoSSR onSSR={<Loading />}>{extensionPointComponent}</NoSSR>
      ) : (
        extensionPointComponent
      )}
    </Fragment>
  )

  /** If it's on inspect mode (?__inspect on querystring) wraps the block
   * on a block-inspector wrapper */
  if (inspect) {
    return (
      <Suspense fallback={maybeClientExtension}>
        <InspectBlockWrapper extension={extension} treePath={newTreePath}>
          {maybeClientExtension}
        </InspectBlockWrapper>
      </Suspense>
    )
  }

  return maybeClientExtension
}

ExtensionPoint.defaultProps = {
  blockProps: {},
  treePath: '',
}

export default withErrorBoundary(ExtensionPoint)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/ExtensionContainer.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC, Fragment } from 'react'

import { getDirectChildren, useTreePath } from '../../utils/treePath'
import ExtensionPoint from '.'
import { useRuntime } from '../RenderContext'

const join = (p: string | null, c: string | null): string =>
  [p, c].filter((id) => !!id).join('/')

interface Props {
  id: string | null
}

const ExtensionContainer: FC<Props> = (props) => {
  const { id } = props
  const { extensions } = useRuntime()
  const { treePath } = useTreePath()
  const containerTreePath = join(treePath, id)
  return (
    <Fragment>
      {getDirectChildren(extensions, containerTreePath).map((cid) => {
        const childTreePath = join(id, cid)
        return (
          <ExtensionPoint {...props} key={childTreePath} id={childTreePath} />
        )
      })}
    </Fragment>
  )
}

export default ExtensionContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/ExtensionPortal.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import React, { Component, ReactPortal } from 'react'
import { createPortal } from 'react-dom'

import { PortalRenderingRequest } from './ExtensionManager'
import ExtensionPoint from './index'

interface Props {
  extension: PortalRenderingRequest
}

class ExtensionPortal extends Component<Props> {
  public static propTypes = {
    extension: PropTypes.object,
  }

  public constructor(props: Props) {
    super(props)
  }

  public render() {
    const { extensionName, destination, props } = this.props.extension
    return createPortal(
      <ExtensionPoint id={extensionName} {...props} />,
      destination
    ) as ReactPortal
  }
}

export default ExtensionPortal

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/ExtensionManager.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import { any, map, append } from 'ramda'
import React, { Component } from 'react'

import ExtensionPortal from './ExtensionPortal'
import { RenderRuntime } from '../../typings/runtime'

export interface PortalRenderingRequest {
  extensionName: string
  destination: HTMLElement
  props: any
}

interface Props {
  runtime: RenderRuntime
}

interface State {
  extensionsToRender: PortalRenderingRequest[]
}

class ExtensionManager extends Component<Props, State> {
  public static propTypes = {
    runtime: PropTypes.object,
  }

  public constructor(props: Props) {
    super(props)
    this.state = {
      extensionsToRender: [],
    }
  }

  public componentDidMount() {
    const {
      runtime: { emitter },
    } = this.props
    emitter.addListener(
      'renderExtensionLoader.addOrUpdateExtension',
      this.updateExtensions
    )
  }

  public componentWillUnmount() {
    const {
      runtime: { emitter },
    } = this.props
    emitter.removeListener(
      'renderExtensionLoader.addOrUpdateExtension',
      this.updateExtensions
    )
  }

  public updateExtensions = (extension: PortalRenderingRequest) => {
    this.setState({
      extensionsToRender: this.addOrUpdateExtension(
        this.state.extensionsToRender,
        extension
      ),
    })
  }

  public render() {
    return this.state.extensionsToRender.map((el) => {
      return <ExtensionPortal key={el.extensionName} extension={el} />
    })
  }

  private addOrUpdateExtension = (
    extensionsList: PortalRenderingRequest[],
    newExtension: PortalRenderingRequest
  ): PortalRenderingRequest[] => {
    const exists = any((el: PortalRenderingRequest) => {
      return el.extensionName === newExtension.extensionName
    })(extensionsList)
    const newExtensionsList = exists
      ? map((el: PortalRenderingRequest) => {
          return el.extensionName === newExtension.extensionName
            ? newExtension
            : el
        }, extensionsList)
      : append(newExtension, extensionsList)
    return newExtensionsList
  }
}

export default ExtensionManager

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/ErrorDisplay.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import PropTypes from 'prop-types'
import React, { ErrorInfo, PureComponent } from 'react'

interface Props {
  treePath?: string
  error?: Error
  errorInfo?: ErrorInfo | null
  operationIds?: string[]
}

interface State {
  errorDetails?: boolean
}

class ErrorDisplay extends PureComponent<Props, State> {
  public static propTypes = {
    error: PropTypes.object,
    errorInfo: PropTypes.object,
    treePath: PropTypes.string,
  }

  public constructor(props: any) {
    super(props)
    this.state = {}
  }

  public handleToggleErrorDetails = () => {
    this.setState({
      errorDetails: !this.state.errorDetails,
    })
  }

  public render() {
    const { treePath, error, errorInfo, operationIds } = this.props
    const { errorDetails } = this.state

    const componentStack = errorInfo && errorInfo.componentStack

    return (
      <div className="bg-washed-red pa6 f5 serious-black br3 pre">
        <span>
          Error rendering extension point <strong>{treePath}</strong>
        </span>
        <button
          type="button"
          className="red ph0 ma0 mh3 bg-transparent bn pointer link"
          onClick={this.handleToggleErrorDetails}
        >
          ({errorDetails ? 'hide' : 'show'} details)
        </button>
        {errorDetails && error && (
          <>
            <ul className="f6 list pl0">
              {operationIds &&
                operationIds.map((operationId) => (
                  <li key={operationId}>
                    <span>Operation ID:</span>{' '}
                    <span className="i">{operationId}</span>
                  </li>
                ))}
            </ul>
            <pre>
              <code className="f6">{error.stack}</code>
            </pre>
          </>
        )}
        {errorDetails && componentStack && (
          <pre>
            <code className="f6">{componentStack}</code>
          </pre>
        )}
      </div>
    )
  }
}

export default ErrorDisplay

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/ComponentLoader.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  useEffect,
  useState,
  FunctionComponent,
  ReactNode,
  useMemo,
} from 'react'

import { getImplementation } from '../../utils/assets'
import GenericPreview from '../Preview/GenericPreview'
import Loading from '../Loading'
import { TreePathContextProvider } from '../../utils/treePath'
import { isSiteEditorIframe } from '../../utils/dom'
import SiteEditorWrapper from './SiteEditorWrapper'
import Hydration from '../Hydration'
import { LazyImages } from '../LazyImages'
import { generateSlot } from '../../utils/slots'
import type { RenderContext } from '../RenderContext'
import { Extension } from '../../typings/runtime'

const componentPromiseMap: any = {}
const componentPromiseResolvedMap: any = {}

export async function fetchComponent(
  component: string,
  runtimeFetchComponent: RenderContext['fetchComponent'],
  retries = 3
): Promise<any> {
  const Component = component && getImplementation(component)

  if (Component) {
    return Component
  }

  if (!(component in componentPromiseMap)) {
    componentPromiseMap[component] = runtimeFetchComponent(component)
  } else if (componentPromiseResolvedMap[component]) {
    /** These retries perhaps are not needed anymore, but keeping just to be safe */
    if (retries > 0) {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      return fetchComponent(component, runtimeFetchComponent, retries - 1)
    }

    /* Loading was completed but the component was not registered.
     * This means something wrong happened */
    throw new Error(`Unable to fetch component ${component}`)
  }

  await componentPromiseMap[component]

  componentPromiseResolvedMap[component] = true

  return getImplementation(component)
}

interface Props {
  component: string | null
  props: Record<string, any>
  treePath: string
  runtime: RenderContext
  hydration: Extension['hydration']
}

const ComponentLoader: FunctionComponent<Props> = (props) => {
  const {
    component,
    children,
    treePath,
    props: componentProps,
    runtime,
    hydration,
  } = props

  /**
   * Slot props should ALWAYS be PascalCased.
   * It is OK to not include componentProps in the dependency array
   * since there is currently no way for users to ADD or UPDATE slots via CMS.
   * What this means is that the slots variable below only needs to be
   * computed once during runtime, since we know that, even if componentProps
   * is updated, the props that function as Slots will NOT change.
   */
  const slots = useMemo(() => {
    if (!componentProps) {
      return {}
    }

    const slotNames = Object.keys(componentProps).filter(
      (key) => key[0] !== key[0].toLowerCase()
    )
    const resultingSlotsProps: Record<string, ReactNode> = {}

    for (const slotName of slotNames) {
      resultingSlotsProps[slotName] = generateSlot({
        treePath,
        slotName,
        slotValue: componentProps[slotName],
        hydration,
      })
    }

    return resultingSlotsProps
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [hydration, treePath])

  const componentPropsWithSlots = useMemo(
    () => ({
      ...componentProps,
      ...slots,
    }),
    [componentProps, slots]
  )
  const asyncComponentProps = useMemo(
    () => ({
      ...props,
      props: componentPropsWithSlots,
    }),
    [componentPropsWithSlots, props]
  )

  const Component = component && getImplementation(component)

  if (component?.includes('Fold')) {
    return null
  }

  let content = Component ? (
    <Component {...componentPropsWithSlots}>{children}</Component>
  ) : (
    <AsyncComponent {...asyncComponentProps}>{children}</AsyncComponent>
  )

  const queryString = runtime?.route?.queryString ?? {}

  const isSiteEditor = Object.keys(queryString).includes('__siteEditor')

  const shouldHydrateEagerly =
    !hydration ||
    hydration === 'always' ||
    /** TODO: Currently it only applies partial hydration on top level components
     * Nested partial hydration should be supported in the future */
    /** (using indexOf instead of regex for performance
     * https://jsperf.com/js-regex-match-vs-substring) */
    treePath?.substring(treePath?.indexOf('/') + 1).indexOf('/') > -1

  if (!isSiteEditor && !shouldHydrateEagerly) {
    content = (
      <LazyImages>
        <Hydration treePath={treePath} hydration={hydration}>
          {content}
        </Hydration>
      </LazyImages>
    )
  }

  content = (
    <TreePathContextProvider treePath={treePath}>
      {content}
    </TreePathContextProvider>
  )

  if (isSiteEditorIframe) {
    content = <SiteEditorWrapper {...props}>{content}</SiteEditorWrapper>
  }

  return content
}

const AsyncComponent: FunctionComponent<Props> = (props) => {
  const {
    component,
    children,
    treePath,
    props: componentProps,
    runtime,
  } = props

  const isRootTreePath = treePath.indexOf('/') === -1
  const isAround = treePath.indexOf('$around') !== -1

  const [Component, setComponent] = useState(
    () => (component && getImplementation(component)) || null
  )

  useEffect(() => {
    // Does nothing if Component is loaded...
    // (or if component is nil)
    if (Component || !component) {
      return
    }

    // ...otherwise, fetches it and stores the result in the Component state
    fetchComponent(component, runtime.fetchComponent).then((result) => {
      if (Component) {
        return
      }
      setComponent(() => result)
    })
  }, [Component, component, runtime.fetchComponent])

  return Component ? (
    <Component {...componentProps}>{children}</Component>
  ) : /** If the component is not loaded yet, renders a "loading"
   * state. This currently only applies to root components
   * (e.g. "store.home") */
  isRootTreePath || isAround ? (
    <>
      {componentProps.beforeElements}
      <GenericPreview />
      {componentProps.afterElements}
    </>
  ) : (
    <Loading />
  )
}

export default ComponentLoader

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint/ExtensionPoint.test.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { cleanup, render } from '@vtex/test-tools/react'
import 'jest-dom/extend-expect'
import ExtensionPoint from './index'
import { TreePathContextProvider } from '../../utils/treePath'
import { RenderContextProvider } from '../RenderContext'
import { TEST_ID as PreviewTestId } from '../Preview'
import { Extension } from '../../typings/runtime'

afterEach(cleanup)

test(`it shouldn't show Preview when there is no extension for current tree path`, () => {
  const mockExtensions: Record<string, Partial<Extension>> = {
    'store.search#category/search-result#category': {
      component: '',
      preview: {
        height: {
          desktop: { defaultValue: 500 },
          mobile: { defaultValue: 500 },
        },
        width: {
          desktop: { defaultValue: 500 },
          mobile: { defaultValue: 500 },
        },
        type: 'box',
      },
    },
    // The extension below is what the ExtensionPoint should use.
    // 'store.search#category/search-result#category/search-title': undefined,
  }

  const { queryByTestId } = render(
    <RenderContextProvider runtime={{ extensions: mockExtensions } as any}>
      <TreePathContextProvider treePath="store.search#category/search-result#category">
        <ExtensionPoint treePath="" id="search-title" />
      </TreePathContextProvider>
    </RenderContextProvider>
  )

  expect(queryByTestId(PreviewTestId)).toBeNull()
})

```