promoted-ts-client

A Typescript Client to contact Promoted APIs. This is primarily intended to be used when logging Requests and Insertions on a Node.js server.

Usage no npm install needed!

<script type="module">
  import promotedTsClient from 'https://cdn.skypack.dev/promoted-ts-client';
</script>

README

promoted-ts-client

A Typescript Client to contact Promoted APIs. This is primarily intended to be used when logging Requests and Insertions on a Node.js server.

Client logging libraries:

Creating a PromotedClient

We recommend creating a PromotedClient in a separate file so it can be reused.

PromotedClient avoids having direct dependencies so customer's have more options for customization and can keep dependencies smaller.

promotedClient.js

import { logOnError, newPromotedClient, throwOnError } from 'promoted-ts-client';
import { v5 as uuid } from 'uuid';
import axios from 'axios';

// Client can choose their preferred RPC client.
const axiosApiClient = <Req, Res>(url: string, apiKey: string, timeout: number) => (request: Req): Promise<Res> =>
  axios.post(
    url,
    request,
    {
      headers: {
        "x-api-key": apiKey,
      },
      timeout,
    });

// These values will vary depending on dev vs prod.
const deliveryApi = 'https://....com/...';
const deliveryApiKey = 'AbCSomeRLongString1';
const deliveryTimeoutMillis = 250;
const metricsApi = 'https://....com/...';
const metricsApiKey = 'AbCSomeRLongString2';
const metricsTimeoutMillis = 3000;

// NextJS example.  For errors, if inDev then throw else log.
const throwError =
  process?.env?.NODE_ENV !== 'production' ||
  (typeof location !== "undefined" && location?.hostname === "localhost");

export const promotedClient = newPromotedClient({
  // TODO - Customize handleError for your server.
  // When developing using Node.js, throwOnError will give a scary unhandled promise warning.
  handleError: throwError ? throwOnError : logOnError;
  deliveryClient: axiosApiClient(deliveryApi, deliveryApiKey, deliveryTimeoutMillis),
  metricsClient: axiosApiClient(metricsApi, metricsApiKey, metricsTimeoutMillis),
  uuid,
  deliveryTimeoutMillis,
  metricsTimeoutMillis,
});

Calling our Delivery API

Let's say the previous code looks like this:

static async getProducts(req: any, res: Response) {
  const products = ...; // Logic to get products from DB, apply filtering, etc.
  sendSuccessToClient(res, { products });
}

We would modify to something like this:

static async getProducts(req: any, res: Response) {
  const products = ...;
  const response = await promotedClient.deliver({
    request: {
      userInfo: {
        logUserId: req.logUserId,
      },
      useCase: 'FEED',
      sessionId: req.sessionId,
      viewId: req.viewId,
    },
    fullInsertions: products.map(product => ({
      // Must be filled in.
      contentId: product.id,
      // You can set custom properties here.
      properties: {
        struct: {
          product,
        },
      },
    })),
  });
  // Change the response Product list to use the values in the returned Insertions.
  sendSuccessToClient(res, {
    products: response.insertion.map(insertion => insertion.properties.struct.product),
  });
  await response.log();
}

There are other optional options.

Argument Type Default Value Description
onlyLog boolean false Can be used to conditionally disable deliver per request
toCompactDeliveryInsertion Insertion => Insertion Returns the argument Can be used to strip out fields being passed into Delivery API
toCompactMetricsInsertion Insertion => Insertion Returns the argument Can be used to strip out fields being passed into Metrics API

Logging only

There are two ways of doing this with PromotedClient:

  1. You can use deliver but add a onlyLog: true property.
  2. You can use prepareForLogging method call instead. The prepareForLogging signature is similar to deliver and should be integrated the same way.

Pagination

  • When calling deliver, we expect that you will pass an unpaged (complete) list of insertions, and the SDK assumes this to be the case. To help you catch this scenario, the SDK will call handleError in the pre-paged case if performChecks is turned on.

  • When calling prepareForLogging with shadow traffic turned on, we also expect an unpaged list of insertions, since in this case we are simulating delivery.

  • When calling prepareForLogging otherwise, you may choose to pass "pre-paged" or "unpaged" insertions based on the insertionPageType field on the MetricsRequest.

    • When insertionPageType is "unpaged", the Request.paging.offset and Request.paging.size parameters are used to log a "window" of insertions.
    • When insertionPageType is "pre-paged", the SDK will not handle pagination of the insertions that are part of the resulting lot request.

Position

  • Do not set the insertion position field in client code. The SDK and Delivery API will set it when deliver is called.
  • When logging only via prepareForLogging, the position field is not set by the SDK on the log request insertions.

The prepareForLogging call assumes the client has already handled pagination. It needs a Request.paging.offset to be passed in for the number of items deep that the page is.

Improving this library

Tech used

Uses

Scripts

  • Run most commands: npm run finish
  • Build the project: npm run build
    • Validate output bundle size with npm run size
  • Lint the project: npm run lint
  • Run unit tests: npm test or npm test

When developing locally

If you want to test local changes in an actual deployment, use npm link.

  1. Make sure your npm uses the same version as the client directory.
  2. Run npm run updatelink.
  3. Go to client directory and run npm link promoted-ts-client.

When you update promoted-ts-client, run npm run updatelink.

When you want to undo, run npm run unlink in this directory and npm unlink promoted-ts-client in the client directory.

Deploy

Based on the anticipated semantic-version, update the SERVER_VERSION constant in client.ts.

We use a GitHub action that runs semantic-release to determine how to update versions. Just do a normal code review and this should work. Depending on the message prefixes (e.g. feat: , fix: , clean: , docs: ), it'll update the version appropriately.

Resources

The base of this repository is a combination of the following repos: