UNPKG

hypertune

Version:

[Hypertune](https://www.hypertune.com/) is the most flexible platform for feature flags, A/B testing, analytics and app configuration. Built with full end-to-end type-safety, Git-style version control and local, synchronous, in-memory flag evaluation. Opt

260 lines (243 loc) 7.21 kB
import { defaultCacheSize, prodLogsEndpointUrl, prodEdgeCdnBaseUrl, uniqueId, sdkVersion, } from "../shared"; import { LogsHandler, CreateOptions, LocalLogger, LogLevel, ObjectValue, ObjectValueWithVariables, Query, } from "../shared/types"; import Context from "./Context"; import Node, { NodeProps } from "./Node"; import Logger from "./Logger"; import { isBrowser } from "./environment"; import getMetadata from "../shared/helpers/getMetadata"; import HypertuneEdgeInitDataProvider from "./initDataProviders/HypertuneEdgeInitDataProvider"; import VercelEdgeConfigInitDataProvider from "./initDataProviders/VercelEdgeConfigInitDataProvider"; import defaultLocalLogger from "../shared/helpers/defaultLocalLogger"; const minIntervalMs = 1_000; const defaultIntervalMs = 2_000; /** * TODO * - Remove `queryCode`; consolidate with `query`; benefit of having them * separate was `queryCode` can be codegen'd differently, but this is not * necessary * - Add default values to arguments where possible * - Move all arguments into `CreateOptions` with type parameters if they have * generated types, then codegen concrete `CreateSourceOptions` */ export default function create<T extends Node = Node>({ NodeConstructor = Node as new (props: NodeProps) => T, token, query = null, queryId, queryCode, variableValues = {}, override = null, options = {}, }: { NodeConstructor?: new (props: NodeProps) => T; token: string; query?: Query<ObjectValueWithVariables> | null; queryId?: string; queryCode?: string; variableValues?: ObjectValue; override?: object | null; options?: CreateOptions; }): T { const traceId = uniqueId(); const loggerId = uniqueId(); const logsHandler = wrapLogsHandler( loggerId, options.logsHandler, options.localLogger ); const localLogger = wrapLocalLogger( loggerId, options.logsHandler, options.localLogger ); let logger: Logger; const remoteLoggingMode = options.remoteLogging?.mode ?? (isBrowser ? "session" : "normal"); try { logger = new Logger({ id: loggerId, traceId, token, remoteLoggingMode, remoteFlushIntervalMs: options.remoteLogging?.flushIntervalMs !== undefined ? options.remoteLogging.flushIntervalMs === null || options.remoteLogging.flushIntervalMs === 0 // TODO: Deprecate ? null : clampInterval(options.remoteLogging.flushIntervalMs) : defaultIntervalMs, remoteLoggingEndpointUrl: options.remoteLogging?.endpointUrl ?? prodLogsEndpointUrl, logsHandler, localLogger, }); } catch (error) { localLogger(LogLevel.Error, "Pre-initialize error.", getMetadata(error)); return new NodeConstructor({ context: null, logger: null, parent: null, step: null, expression: null, initDataHash: null, }); } const usesVercelEdgeConfig = options.initDataProvider && options.initDataProvider instanceof VercelEdgeConfigInitDataProvider; logger.log(LogLevel.Debug, null, "Detected environment.", { isBrowser, usesVercelEdgeConfig, }); try { const context = new Context({ traceId, query, initQuery: queryId ? { type: "StoredQuery", id: queryId } : queryCode ? { type: "GraphqlQuery", code: queryCode } : { type: "Query", query }, variableValues, initData: options.initData ?? null, lastInitDataRefreshTime: options.lastInitDataRefreshTime ?? options.lastDataProviderInitTime ?? null, initDataProvider: options.initDataProvider !== undefined ? options.initDataProvider : new HypertuneEdgeInitDataProvider({ token, baseUrl: prodEdgeCdnBaseUrl, branchName: options.branchName, localLogger, }), initDataRefreshIntervalMs: // Prioritize the new option else use the old option if it's not set to // 0 else use the default interval. In all cases, force the value to be // at least the minimum interval. clampInterval( options.initDataRefreshIntervalMs ?? (options.initIntervalMs || defaultIntervalMs) ), shouldRefreshInitData: // Prioritize the new option else if the old option is set to 0, // disable, else enable except when using Vercel Edge Config. options.shouldRefreshInitData ?? options.shouldCheckForUpdates ?? (options.initIntervalMs === 0 ? false : !usesVercelEdgeConfig), shouldRefreshInitDataOnCreate: options.shouldRefreshInitDataOnCreate ?? !usesVercelEdgeConfig, shouldSkipInitDataUpdateOnRefresh: options.shouldSkipInitDataUpdateOnRefresh ?? false, logger, cacheSize: options.cacheSize ?? defaultCacheSize, override: override ?? null, }); return new NodeConstructor({ context, logger, parent: null, step: null, expression: context.initData?.reducedExpression ?? null, initDataHash: context.initData?.hash ?? null, }); } catch (error) { logger.log( LogLevel.Error, /* commitId */ null, "Initialize error.", getMetadata(error) ); return new NodeConstructor({ context: null, logger, parent: null, step: null, expression: null, initDataHash: null, }); } } function clampInterval(intervalMs: number): number { return Math.max(intervalMs, minIntervalMs); } function wrapLogsHandler( loggerId: string, logsHandler: LogsHandler | undefined, localLogger: LocalLogger | undefined ): LogsHandler { if (!localLogger && !logsHandler) { return (reductionLogs) => { reductionLogs.messageList.forEach(({ level, message, metadata }) => defaultLocalLogger(level, message, metadata) ); }; } return (reductionLogs) => { const messages = !logsHandler && !localLogger ? reductionLogs.messageList : reductionLogs.messageList.map(({ level, message, metadata }) => ({ level, message, metadata: { sdkVersion, loggerId, ...metadata, }, })); if (logsHandler) { logsHandler({ ...reductionLogs, messageList: messages }); return; } if (localLogger) { messages.forEach(({ level, message, metadata }) => { localLogger(level, message, metadata); }); } }; } function wrapLocalLogger( loggerId: string, logsHandler: LogsHandler | undefined, localLogger: LocalLogger | undefined ): LocalLogger { if (!localLogger && !logsHandler) { return defaultLocalLogger; } return (level, message, baseMetadata) => { const metadata = { sdkVersion, loggerId, ...baseMetadata, }; if (logsHandler) { logsHandler({ messageList: [{ level, message, metadata }], eventList: [], exposureList: [], evaluationList: [], }); return; } if (localLogger) { localLogger(level, message, metadata); } }; }