UNPKG

@stuntman/shared

Version:

Stuntman - HTTP proxy / mock shared types and utils

253 lines (217 loc) 7.41 kB
/* eslint-disable @typescript-eslint/no-unsafe-function-type */ import config from './stuntmanConfig'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; export * from './constants'; export * from './appError'; export * from './logger'; export * from './stringify'; export * from './rawHeaders'; export * from './gqlParser'; export * from './escapeStringRegexp'; export * from './errorToLog'; import fs from 'fs'; export const stuntmanConfig = config; const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\/src$/, '/dist'); // TODO this file read sucks export const INDEX_DTS = fs.readFileSync(`${__dirname}/index.d.ts`, 'utf-8'); type NonObject = string | number | boolean | symbol | undefined | null | any[]; export type SharedProps<T1, T2> = Pick<T1 | T2, Extract<keyof T1, keyof T2>>; export type WithRequiredProperty<Type, Key extends keyof Type> = Type & { [Property in Key]-?: Type[Property]; }; interface SerializableTypesRecord<T> { [k: string | number]: T; } export type RecursivePartial<T> = { [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object ? RecursivePartial<T[P]> : T[P]; }; type SerializableTypes = | string | number | boolean | undefined | null | RegExp | SerializableTypes[] | SerializableTypesRecord<SerializableTypes>; export enum HttpCode { OK = 200, NO_CONTENT = 204, BAD_REQUEST = 400, UNAUTHORIZED = 401, NOT_FOUND = 404, CONFLICT = 409, UNPROCESSABLE_ENTITY = 422, INTERNAL_SERVER_ERROR = 500, } export type LocalVariables = Record<string, SerializableTypes>; export type RuleMatchResult = | boolean | { result: boolean; enableRuleIds?: string[]; disableRuleIds?: string[]; description?: string; labels?: string[] }; export type RemotableFunction<T extends Function> = { localFn: T; localVariables?: LocalVariables; }; export type SerializedRemotableFunction = { localFn: string; localVariables?: string; remoteFn: string; }; type WithRemotableFunctions<Type> = { [PropertyKey in keyof Type]: Extract<Type[PropertyKey], Function> extends never ? Exclude<Type[PropertyKey], NonObject> extends never ? Type[PropertyKey] : WithRemotableFunctions<Exclude<Type[PropertyKey], NonObject>> : Exclude<Type[PropertyKey], Function> | RemotableFunction<Extract<Type[PropertyKey], Function>>; }; export type WithSerializedFunctions<Type> = { [PropertyKey in keyof Type]: Extract<Type[PropertyKey], RemotableFunction<Function>> extends never ? Exclude<Type[PropertyKey], NonObject> extends never ? Type[PropertyKey] : WithSerializedFunctions<Exclude<Type[PropertyKey], NonObject>> : Exclude<Type[PropertyKey], RemotableFunction<Function>> | SerializedRemotableFunction; }; export interface RawHeadersInterface extends Array<string> { get: (name: string) => string | undefined; set: (name: string, value: string) => void; has: (name: string, value?: string) => boolean; add: (name: string, value: string) => void; remove: (name: string) => void; toHeaderPairs: () => readonly [string, string][]; } export const HTTP_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'] as const; export type HttpMethod = (typeof HTTP_METHODS)[number]; export type BaseRequest = { rawHeaders: RawHeadersInterface; url: string; body?: any; method: HttpMethod; }; export type Request = BaseRequest & { id: string; timestamp: number; gqlBody?: GQLRequestBody | undefined; jwt?: any | undefined; }; export type Response = { rawHeaders?: RawHeadersInterface; status?: number; body?: any; timestamp?: number; }; export type LogEntry = { originalRequest: Request; labels?: string[] | undefined; mockRuleId?: string; originalResponse?: Response; modifiedRequest?: Request; modifiedResponse?: Response; }; export type RuleMatchFunction = (request: Request) => RuleMatchResult; export type RequestManipulationFn = (request: Request) => Request | Promise<Request>; export type ResponseManipulationFn = (request: Request, response: Response) => Response | Promise<Response>; export type ResponseGenerationFn = (request: Request) => Response | Promise<Response>; export type Actions = | { proxyPass: true; mockResponse?: undefined; modifyRequest?: undefined; modifyResponse?: undefined; } | { mockResponse: Response | ResponseGenerationFn; proxyPass?: undefined; modifyRequest?: undefined; modifyResponse?: undefined; } | { modifyRequest: RequestManipulationFn; modifyResponse?: ResponseManipulationFn; proxyPass?: true | undefined; mockResponse?: undefined; } | { modifyRequest?: RequestManipulationFn; modifyResponse: ResponseManipulationFn; proxyPass?: true | undefined; mockResponse?: undefined; }; export type Rule = { id: string; priority?: number; matches: RuleMatchFunction; // function for picking request to process labels?: string[]; // groupping req/res pairs for searching later e.g. ['exoclick', 'testId123'] actions: Actions; disableAfterUse?: boolean | number; // disable after rule is triggered n-times removeAfterUse?: boolean | number; // disable after rule is triggered n-times ttlSeconds: number; storeTraffic?: boolean; isEnabled?: boolean; }; export type DeployedRule = Omit<Rule, 'disableAfterUse' | 'removeAfterUse' | 'ttlSeconds'>; export type SerializableRule = WithRemotableFunctions<Rule>; export type SerializedRule = WithSerializedFunctions<SerializableRule>; export type LiveRule = Rule & { counter: number; createdTimestamp: number; }; export type GQLRequestBody = { operationName: string; variables?: any; query: string; type: 'query' | 'mutation'; methodName?: string; }; export type WebGuiConfig = { disabled: boolean; }; export type ApiConfig = | { disabled: true } | { port: number; disabled: false; apiKeyReadWrite: string | null; apiKeyReadOnly: string | null; }; export type ClientConfig = { protocol: 'http' | 'https'; host: string; port: number; timeout: number; apiKey?: string; }; export type MockConfig = { domain: string; port: number; httpsPort?: number; httpsKey?: string; httpsCert?: string; timeout: number; externalDns: string[]; rulesPath: string; disableProxy?: boolean; }; export type StorageConfig = { limitCount: number; limitSize: number; ttl: number; }; export type Config = { webgui: WebGuiConfig; api: ApiConfig; mock: MockConfig; storage: { traffic: StorageConfig; }; client: ClientConfig; }; export interface RuleExecutorInterface { addRule: (rule: Rule, overwrite?: boolean) => Promise<LiveRule>; removeRule: (id: string) => Promise<void>; enableRule: (id: string) => void; disableRule: (id: string) => void; findMatchingRule: (request: Request) => Promise<LiveRule | null>; getRules: () => Promise<readonly LiveRule[]>; getRule: (id: string) => Promise<LiveRule | undefined>; }