rudra-js

parseTrackingInput

import { parseTrackingInput } from '@rudra-js/core';

Call parseTrackingInput to check one page view and fill in the defaults you left out. Pass what it returns to generator.generate.

parseTrackingInput(value: unknown) => TrackingInput

Reference

parseTrackingInput(value)

Parameters

  • value: The page view: the shopper in user, the page in context, what the shopper has done in signals, and the products you're willing to show in candidates. Type it as TrackingInputDraft when you build it in your own code. Every field is listed under Fields.

Returns

The page view as a TrackingInput, with every default filled in. When the page view doesn't fit, it throws a ZodError instead.

Caveats

  • Pass the parsed input.candidates to RudraComponent, never the raw objects. The parsed list has the defaults filled in, such as currency and isInStock.
  • Unknown fields are rejected. A misspelled recentSearches makes the parse fail.
  • meta on an interaction is the one place that takes keys of your own. Any key works except __proto__.
  • Every text field and every list has a size limit. FIELD_LIMITS holds the numbers, so your own code can check against them.
  • SKUs in candidates have to be unique.
  • context.locale takes one language tag, such as en-US. An Accept-Language header fails.
  • In an app on zod 3, error instanceof ZodError is false. Read error.issues instead.

safeParseTrackingInput(value)

Call safeParseTrackingInput to check a page view without throwing.

Parameters

  • value: The same page view parseTrackingInput takes.

Returns

A TrackingInputResult. When result.success is true, result.data is the parsed page view. When it is false, result.error.issues lists what failed.

Fields

NameTypeDefaultConstraints
schemaVersionoptional"1""1"
userobject
user.idstring1 to 128 chars
user.segmentoptionalstring1 to 128 chars
user.isReturningoptionalboolean
contextRenderContext
context.surfacestring1 to 128 chars
context.slotoptionalstring"recommendations"1 to 128 chars
context.currentSkuoptionalstring1 to 128 chars
context.currentCategoryoptionalstring1 to 128 chars
context.searchQueryoptionalstringmax 200 chars
context.localeoptionalstring"en-US"max 35 chars, pattern ^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$
context.maxItemsoptionalinteger41 to 12
signalsoptionalobject{}
signals.likesoptionalSkuSignal[][]max 500 items
signals.likes.skustring1 to 128 chars
signals.likes.categoryoptionalstring1 to 128 chars
signals.likes.atoptionalinteger0 to 4102444800000
signals.likes.weightoptionalnumber0 to 1
signals.dislikesoptionalSkuSignal[][]max 500 items
signals.dislikes.skustring1 to 128 chars
signals.dislikes.categoryoptionalstring1 to 128 chars
signals.dislikes.atoptionalinteger0 to 4102444800000
signals.dislikes.weightoptionalnumber0 to 1
signals.mostViewedoptionalViewSignal[][]max 500 items
signals.mostViewed.skustring1 to 128 chars
signals.mostViewed.categoryoptionalstring1 to 128 chars
signals.mostViewed.atoptionalinteger0 to 4102444800000
signals.mostViewed.weightoptionalnumber0 to 1
signals.mostViewed.viewsoptionalinteger1max 9007199254740991, > 0
signals.mostViewed.dwellMsoptionalnumbermin 0
signals.lastPurchasedoptionalPurchaseSignal[][]max 500 items
signals.lastPurchased.skustring1 to 128 chars
signals.lastPurchased.categoryoptionalstring1 to 128 chars
signals.lastPurchased.atoptionalinteger0 to 4102444800000
signals.lastPurchased.weightoptionalnumber0 to 1
signals.lastPurchased.quantityoptionalinteger1max 9007199254740991, > 0
signals.lastPurchased.priceoptionalnumbermin 0
signals.cartoptionalSkuSignal[][]max 500 items
signals.cart.skustring1 to 128 chars
signals.cart.categoryoptionalstring1 to 128 chars
signals.cart.atoptionalinteger0 to 4102444800000
signals.cart.weightoptionalnumber0 to 1
signals.recentSearchesoptionalstring[][]max 500 items
signals.interactionsoptionalInteraction[][]max 500 items
signals.interactions.typestring1 to 128 chars
signals.interactions.skuoptionalstring1 to 128 chars
signals.interactions.categoryoptionalstring1 to 128 chars
signals.interactions.atoptionalinteger0 to 4102444800000
signals.interactions.valueoptionalstring | number | boolean
signals.interactions.metaoptionalany
candidatesProduct[]1 to 200 items
bundlesoptionalBundle[][]max 20 items

Usage

Building the input from a product page view

Build one page view for each render. This one is for a shopper looking at the trail shoe TR-102.

import { parseTrackingInput } from '@rudra-js/core';

export const input = parseTrackingInput({
  user: { id: 'shopper-1', isReturning: true },
  context: { surface: 'pdp', currentSku: 'TR-102', currentCategory: 'Trail Running' },
  signals: {
    likes: [{ sku: 'TR-104' }],
    mostViewed: [{ sku: 'TR-104', views: 3 }],
    recentSearches: ['waterproof trail shoe'],
  },
  candidates: [
    {
      sku: 'TR-102',
      title: 'Switchback Trail Shoe GTX',
      category: 'Trail Running',
      price: 174,
      imageUrl: '/images/tr-102.png',
    },
    {
      sku: 'TR-104',
      title: 'Ridgeline Trail Shoe',
      category: 'Trail Running',
      price: 139,
      imageUrl: 'https://cdn.example.com/tr-104.png',
      tags: ['waterproof'],
    },
    {
      sku: 'SK-201',
      title: 'Merino Hiking Socks',
      category: 'Socks',
      price: 22,
    },
  ],
});

You have to send user.id, context.surface and at least one candidate. The rest is optional.

Leave out signals for a first-time visitor. Every list in it defaults to [].

Checking input without throwing

Use safeParseTrackingInput when the page view comes from outside your code, such as a request body.

import { safeParseTrackingInput, type TrackingInput } from '@rudra-js/core';

export function readPageView(body: unknown): TrackingInput | null {
  const result = safeParseTrackingInput(body);
  if (!result.success) {
    console.warn('The page view did not fit:', result.error.issues);
    return null;
  }
  return result.data;
}

Each issue has a path and a message, such as Unrecognized key: "recentSearch".