UNPKG

promoted-ts-client

Version:
685 lines (631 loc) 21.1 kB
/** * Simple function interface for making API calls. */ interface ApiClient<Req, Res> { (request: Req): Promise<Res>; } /** * Used to set default values on BaseRequests in the Client's constructor. */ interface RequiredBaseRequest { /** * A way to customize when `deliver` should not run an experiment and just log * (CONTROL) vs run through the experiment deliver code path. * Defaults to false. */ onlyLog: boolean; } /** * Common interface so we can set request values in PromotedClient's constructor. */ interface BaseRequest { /** * A way to customize when `deliver` should not run an experiment and just log * (CONTROL) vs run through the experiment deliver code path. * Defaults to false. */ onlyLog?: boolean; } interface ErrorHandler { (err: Error): void; } /** * Simple ErrorHandler that throws the error. */ declare const throwOnError: ErrorHandler; /** * Simple ErrorHandler that logs to console.err. */ declare const logOnError: ErrorHandler; /** * Sampler provides algorithms that let us choose a subset of SDK * traffic to process, in a testable way. */ interface Sampler { sampleRandom(threshold: number): boolean; } // package: common // file: proto/common/common.proto interface UserInfo { userId?: string; anonUserId?: string; isInternalUser?: boolean; } interface Timing { clientLogTimestamp?: number; eventApiTimestamp?: number; } interface Properties { structBytes?: Uint8Array | string; // TODO - support a type for struct. struct?: any; } type TrafficType = 0 | 1 | 2 | 4 | 5 | 'UNKNOWN_TRAFFIC_TYPE' | 'PRODUCTION' | 'REPLAY' | 'SHADOW' | 'LOAD_TEST'; type ClientType = 0 | 1 | 2 | 'UNKNOWN_REQUEST_CLIENT' | 'PLATFORM_SERVER' | 'PLATFORM_CLIENT'; interface ClientInfo { trafficType?: TrafficType; clientType?: ClientType; } interface Size { width?: number; height?: number; } interface ClientBrandHint { brand?: string; version?: string; } interface ClientHints { isMobile?: boolean; brand?: ClientBrandHint[]; architcture?: string; model?: string; platform?: string; platformVersion?: string; uaFullVersion?: string; } interface Location { latitude?: number; longitude?: number; accuracyInMeters?: number; } interface Browser { userAgent?: string; viewportSize?: Size; clientHints?: ClientHints; referrer?: string; } interface Screen { size?: Size; scale?: number; } interface Device { deviceType?: Device; brand?: string; manufacturer?: string; identifier?: string; screen?: Screen; ipAddress?: string; location?: Location; browser?: Browser; } // package: delivery // file: proto/delivery/delivery.proto interface DeliveryLog { request?: Request; response?: Response; execution?: DeliveryExecution; } interface Request { platformId?: number; userInfo?: UserInfo; timing?: Timing; device?: Device; requestId?: string; viewId?: string; sessionId?: string; useCase?: UseCaseMap[keyof UseCaseMap] | UseCaseString; searchQuery?: string; insertion?: Array<Insertion>; deliveryConfig?: DeliveryConfig; properties?: Properties; paging?: Paging; clientInfo?: ClientInfo; clientRequestId?: string; disablePersonalization?: boolean; } interface Paging { pagingId?: string; size?: number; cursor?: string; offset?: number; } interface PagingInfo { pagingId?: string; cursor?: string; } interface Response { requestId: string; insertion: Array<Insertion>; pagingInfo: PagingInfo; } interface DeliveryExecution { executionServer?: ExecutionServerMap[keyof ExecutionServerMap] | ExecutionServerString; serverVersion?: string; } interface ExecutionServerMap { UNKNOWN_EXECUTION_SERVER: 0; API: 1; SDK: 2; SIMPLE_API: 3; } type ExecutionServerString = 'UNKNOWN_EXECUTION_SERVER' | 'API' | 'SDK' | 'SIMPLE_API'; interface BlenderRule { ruleType?: BlenderRuleTypeMap[keyof BlenderRuleTypeMap] | BlenderRuleTypeString; priority?: number; properties?: Properties; } interface DeliveryConfig { blenderRule?: Array<BlenderRule>; } interface Insertion { platformId?: number; userInfo?: UserInfo; timing?: Timing; insertionId?: string; requestId?: string; viewId?: string; sessionId?: string; contentId?: string; position?: number; retrievalRank?: number; retrievalScore?: number; properties?: Properties; } interface UseCaseMap { UNKNOWN_USE_CASE: 0; CUSTOM: 1; SEARCH: 2; SEARCH_SUGGESTIONS: 3; FEED: 4; RELATED_CONTENT: 5; CLOSE_UP: 6; CATEGORY_CONTENT: 7; MY_CONTENT: 8; MY_SAVED_CONTENT: 9; SELLER_CONTENT: 10; } type UseCaseString = | 'UNKNOWN_USE_CASE' | 'CUSTOM' | 'SEARCH' | 'SEARCH_SUGGESTIONS' | 'FEED' | 'RELATED_CONTENT' | 'CLOSE_UP' | 'CATEGORY_CONTENT' | 'MY_CONTENT' | 'MY_SAVED_CONTENT' | 'SELLER_CONTENT'; declare const UseCase: UseCaseMap; interface BlenderRuleTypeMap { UNKNOWN_RULE_TYPE: 0; POSITIVE: 1; INSERT: 2; NEGATIVE: 3; DIVERSITY: 4; } type BlenderRuleTypeString = 'UNKNOWN_RULE_TYPE' | 'POSITIVE' | 'INSERT' | 'NEGATIVE' | 'DIVERSITY'; declare const BlenderRuleType: BlenderRuleTypeMap; // package: event // file: proto/event/event.proto interface CohortMembership { platformId?: number; userInfo?: UserInfo; timing?: Timing; membershipId?: string; cohortId?: string; arm?: CohortArmMap[keyof CohortArmMap] | CohortArmString; properties?: Properties; } interface LogRequest { platformId?: number; userInfo?: UserInfo; timing?: Timing; clientInfo?: ClientInfo; cohortMembership?: Array<CohortMembership>; deliveryLog?: Array<DeliveryLog>; } type LogResponse = object; interface CohortArmMap { UNKNOWN_GROUP: 0; CONTROL: 1; TREATMENT: 2; TREATMENT1: 3; TREATMENT2: 4; TREATMENT3: 5; } declare const CohortArm: CohortArmMap; type CohortArmString = 'UNKNOWN_GROUP' | 'CONTROL' | 'TREATMENT' | 'TREATMENT1' | 'TREATMENT2' | 'TREATMENT3'; /** * Arguments for the client so values can be overriden */ interface PromotedClientArguments { /** * A way to turn off logging. Defaults to true. */ enabled?: boolean; /** * The client used to call Delivery API. No default so we can reduce network * dependencies on the core library. */ deliveryClient: ApiClient<Request, Response>; /** * The client used to call Metrics API. No default so we can reduce network * dependencies on the core library. */ metricsClient: ApiClient<LogRequest, LogResponse>; /** * Whether to validate Requests. Safer but slower. Defaults to true. */ validateRequests?: boolean; /** * Rate (in the range 0.0-1.0) of logging traffic to forward to * the Delivery API for use as shadow traffic. 0.0 does no forwarding, * and 1.0 forwards every request. Does not happen if a Delivery API * call is attempted. */ shadowTrafficDeliveryRate?: number; /** * Option to make shadow traffic a blocking (as opposed to background) * call to delivery API, defaults to False. */ blockingShadowTraffic?: boolean; /** * Default values to use on DeliveryRequests. */ defaultRequestValues?: BaseRequest; /** * Handles errors. * Exposed to give clients flexibility into how errors are handled. * E.g. in dev, throw an error. In prod, silently log and monitor. * * Example NextJS code: * ``` * const throwError = * process?.env?.NODE_ENV !== 'production' || * (typeof location !== "undefined" && location?.hostname === "localhost"); * ... * handleError: throwError ? (err) => { * throw error; * } : (err) => console.error(err); * ``` */ handleError: ErrorHandler; /** * Required as a dependency so clients can load reduce dependency on multiple * uuid libraries. */ uuid: () => string; deliveryTimeoutMillis?: number; metricsTimeoutMillis?: number; /** * Allows for customizing when the treatment gets applied. */ shouldApplyTreatment?: (cohortMembership: CohortMembership | undefined) => boolean; /** * For testing. Allows for easy mocking of the clock. */ nowMillis?: () => number; /** * Exposed for testing, easy mocking of request sampling. */ sampler?: Sampler; /** * For testing. Used by unit tests to swap out timeout functionality. */ deliveryTimeoutWrapper?: <T>(promise: Promise<T>, timeoutMillis: number) => Promise<T>; /** * For testing. Used by unit tests to swap out timeout functionality. */ metricsTimeoutWrapper?: <T>(promise: Promise<T>, timeoutMillis: number) => Promise<T>; /** * The maximum number of request insertions that will be passed to (and returned from) * Delivery API. */ maxRequestInsertions?: number; } /** * Indicates where response insertions were generated by Promoted. */ declare enum ExecutionServer { /** * Response insertions were generated by the Promoted API service. */ API = 1, /** * Response insertions were generated on the client side by the Promoted SDK. */ SDK = 2 } /** * A shared response for Metrics or Delivery API. Makes it easy to swap * out either method. * * Has two main uses: * 1) return a modified list of Insertions. * 2) clients must call the log method after they send their content back * to the client. They call either `ClientResponse.log` or the `log` helper * method (which hides the Promise). */ interface ClientResponse { /** * Sends the log records to Metrics API. * Clients need to call this one of the log methods, preferrably after they * send the response to their UI/apps. */ log: () => Promise<void>; /** * A LogRequest suitable for calling the metrics client. * Undefined means we do not need any follow up logging */ logRequest?: LogRequest; /** * A list of the response Insertions. This list may be truncated * based on paging parameters, i.e. if Deliver is called with more * items than any optionally provided Paging.size parameter on the * request, at most page size insertions will be forwarded on. */ responseInsertions: Insertion[]; /** * Where the response insertions were generated (i.e. by the API or * locally in the SDK). Undefined on prepareForLogging responses. */ executionServer?: ExecutionServer; /** * The client request id, for tracking purposes. */ clientRequestId?: string; } /** * Represents a single call for retrieving and ranking content. */ interface DeliveryRequest { /** * The Request for content. */ request: Request; /** * Clients can send a subset of all request insertions to Promoted on * `request.insertion`. The `retrievalInsertionOffset` specifies the start index of * the array `request.insertion` in the list of all request insertions. * * `request.paging.offset` should be set to the zero-based position in all * request insertions (not the relative position in `request.insertion`s). * * Examples: * a. If there are 10 items and all 10 items are in `request.insertion`, then * retrievalInsertionOffset=0. * b. If there are 10,000 items and the first 500 items are on `request.insertion`, * then retrievalInsertionOffset=0. * c. If there are 10,000 items and we want to send items [500,1000) on * `request.insertion`, then retrievalInsertionOffset=500. * d. If there are 10,000 items and we want to send the last page [9500,10000) * on `request.insertion`, then retrievalInsertionOffset=9500. * * This field is required because an incorrect value could result in a bad bug. * If you only send the first X request insertions, then retrievalInsertionOffset=0. * * If you are only sending the first X insertions to Promoted, you can set * retrievalInsertionOffset=0. * * For now, Promoted requires that `retrievalInsertionOffset <= paging.offset`. * This will reduce the chance of errors and allow the SDK to fallback to * * Promoted recommends that the block size is a multiple of the page size. * This reduces the chance of page size issues. * * Follow this link for more details. * https://docs.promoted.ai/docs/ranking-requests#sending-even-more-request-insertions */ retrievalInsertionOffset: number; /** * A way to customize when `deliver` should not run an experiment and just log * (CONTROL) vs run through the experiment deliver code path. * Defaults to false. */ onlyLog?: boolean; /** * Used to run a client-side experiment. Activation happens if other * overrides do not disable it (`enabled=false` or `onlyLog=true`). * * If undefined, no experiment is run. By default, Delivery API is called. * * If set, this is runs `deliver` as a client-side experiment. The CONTROL * arm does not call Delivery API. It only logs. The TREATMENT arm uses * Delivery API to change Insertions. * * This CohortMembership is also logged to Metrics. */ experiment?: CohortMembership; } declare const SERVER_VERSION = "ts.13.1.0"; /** * Traffic types * TODO: Ideally these should come from common.d.ts but that needs a more * sophisticated proto translation than what we have now. */ declare const TrafficType_UNKNOWN_TRAFFIC_TYPE = 0; declare const TrafficType_PRODUCTION = 1; declare const TrafficType_REPLAY = 2; declare const TrafficType_SHADOW = 4; declare const ClientType_UNKNOWN_REQUEST_CLIENT = 0; declare const ClientType_PLATFORM_SERVER = 1; declare const ClientType_PLATFORM_CLIENT = 2; /** * The main interface for our Promoted Client. */ interface PromotedClient { /** * Used to call Delivery API or Metrics API. Takes the inputted list of Content and * ranks it. Supports running conditionally. */ deliver(deliveryRequest: DeliveryRequest): Promise<ClientResponse>; /** * Indicates whether this is client is performing actions or not. */ enabled: boolean; } /** * A utilty method for logging and ignoring the response. */ declare const log: (clientResponse: ClientResponse) => void; /** * Noop function. Mostly to increase code coverage. */ declare const noopFn: () => void; /** * Create PromotedClients. */ declare const newPromotedClient: (args: PromotedClientArguments) => NoopPromotedClient | PromotedClientImpl; /** * Used when clients want to disable all functionality. */ declare class NoopPromotedClient implements PromotedClient { private pager; constructor(); deliver(deliveryRequest: DeliveryRequest): Promise<ClientResponse>; get enabled(): boolean; } /** * A PromotedClient implementation that calls Promoted's APIs. */ declare class PromotedClientImpl implements PromotedClient { private deliveryClient; private metricsClient; private validateRequests; private shadowTrafficDeliveryRate; private blockingShadowTraffic; private defaultRequestValues; private handleError; private uuid; private deliveryTimeoutMillis; private metricsTimeoutMillis; private shouldApplyTreatment; private sampler; private pager; private maxRequestInsertions; private nowMillis; private deliveryTimeoutWrapper; private metricsTimeoutWrapper; /** * @params {DeliveryClientArguments} args The arguments for Promoted client creation. */ constructor(args: PromotedClientArguments); get enabled(): boolean; /** * Used to optimize a list of content. This function modifies deliveryRequest. */ deliver(deliveryRequest: DeliveryRequest): Promise<ClientResponse>; /** * Applies logic to determine whether or not this request should be forwarded * to Delivery API as shadow traffic. * @returns true if we should forward, false otherwise. */ private shouldSendAsShadowTraffic; /** Calls Delivery API and validates the response. */ private callDelivery; /** * Creates a non-blocking shadow traffic request to delivery. * @param request the underlying request. */ private deliverNonBlockingShadowTraffic; /** * Creates a blocking shadow traffic request to delivery. * @param request the underlying request. */ private deliverBlockingShadowTraffic; /** * On-demand creation of a LogRequest suitable for sending to the metrics client. * @param request used to get common fields from. * @returns a function to create a LogRequest on demand. */ private createLogRequest; private createSdkDeliveryLog; /** * Creates a function that can be used after sending the response. */ private createLogFn; private fillInRequestFields; private handleRequestError; } /** * Represents a two arm Experiment configuration. * * WARNING - while ramping up an experiment, do not change * numControlBuckets or numTreatmentBuckets. This will * likely produce bad results. * * For the buckets, the treatment buckets are after the * control buckets. */ interface TwoArmExperimentConfig { /** Name of cohort. */ cohortId: string; /** Num of the numControlBuckets that are active. */ numActiveControlBuckets: number; numControlBuckets: number; /** Num of the numTreatmentBuckets that are active. */ numActiveTreatmentBuckets: number; numTreatmentBuckets: number; } /** * A version of TwoArmExperimentConfig with extra properties. */ interface ProcessedTwoArmExperimentConfig extends TwoArmExperimentConfig { /** A hash of the cohortId. */ cohortIdHash: number; /** numControlBuckets + numTreatmentBuckets */ numTotalBuckets: number; } /** * Create a simple 50-50 Experiment config using 100 userId buckets. * @param cohortId name of the experiment * @param controlPercent percent of total to activate into the control arm. Range=[0,50] * @param treatmentPercent percent of total to activate into the treatment arm. Range=[0,50] * @param numBuckets the number of user buckets. Defaults to 1k. * Change this value if you need more than 0.1 precision for percents. */ declare const twoArmExperimentConfig5050: (cohortId: string, controlPercent: number, treatmentPercent: number, numBuckets?: number) => ProcessedTwoArmExperimentConfig; /** * Runs checks and creates cached values. */ declare const prepareTwoArmExperimentConfig: (config: TwoArmExperimentConfig) => ProcessedTwoArmExperimentConfig; /** * Takes a userId and figures out the CohortMembership. * Returns undefined when a user is not activated into * an experiment. */ declare const twoArmExperimentMembership: (userId: string, config: ProcessedTwoArmExperimentConfig) => CohortMembership | undefined; /** * Returns a simple hash of a string. Base implementation is from Java. */ declare const hashCode: (input: string) => number; /** * Returns a simple combined hash of two other hashes. From Effective Java. */ declare const combineHash: (hash1: number, hash2: number) => number; declare const mod: (value: number, modulo: number) => number; interface HasInsertionId { insertionId: string | undefined | null; } /** * Returns a list of Content by mapping responseInsertions.content using * contentLookup. Skips content not found in contentLookup. */ declare const toContents: <T extends HasInsertionId>(responseInsertions: Insertion[], contentLookup: Record<string, T>) => T[]; /** * Returns a list of Content by mapping responseInsertions.content using * contentLookup. Skips missing contentIds and logs a warning. */ declare const toContentsWithoutInsertionId: <T>(responseInsertions: Insertion[], contentLookup: Record<string, any>) => T[]; export { type ApiClient, type BaseRequest, type BlenderRule, BlenderRuleType, type BlenderRuleTypeMap, type BlenderRuleTypeString, type ClientInfo, type ClientResponse, ClientType_PLATFORM_CLIENT, ClientType_PLATFORM_SERVER, ClientType_UNKNOWN_REQUEST_CLIENT, CohortArm, type CohortArmMap, type CohortArmString, type CohortMembership, type DeliveryConfig, type DeliveryRequest, type ErrorHandler, ExecutionServer, type Insertion, type LogRequest, type LogResponse, NoopPromotedClient, type ProcessedTwoArmExperimentConfig, type PromotedClient, type PromotedClientArguments, PromotedClientImpl, type Properties, type Request, type RequiredBaseRequest, type Response, SERVER_VERSION, type Timing, type TrafficType, TrafficType_PRODUCTION, TrafficType_REPLAY, TrafficType_SHADOW, TrafficType_UNKNOWN_TRAFFIC_TYPE, type TwoArmExperimentConfig, UseCase, type UseCaseMap, type UseCaseString, type UserInfo, combineHash, hashCode, log, logOnError, mod, newPromotedClient, noopFn, prepareTwoArmExperimentConfig, throwOnError, toContents, toContentsWithoutInsertionId, twoArmExperimentConfig5050, twoArmExperimentMembership };