hypertune
Advanced tools
Comparing version 2.2.5 to 2.3.0-alpha.0
# Changelog | ||
## 2.3.0 | ||
- Traceability and shutdown behavior improvements. | ||
## 2.2.5 | ||
@@ -4,0 +8,0 @@ |
@@ -68,3 +68,3 @@ "use strict"; | ||
}) | ||
.option("--framework [value]", "Framework (nextApp, nextAppFlags, nextPages, react, remix or gatsby)") | ||
.option("--framework [value]", "Framework (nextApp, nextPages, react, remix or gatsby)") | ||
.option("--platform [value]", "Platform (vercel)") | ||
@@ -71,0 +71,0 @@ .option("--getHypertuneImportPath [value]", "Relative import path for a file with a default export of the `getHypertune` function, which takes a single object argument containing readonly `headers` and `cookies`; only used for the nextApp framework and vercel platform"); |
@@ -0,1 +1,2 @@ | ||
/// <reference types="node" /> | ||
import { InitData, Expression, Query, Value, ObjectValueWithVariables, UpdateListener, ReductionLogs, DehydratedState, DeepPartial, InitDataProvider, ObjectValue } from "../shared"; | ||
@@ -12,4 +13,5 @@ import Logger from "./Logger"; | ||
protected readonly updateListeners: Map<UpdateListener, boolean>; | ||
protected shouldClose: boolean; | ||
protected updateTimeout: NodeJS.Timeout | null; | ||
readonly logger: Logger; | ||
shouldClose: boolean; | ||
initData: InitData | null; | ||
@@ -24,4 +26,6 @@ lastDataProviderInitTime: number | null; | ||
override: DeepPartial<ObjectValue> | null; | ||
constructor({ initData, initDataProvider, initDataRefreshIntervalMs, shouldCheckForUpdates, query, queryCode, variableValues, logger, loggerFlushIntervalMs, cacheSize, override, }: { | ||
constructor({ traceId, initData, lastDataProviderInitTime, initDataProvider, initDataRefreshIntervalMs, shouldCheckForUpdates, query, queryCode, variableValues, logger, cacheSize, override, }: { | ||
traceId: string; | ||
initData: InitData | null; | ||
lastDataProviderInitTime: number | null; | ||
initDataProvider: InitDataProvider | null; | ||
@@ -34,3 +38,2 @@ initDataRefreshIntervalMs: number; | ||
logger: Logger; | ||
loggerFlushIntervalMs: number | null; | ||
cacheSize: number; | ||
@@ -47,2 +50,3 @@ override: object | null; | ||
isReady(): boolean; | ||
close(traceId: string): Promise<void>; | ||
getStateHash(): string; | ||
@@ -55,2 +59,3 @@ addUpdateListener(listener: UpdateListener): void; | ||
hydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>(traceId: string, dehydratedState: DehydratedState<TOverride, TVariableValues>): void; | ||
private setLastDataProviderInitTime; | ||
reduce(query: Query<ObjectValueWithVariables> | null, expression: Expression): Expression; | ||
@@ -57,0 +62,0 @@ private log; |
@@ -23,5 +23,17 @@ "use strict"; | ||
// eslint-disable-next-line max-params | ||
constructor({ initData, initDataProvider, initDataRefreshIntervalMs, shouldCheckForUpdates, query, queryCode, variableValues, logger, loggerFlushIntervalMs, cacheSize, override, }) { | ||
constructor({ traceId, initData, lastDataProviderInitTime, initDataProvider, initDataRefreshIntervalMs, shouldCheckForUpdates, query, queryCode, variableValues, logger, cacheSize, override, }) { | ||
this.shouldClose = false; | ||
this.updateTimeout = null; | ||
// @internal | ||
this.shouldClose = false; | ||
this.initData = null; | ||
// @internal | ||
this.lastDataProviderInitTime = null; | ||
// @internal | ||
this.getFieldCache = null; | ||
// @internal | ||
this.getItemsCache = null; | ||
// @internal | ||
this.evaluateCache = null; | ||
// @internal | ||
this.override = null; | ||
this.initDataProvider = initDataProvider; | ||
@@ -34,7 +46,2 @@ this.initDataRefreshIntervalMs = initDataRefreshIntervalMs; | ||
this.logger = logger; | ||
this.initData = null; | ||
this.lastDataProviderInitTime = null; | ||
this.getFieldCache = null; | ||
this.getItemsCache = null; | ||
this.evaluateCache = null; | ||
this.override = override; | ||
@@ -46,7 +53,7 @@ if (cacheSize > 0) { | ||
} | ||
const traceId = (0, shared_1.uniqueId)(); | ||
if (initData) { | ||
this.initFromData(traceId, initData); | ||
this.setLastDataProviderInitTime(lastDataProviderInitTime); | ||
} | ||
this.initAndStartIntervals(shouldCheckForUpdates, loggerFlushIntervalMs); | ||
this.initAndStartIntervals(traceId, shouldCheckForUpdates); | ||
if (environment_1.isBrowser) { | ||
@@ -57,3 +64,3 @@ window.addEventListener("beforeunload", () => __awaiter(this, void 0, void 0, function* () { | ||
} | ||
yield this.logger.flush(/* traceId */ (0, shared_1.uniqueId)()); | ||
yield this.close(/* traceId */ (0, shared_1.uniqueId)()); | ||
})); | ||
@@ -201,5 +208,5 @@ } | ||
} | ||
initAndStartIntervals(shouldCheckForUpdates, loggerFlushIntervalMs) { | ||
initAndStartIntervals(initTraceId, shouldCheckForUpdates) { | ||
if (!this.initDataProvider || !shouldCheckForUpdates) { | ||
this.log(/* traceId */ "", shared_1.LogLevel.Info, "Not polling for updates."); | ||
this.log(initTraceId, shared_1.LogLevel.Info, "Not polling for updates."); | ||
} | ||
@@ -210,2 +217,8 @@ if (this.initDataProvider) { | ||
const traceId = (0, shared_1.uniqueId)(); | ||
if (this.shouldClose) { | ||
// No need to get updates when the SDK is shutting down. | ||
this.updateTimeout = null; | ||
this.log(traceId, shared_1.LogLevel.Debug, "Stopped getting updates from the data provider."); | ||
return; | ||
} | ||
this.initIfNeeded(traceId, shared_1.defaultRetries) | ||
@@ -215,8 +228,8 @@ .catch((error) => this.log(traceId, shared_1.LogLevel.Error, "Error updating from data provider.", (0, getMetadata_1.default)(error))) | ||
if (this.shouldClose) { | ||
this.log( | ||
/* traceId */ "", shared_1.LogLevel.Debug, "Stopped getting updates from the data provider."); | ||
this.updateTimeout = null; | ||
this.log(traceId, shared_1.LogLevel.Debug, "Stopped getting updates from the data provider."); | ||
return; | ||
} | ||
if (shouldCheckForUpdates) { | ||
setTimeout(update, this.initDataRefreshIntervalMs); | ||
this.updateTimeout = setTimeout(update, this.initDataRefreshIntervalMs); | ||
} | ||
@@ -227,22 +240,2 @@ }); | ||
} | ||
// eslint-disable-next-line @hypertune/prefer-early-returns | ||
if (loggerFlushIntervalMs !== null) { | ||
// eslint-disable-next-line func-style | ||
const flushLogQueue = () => { | ||
setTimeout(() => __awaiter(this, void 0, void 0, function* () { | ||
if (this.shouldClose) { | ||
this.log( | ||
/* traceId */ "", shared_1.LogLevel.Debug, "Stopped flushing log queue."); | ||
return; | ||
} | ||
yield this.logger.flush(/* traceId */ (0, shared_1.uniqueId)()); | ||
flushLogQueue(); | ||
}), loggerFlushIntervalMs); | ||
}; | ||
flushLogQueue(); | ||
} | ||
else { | ||
this.log( | ||
/* traceId */ "", shared_1.LogLevel.Info, "Not automatically flushing logs."); | ||
} | ||
} | ||
@@ -256,2 +249,15 @@ // @internal | ||
// @internal | ||
close(traceId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.log(traceId, shared_1.LogLevel.Info, "Closing..."); | ||
this.shouldClose = true; | ||
if (this.updateTimeout) { | ||
clearTimeout(this.updateTimeout); | ||
this.updateTimeout = null; | ||
} | ||
yield this.logger.close(traceId); | ||
this.log(traceId, shared_1.LogLevel.Info, "Closed."); | ||
}); | ||
} | ||
// @internal | ||
getStateHash() { | ||
@@ -294,3 +300,3 @@ var _a, _b; | ||
} | ||
const { override } = this; | ||
const { lastDataProviderInitTime, override } = this; | ||
const dehydrateQuery = query !== null && query !== void 0 ? query : this.query; | ||
@@ -305,2 +311,3 @@ const dehydrateQueryVariableValues = variableValues !== null && variableValues !== void 0 ? variableValues : this.variableValues; | ||
initData, | ||
lastDataProviderInitTime, | ||
override: override, | ||
@@ -313,3 +320,3 @@ variableValues: dehydrateQueryVariableValues, | ||
this.log(traceId, shared_1.LogLevel.Info, "Hydrating..."); | ||
const { initData, override, variableValues } = dehydratedState; | ||
const { initData, override, variableValues, lastDataProviderInitTime } = dehydratedState; | ||
if (initData && variableValues) { | ||
@@ -319,9 +326,13 @@ this.variableValues = variableValues; | ||
this.initFromData(traceId, initData); | ||
if (this.initDataProvider) { | ||
// Set the last initialization time as hydration should only be used with | ||
// the latest available data. | ||
this.lastDataProviderInitTime = Date.now(); | ||
} | ||
this.setLastDataProviderInitTime(lastDataProviderInitTime); | ||
this.setOverride(traceId, override); | ||
} | ||
setLastDataProviderInitTime(newTime) { | ||
if (!this.initDataProvider || !newTime) { | ||
return; | ||
} | ||
// Don't set the time to a future value. | ||
const now = Date.now(); | ||
this.lastDataProviderInitTime = newTime > now ? now : newTime; | ||
} | ||
// @internal | ||
@@ -328,0 +339,0 @@ reduce(query, expression) { |
@@ -16,3 +16,4 @@ "use strict"; | ||
function create({ NodeConstructor, token, query, queryCode, variableValues, override, options, }) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; | ||
const traceId = (0, shared_1.uniqueId)(); | ||
let logger; | ||
@@ -22,5 +23,12 @@ const remoteLoggingMode = (_b = (_a = options.remoteLogging) === null || _a === void 0 ? void 0 : _a.mode) !== null && _b !== void 0 ? _b : (environment_1.isBrowser ? "session" : "normal"); | ||
logger = new Logger_1.default({ | ||
traceId, | ||
token, | ||
remoteLoggingMode, | ||
remoteLoggingEndpointUrl: (_d = (_c = options.remoteLogging) === null || _c === void 0 ? void 0 : _c.endpointUrl) !== null && _d !== void 0 ? _d : shared_1.prodLogsEndpointUrl, | ||
remoteFlushIntervalMs: ((_c = options.remoteLogging) === null || _c === void 0 ? void 0 : _c.flushIntervalMs) !== undefined | ||
? options.remoteLogging.flushIntervalMs === null || | ||
options.remoteLogging.flushIntervalMs === 0 // TODO: Deprecate | ||
? null | ||
: clampInterval(options.remoteLogging.flushIntervalMs) | ||
: defaultIntervalMs, | ||
remoteLoggingEndpointUrl: (_e = (_d = options.remoteLogging) === null || _d === void 0 ? void 0 : _d.endpointUrl) !== null && _e !== void 0 ? _e : shared_1.prodLogsEndpointUrl, | ||
localLogger: options.localLogger, | ||
@@ -48,6 +56,8 @@ }); | ||
const context = new Context_1.default({ | ||
traceId, | ||
query, | ||
queryCode, | ||
variableValues, | ||
initData: (_e = options.initData) !== null && _e !== void 0 ? _e : null, | ||
initData: (_f = options.initData) !== null && _f !== void 0 ? _f : null, | ||
lastDataProviderInitTime: (_g = options.lastDataProviderInitTime) !== null && _g !== void 0 ? _g : null, | ||
initDataProvider: options.initDataProvider !== undefined | ||
@@ -65,20 +75,9 @@ ? options.initDataProvider | ||
// at least the minimum interval. | ||
clampInterval((_f = options.initDataRefreshIntervalMs) !== null && _f !== void 0 ? _f : (options.initIntervalMs || defaultIntervalMs)), | ||
clampInterval((_h = options.initDataRefreshIntervalMs) !== null && _h !== void 0 ? _h : (options.initIntervalMs || defaultIntervalMs)), | ||
shouldCheckForUpdates: | ||
// Prioritize the new option else if the old option is set to 0, | ||
// disable, else enable except for Next.js servers. | ||
(_g = options.shouldCheckForUpdates) !== null && _g !== void 0 ? _g : (options.initIntervalMs === 0 ? false : !isNextJsBuildOrServer), | ||
(_j = options.shouldCheckForUpdates) !== null && _j !== void 0 ? _j : (options.initIntervalMs === 0 ? false : !isNextJsBuildOrServer), | ||
logger, | ||
loggerFlushIntervalMs: | ||
// Use the provided option else use the default interval but disable | ||
// flushing by default if remote logging mode is set to off. | ||
remoteLoggingMode === "off" | ||
? null | ||
: ((_h = options.remoteLogging) === null || _h === void 0 ? void 0 : _h.flushIntervalMs) !== undefined | ||
? options.remoteLogging.flushIntervalMs === null || | ||
options.remoteLogging.flushIntervalMs === 0 // TODO: Deprecate | ||
? null | ||
: clampInterval(options.remoteLogging.flushIntervalMs) | ||
: defaultIntervalMs, | ||
cacheSize: (_j = options.cacheSize) !== null && _j !== void 0 ? _j : shared_1.defaultCacheSize, | ||
cacheSize: (_k = options.cacheSize) !== null && _k !== void 0 ? _k : shared_1.defaultCacheSize, | ||
override: override !== null && override !== void 0 ? override : null, | ||
@@ -91,3 +90,3 @@ }); | ||
step: null, | ||
expression: (_l = (_k = context.initData) === null || _k === void 0 ? void 0 : _k.reducedExpression) !== null && _l !== void 0 ? _l : null, | ||
expression: (_m = (_l = context.initData) === null || _l === void 0 ? void 0 : _l.reducedExpression) !== null && _m !== void 0 ? _m : null, | ||
}); | ||
@@ -94,0 +93,0 @@ } |
import { ReductionLogs, Expression, LocalLogger, LogLevel } from "../shared"; | ||
import { RemoteLoggingMode } from "../shared/types"; | ||
/** | ||
* Logger provides a high level API for Node and generic SDK logs. It emits | ||
* these logs using the local logger and it also forwards them to the | ||
* `RemoteLogger` based on the selected `RemoteLoggingMode`. | ||
*/ | ||
export default class Logger { | ||
@@ -9,5 +14,7 @@ readonly id: string; | ||
private readonly localLogger; | ||
constructor({ token, remoteLoggingMode, remoteLoggingEndpointUrl, localLogger, }: { | ||
constructor({ traceId, token, remoteLoggingMode, remoteFlushIntervalMs, remoteLoggingEndpointUrl, localLogger, }: { | ||
traceId: string; | ||
token: string; | ||
remoteLoggingMode: RemoteLoggingMode; | ||
remoteFlushIntervalMs: number | null; | ||
remoteLoggingEndpointUrl: string; | ||
@@ -27,6 +34,7 @@ localLogger?: LocalLogger; | ||
}): void; | ||
private shouldRemoteLog; | ||
private shouldRemoteNodeLog; | ||
log(level: LogLevel, commitId: string | null, message: string, metadata: object): void; | ||
flush(traceId: string): Promise<void>; | ||
close(traceId: string): Promise<void>; | ||
} | ||
//# sourceMappingURL=Logger.d.ts.map |
@@ -16,3 +16,3 @@ "use strict"; | ||
const shared_1 = require("../shared"); | ||
const BackendRemoteLogger_1 = __importDefault(require("../shared/helpers/BackendRemoteLogger")); | ||
const RemoteLogger_1 = __importDefault(require("../shared/helpers/RemoteLogger")); | ||
const toConsoleFunction_1 = __importDefault(require("../shared/helpers/toConsoleFunction")); | ||
@@ -23,15 +23,19 @@ const LRUCache_1 = __importDefault(require("../shared/helpers/LRUCache")); | ||
const newTracedFetch_1 = __importDefault(require("../shared/helpers/newTracedFetch")); | ||
const NoopRemoteLogger_1 = __importDefault(require("../shared/helpers/NoopRemoteLogger")); | ||
const fetchMaxKeepAliveRequestSizeBytes = 64000; | ||
/** | ||
* Logger provides a high level API for Node and generic SDK logs. It emits | ||
* these logs using the local logger and it also forwards them to the | ||
* `RemoteLogger` based on the selected `RemoteLoggingMode`. | ||
*/ | ||
class Logger { | ||
constructor({ token, remoteLoggingMode, remoteLoggingEndpointUrl, localLogger, }) { | ||
constructor({ traceId, token, remoteLoggingMode, remoteFlushIntervalMs, remoteLoggingEndpointUrl, localLogger, }) { | ||
this.remoteLogCache = new LRUCache_1.default(10000); | ||
this.id = (0, shared_1.uniqueId)(); | ||
this.remoteLoggingMode = | ||
remoteLoggingMode === "off" ? "normal" : remoteLoggingMode; | ||
this.remoteLoggingMode = remoteLoggingMode; | ||
this.localLogger = wrapLocalLogger(this.id, localLogger); | ||
this.remoteLogger = | ||
remoteLoggingMode === "off" | ||
? (0, NoopRemoteLogger_1.default)() | ||
: new BackendRemoteLogger_1.default({ | ||
? null | ||
: new RemoteLogger_1.default({ | ||
traceId, | ||
token, | ||
@@ -43,5 +47,7 @@ createLogs: getCreateLogsFunction((0, newTracedFetch_1.default)({ | ||
localLogger: this.localLogger, | ||
flushIntervalMs: remoteFlushIntervalMs, | ||
}); | ||
} | ||
nodeLog({ commitId, initDataHash, nodeTypeName, nodePath, nodeExpression, level, message, metadata, reductionLogs, }) { | ||
var _a, _b, _c, _d, _e; | ||
const logMessage = `${nodeTypeName}Node at ${nodePath}: ${message}`; | ||
@@ -55,4 +61,4 @@ const logMetadata = Object.assign({ sdkVersion: shared_1.sdkVersion, | ||
if ((level === shared_1.LogLevel.Warn || level === shared_1.LogLevel.Error) && | ||
this.shouldRemoteLog(initDataHash, nodePath, message)) { | ||
this.remoteLogger.log(shared_1.LogType.SdkNode, level, commitId, logMessage, logMetadata); | ||
this.shouldRemoteNodeLog(initDataHash, nodePath, message)) { | ||
(_a = this.remoteLogger) === null || _a === void 0 ? void 0 : _a.log(shared_1.LogType.SdkNode, level, commitId, logMessage, logMetadata); | ||
} | ||
@@ -63,18 +69,18 @@ if (reductionLogs) { | ||
this.localLogger(shared_1.LogLevel.Error, errorMessage, logMetadata); | ||
if (this.shouldRemoteLog(initDataHash, nodePath, message)) { | ||
this.remoteLogger.log(shared_1.LogType.SdkNode, level, commitId, errorMessage, logMetadata); | ||
if (this.shouldRemoteNodeLog(initDataHash, nodePath, message)) { | ||
(_b = this.remoteLogger) === null || _b === void 0 ? void 0 : _b.log(shared_1.LogType.SdkNode, level, commitId, errorMessage, logMetadata); | ||
} | ||
return; | ||
} | ||
if (this.shouldRemoteLog(initDataHash, nodePath, "evaluations")) { | ||
this.remoteLogger.evaluations(commitId, reductionLogs.evaluations); | ||
if (this.shouldRemoteNodeLog(initDataHash, nodePath, "evaluations")) { | ||
(_c = this.remoteLogger) === null || _c === void 0 ? void 0 : _c.evaluations(commitId, reductionLogs.evaluations); | ||
} | ||
// We always log events to the backend | ||
this.remoteLogger.events(commitId, reductionLogs.events); | ||
if (this.shouldRemoteLog(initDataHash, nodePath, "exposures")) { | ||
this.remoteLogger.exposures(commitId, reductionLogs.exposures); | ||
(_d = this.remoteLogger) === null || _d === void 0 ? void 0 : _d.events(commitId, reductionLogs.events); | ||
if (this.shouldRemoteNodeLog(initDataHash, nodePath, "exposures")) { | ||
(_e = this.remoteLogger) === null || _e === void 0 ? void 0 : _e.exposures(commitId, reductionLogs.exposures); | ||
} | ||
} | ||
} | ||
shouldRemoteLog(initDataHash, nodePath, cacheKeySuffix) { | ||
shouldRemoteNodeLog(initDataHash, nodePath, cacheKeySuffix) { | ||
switch (this.remoteLoggingMode) { | ||
@@ -94,2 +100,5 @@ case "session": { | ||
} | ||
case "off": { | ||
return false; | ||
} | ||
default: { | ||
@@ -102,10 +111,19 @@ const neverLoggingMode = this.remoteLoggingMode; | ||
log(level, commitId, message, metadata) { | ||
var _a; | ||
this.localLogger(level, message, metadata); | ||
if (level === shared_1.LogLevel.Warn || level === shared_1.LogLevel.Error) { | ||
this.remoteLogger.log(shared_1.LogType.SdkMessage, level, commitId, message, Object.assign(Object.assign({}, metadata), { sdkVersion: shared_1.sdkVersion })); | ||
if (this.remoteLoggingMode !== "off" && | ||
(level === shared_1.LogLevel.Warn || level === shared_1.LogLevel.Error)) { | ||
(_a = this.remoteLogger) === null || _a === void 0 ? void 0 : _a.log(shared_1.LogType.SdkMessage, level, commitId, message, Object.assign(Object.assign({}, metadata), { sdkVersion: shared_1.sdkVersion })); | ||
} | ||
} | ||
flush(traceId) { | ||
return this.remoteLogger.flush(traceId); | ||
return this.remoteLogger | ||
? this.remoteLogger.flush(traceId) | ||
: Promise.resolve(); | ||
} | ||
close(traceId) { | ||
return this.remoteLogger | ||
? this.remoteLogger.close(traceId) | ||
: Promise.resolve(); | ||
} | ||
} | ||
@@ -112,0 +130,0 @@ exports.default = Logger; |
@@ -40,7 +40,11 @@ "use strict"; | ||
function mergeObjectSource(value, override) { | ||
return Object.fromEntries(Object.entries(value).map(([fieldName, field]) => [ | ||
return Object.fromEntries(Object.entries(value) | ||
.map(([fieldName, field]) => [ | ||
fieldName, | ||
mergeSource(field, override[fieldName]), | ||
])); | ||
]) | ||
.concat( | ||
// Add any additional fields from the override to the object. | ||
Object.entries(override).filter(([fieldName]) => !(fieldName in value)))); | ||
} | ||
//# sourceMappingURL=merge.js.map |
@@ -63,3 +63,4 @@ "use strict"; | ||
}); | ||
(0, vitest_1.expect)((0, merge_1.default)({ a: 1, b: "str" }, { c: "val" })).toEqual({ a: 1, b: "str", c: "val" }); | ||
}); | ||
//# sourceMappingURL=merge.test.js.map |
@@ -53,4 +53,8 @@ import { Expression, ObjectValue, Query, Step, Value, UpdateListener, ReductionLogs, DehydratedState, DeepPartial, ObjectValueWithVariables } from "../shared"; | ||
hydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>(dehydratedState: DehydratedState<TOverride, TVariableValues>, traceId?: string): void; | ||
close(): void; | ||
/** | ||
* Close flushes any remaining logs and stops all background processes | ||
* ensuring clean shutdown. | ||
*/ | ||
close(traceId?: string): Promise<void>; | ||
} | ||
//# sourceMappingURL=Node.d.ts.map |
@@ -369,9 +369,13 @@ "use strict"; | ||
} | ||
close() { | ||
/** | ||
* Close flushes any remaining logs and stops all background processes | ||
* ensuring clean shutdown. | ||
*/ | ||
close(traceId = (0, shared_1.uniqueId)()) { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(shared_1.LogLevel.Error, "No context so cannot close."); | ||
return; | ||
return Promise.resolve(); | ||
} | ||
context.shouldClose = true; | ||
return context.close(traceId); | ||
} | ||
@@ -378,0 +382,0 @@ } |
export { default as asError } from "./asError"; | ||
export { default as BackendRemoteLogger } from "./BackendRemoteLogger"; | ||
export { default as RemoteLogger } from "./RemoteLogger"; | ||
export { default as emptyThrows } from "./emptyThrows"; | ||
@@ -4,0 +4,0 @@ export { default as evaluate, complexFormExpressionEvaluationError, } from "./evaluate"; |
@@ -20,7 +20,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getSplitsAndCommitConfigForExpression = exports.uniqueId = exports.toWords = exports.toStartCaseWords = exports.toStartCase = exports.toInt = exports.toDimensionTypeEnum = exports.throws = exports.stableStringify = exports.reduce = exports.prefixError = exports.nullThrows = exports.nanThrows = exports.mapExpressionWithResult = exports.mapExpression = exports.fold = exports.getNextName = exports.hash = exports.complexFormExpressionEvaluationError = exports.evaluate = exports.emptyThrows = exports.BackendRemoteLogger = exports.asError = void 0; | ||
exports.getSplitsAndCommitConfigForExpression = exports.uniqueId = exports.toWords = exports.toStartCaseWords = exports.toStartCase = exports.toInt = exports.toDimensionTypeEnum = exports.throws = exports.stableStringify = exports.reduce = exports.prefixError = exports.nullThrows = exports.nanThrows = exports.mapExpressionWithResult = exports.mapExpression = exports.fold = exports.getNextName = exports.hash = exports.complexFormExpressionEvaluationError = exports.evaluate = exports.emptyThrows = exports.RemoteLogger = exports.asError = void 0; | ||
var asError_1 = require("./asError"); | ||
Object.defineProperty(exports, "asError", { enumerable: true, get: function () { return __importDefault(asError_1).default; } }); | ||
var BackendRemoteLogger_1 = require("./BackendRemoteLogger"); | ||
Object.defineProperty(exports, "BackendRemoteLogger", { enumerable: true, get: function () { return __importDefault(BackendRemoteLogger_1).default; } }); | ||
var RemoteLogger_1 = require("./RemoteLogger"); | ||
Object.defineProperty(exports, "RemoteLogger", { enumerable: true, get: function () { return __importDefault(RemoteLogger_1).default; } }); | ||
var emptyThrows_1 = require("./emptyThrows"); | ||
@@ -27,0 +27,0 @@ Object.defineProperty(exports, "emptyThrows", { enumerable: true, get: function () { return __importDefault(emptyThrows_1).default; } }); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** Replaced by the value in package.json on build */ | ||
exports.default = "2.2.5"; | ||
exports.default = "2.3.0-alpha.0"; | ||
//# sourceMappingURL=sdkVersion.js.map |
@@ -545,9 +545,2 @@ import { isQueryVariableKey } from "./constants"; | ||
export type UpdateListener = (newStateHash: string) => void; | ||
export type RemoteLogger = { | ||
log(type: LogType, level: LogLevel, commitId: string | null, message: string, metadata: object): void; | ||
evaluations(commitId: string, evaluations: CountMap): void; | ||
events(commitId: string, events: CountMap): void; | ||
exposures(commitId: string, exposures: CountMap): void; | ||
flush(traceId: string): Promise<void>; | ||
}; | ||
export type TracedFetch = (traceId: string, url: string, requestInit: Omit<RequestInit, "headers"> & { | ||
@@ -603,2 +596,8 @@ headers: Record<string, string>; | ||
/** | ||
* `lastDataProviderInitTime` is a timestamp specifying the last time | ||
* the provided `initData` was last checked for freshness using an | ||
* `InitDataProvider`. It is ignored when no `initData` was provided. | ||
*/ | ||
lastDataProviderInitTime?: number | null; | ||
/** | ||
* `initDataRefreshIntervalMs` specifies the interval, in milliseconds, at | ||
@@ -689,2 +688,3 @@ * which the SDK checks for updates. The minimum allowed value is 1000ms. | ||
initData: InitData; | ||
lastDataProviderInitTime: number | null; | ||
override: DeepPartial<TOverride> | null; | ||
@@ -691,0 +691,0 @@ variableValues: TVariableValues; |
{ | ||
"name": "hypertune", | ||
"version": "2.2.5", | ||
"version": "2.3.0-alpha.0", | ||
"private": false, | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -103,3 +103,3 @@ import { CAC } from "cac"; | ||
"--framework [value]", | ||
"Framework (nextApp, nextAppFlags, nextPages, react, remix or gatsby)" | ||
"Framework (nextApp, nextPages, react, remix or gatsby)" | ||
) | ||
@@ -106,0 +106,0 @@ .option("--platform [value]", "Platform (vercel)") |
@@ -39,25 +39,27 @@ import pRetry from "p-retry"; | ||
protected readonly updateListeners: Map<UpdateListener, boolean>; | ||
protected shouldClose = false; | ||
protected updateTimeout: NodeJS.Timeout | null = null; | ||
// @internal | ||
public readonly logger: Logger; | ||
// @internal | ||
public shouldClose = false; | ||
public initData: InitData | null = null; | ||
// @internal | ||
public initData: InitData | null; | ||
public lastDataProviderInitTime: number | null = null; | ||
// @internal | ||
public lastDataProviderInitTime: number | null; | ||
public readonly getFieldCache: LRUCache<Expression> | null = null; | ||
// @internal | ||
public readonly getFieldCache: LRUCache<Expression> | null; | ||
public readonly getItemsCache: LRUCache<Expression[]> | null = null; | ||
// @internal | ||
public readonly getItemsCache: LRUCache<Expression[]> | null; | ||
// @internal | ||
public readonly evaluateCache: LRUCache<{ | ||
value: Value; | ||
logs: ReductionLogs; | ||
}> | null; | ||
}> | null = null; | ||
// @internal | ||
public override: DeepPartial<ObjectValue> | null; | ||
public override: DeepPartial<ObjectValue> | null = null; | ||
// eslint-disable-next-line max-params | ||
constructor({ | ||
traceId, | ||
initData, | ||
lastDataProviderInitTime, | ||
initDataProvider, | ||
@@ -70,7 +72,8 @@ initDataRefreshIntervalMs, | ||
logger, | ||
loggerFlushIntervalMs, | ||
cacheSize, | ||
override, | ||
}: { | ||
traceId: string; | ||
initData: InitData | null; | ||
lastDataProviderInitTime: number | null; | ||
initDataProvider: InitDataProvider | null; | ||
@@ -83,3 +86,2 @@ initDataRefreshIntervalMs: number; | ||
logger: Logger; | ||
loggerFlushIntervalMs: number | null; | ||
cacheSize: number; | ||
@@ -94,9 +96,3 @@ override: object | null; | ||
this.updateListeners = new Map(); | ||
this.logger = logger; | ||
this.initData = null; | ||
this.lastDataProviderInitTime = null; | ||
this.getFieldCache = null; | ||
this.getItemsCache = null; | ||
this.evaluateCache = null; | ||
this.override = override; | ||
@@ -109,8 +105,8 @@ | ||
} | ||
const traceId = uniqueId(); | ||
if (initData) { | ||
this.initFromData(traceId, initData); | ||
this.setLastDataProviderInitTime(lastDataProviderInitTime); | ||
} | ||
this.initAndStartIntervals(shouldCheckForUpdates, loggerFlushIntervalMs); | ||
this.initAndStartIntervals(traceId, shouldCheckForUpdates); | ||
@@ -122,3 +118,3 @@ if (isBrowser) { | ||
} | ||
await this.logger.flush(/* traceId */ uniqueId()); | ||
await this.close(/* traceId */ uniqueId()); | ||
}); | ||
@@ -395,7 +391,7 @@ } | ||
private initAndStartIntervals( | ||
shouldCheckForUpdates: boolean, | ||
loggerFlushIntervalMs: number | null | ||
initTraceId: string, | ||
shouldCheckForUpdates: boolean | ||
): void { | ||
if (!this.initDataProvider || !shouldCheckForUpdates) { | ||
this.log(/* traceId */ "", LogLevel.Info, "Not polling for updates."); | ||
this.log(initTraceId, LogLevel.Info, "Not polling for updates."); | ||
} | ||
@@ -407,2 +403,13 @@ if (this.initDataProvider) { | ||
if (this.shouldClose) { | ||
// No need to get updates when the SDK is shutting down. | ||
this.updateTimeout = null; | ||
this.log( | ||
traceId, | ||
LogLevel.Debug, | ||
"Stopped getting updates from the data provider." | ||
); | ||
return; | ||
} | ||
this.initIfNeeded(traceId, defaultRetries) | ||
@@ -419,4 +426,5 @@ .catch((error) => | ||
if (this.shouldClose) { | ||
this.updateTimeout = null; | ||
this.log( | ||
/* traceId */ "", | ||
traceId, | ||
LogLevel.Debug, | ||
@@ -428,3 +436,6 @@ "Stopped getting updates from the data provider." | ||
if (shouldCheckForUpdates) { | ||
setTimeout(update, this.initDataRefreshIntervalMs); | ||
this.updateTimeout = setTimeout( | ||
update, | ||
this.initDataRefreshIntervalMs | ||
); | ||
} | ||
@@ -435,30 +446,2 @@ }); | ||
} | ||
// eslint-disable-next-line @hypertune/prefer-early-returns | ||
if (loggerFlushIntervalMs !== null) { | ||
// eslint-disable-next-line func-style | ||
const flushLogQueue = (): void => { | ||
setTimeout(async () => { | ||
if (this.shouldClose) { | ||
this.log( | ||
/* traceId */ "", | ||
LogLevel.Debug, | ||
"Stopped flushing log queue." | ||
); | ||
return; | ||
} | ||
await this.logger.flush(/* traceId */ uniqueId()); | ||
flushLogQueue(); | ||
}, loggerFlushIntervalMs); | ||
}; | ||
flushLogQueue(); | ||
} else { | ||
this.log( | ||
/* traceId */ "", | ||
LogLevel.Info, | ||
"Not automatically flushing logs." | ||
); | ||
} | ||
} | ||
@@ -474,2 +457,16 @@ | ||
// @internal | ||
async close(traceId: string): Promise<void> { | ||
this.log(traceId, LogLevel.Info, "Closing..."); | ||
this.shouldClose = true; | ||
if (this.updateTimeout) { | ||
clearTimeout(this.updateTimeout); | ||
this.updateTimeout = null; | ||
} | ||
await this.logger.close(traceId); | ||
this.log(traceId, LogLevel.Info, "Closed."); | ||
} | ||
// @internal | ||
getStateHash(): string { | ||
@@ -527,3 +524,3 @@ const initDataHash = this.initData?.hash ?? null; | ||
} | ||
const { override } = this; | ||
const { lastDataProviderInitTime, override } = this; | ||
@@ -556,2 +553,3 @@ const dehydrateQuery = query ?? this.query; | ||
initData, | ||
lastDataProviderInitTime, | ||
override: override as DeepPartial<TOverride> | null, | ||
@@ -568,3 +566,4 @@ variableValues: dehydrateQueryVariableValues, | ||
this.log(traceId, LogLevel.Info, "Hydrating..."); | ||
const { initData, override, variableValues } = dehydratedState; | ||
const { initData, override, variableValues, lastDataProviderInitTime } = | ||
dehydratedState; | ||
@@ -575,10 +574,15 @@ if (initData && variableValues) { | ||
this.initFromData(traceId, initData); | ||
if (this.initDataProvider) { | ||
// Set the last initialization time as hydration should only be used with | ||
// the latest available data. | ||
this.lastDataProviderInitTime = Date.now(); | ||
} | ||
this.setLastDataProviderInitTime(lastDataProviderInitTime); | ||
this.setOverride<TOverride>(traceId, override); | ||
} | ||
private setLastDataProviderInitTime(newTime: number | null): void { | ||
if (!this.initDataProvider || !newTime) { | ||
return; | ||
} | ||
// Don't set the time to a future value. | ||
const now = Date.now(); | ||
this.lastDataProviderInitTime = newTime > now ? now : newTime; | ||
} | ||
// @internal | ||
@@ -585,0 +589,0 @@ reduce( |
@@ -5,2 +5,3 @@ import { | ||
prodEdgeCdnBaseUrl, | ||
uniqueId, | ||
} from "../shared"; | ||
@@ -41,2 +42,3 @@ import { | ||
}): T { | ||
const traceId = uniqueId(); | ||
let logger: Logger; | ||
@@ -48,4 +50,12 @@ const remoteLoggingMode = | ||
logger = new Logger({ | ||
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: | ||
@@ -78,2 +88,3 @@ options.remoteLogging?.endpointUrl ?? prodLogsEndpointUrl, | ||
const context = new Context({ | ||
traceId, | ||
query, | ||
@@ -83,2 +94,3 @@ queryCode, | ||
initData: options.initData ?? null, | ||
lastDataProviderInitTime: options.lastDataProviderInitTime ?? null, | ||
initDataProvider: | ||
@@ -107,13 +119,2 @@ options.initDataProvider !== undefined | ||
logger, | ||
loggerFlushIntervalMs: | ||
// Use the provided option else use the default interval but disable | ||
// flushing by default if remote logging mode is set to off. | ||
remoteLoggingMode === "off" | ||
? null | ||
: options.remoteLogging?.flushIntervalMs !== undefined | ||
? options.remoteLogging.flushIntervalMs === null || | ||
options.remoteLogging.flushIntervalMs === 0 // TODO: Deprecate | ||
? null | ||
: clampInterval(options.remoteLogging.flushIntervalMs) | ||
: defaultIntervalMs, | ||
cacheSize: options.cacheSize ?? defaultCacheSize, | ||
@@ -120,0 +121,0 @@ override: override ?? null, |
@@ -12,4 +12,4 @@ import { | ||
} from "../shared"; | ||
import { RemoteLogger, RemoteLoggingMode } from "../shared/types"; | ||
import BackendRemoteLogger from "../shared/helpers/BackendRemoteLogger"; | ||
import { RemoteLoggingMode } from "../shared/types"; | ||
import RemoteLogger from "../shared/helpers/RemoteLogger"; | ||
import toConsoleFunction from "../shared/helpers/toConsoleFunction"; | ||
@@ -20,21 +20,29 @@ import LRUCache from "../shared/helpers/LRUCache"; | ||
import newTracedFetch from "../shared/helpers/newTracedFetch"; | ||
import NoopRemoteLogger from "../shared/helpers/NoopRemoteLogger"; | ||
const fetchMaxKeepAliveRequestSizeBytes = 64_000; | ||
/** | ||
* Logger provides a high level API for Node and generic SDK logs. It emits | ||
* these logs using the local logger and it also forwards them to the | ||
* `RemoteLogger` based on the selected `RemoteLoggingMode`. | ||
*/ | ||
export default class Logger { | ||
public readonly id: string; | ||
private readonly remoteLoggingMode: "normal" | "session"; | ||
private readonly remoteLoggingMode: RemoteLoggingMode; | ||
private readonly remoteLogCache: LRUCache<boolean> = new LRUCache(10_000); | ||
private readonly remoteLogger: RemoteLogger; | ||
private readonly remoteLogger: RemoteLogger | null; | ||
private readonly localLogger: LocalLogger; | ||
constructor({ | ||
traceId, | ||
token, | ||
remoteLoggingMode, | ||
remoteFlushIntervalMs, | ||
remoteLoggingEndpointUrl, | ||
localLogger, | ||
}: { | ||
traceId: string; | ||
token: string; | ||
remoteLoggingMode: RemoteLoggingMode; | ||
remoteFlushIntervalMs: number | null; | ||
remoteLoggingEndpointUrl: string; | ||
@@ -44,4 +52,3 @@ localLogger?: LocalLogger; | ||
this.id = uniqueId(); | ||
this.remoteLoggingMode = | ||
remoteLoggingMode === "off" ? "normal" : remoteLoggingMode; | ||
this.remoteLoggingMode = remoteLoggingMode; | ||
@@ -52,4 +59,5 @@ this.localLogger = wrapLocalLogger(this.id, localLogger); | ||
remoteLoggingMode === "off" | ||
? NoopRemoteLogger() | ||
: new BackendRemoteLogger({ | ||
? null | ||
: new RemoteLogger({ | ||
traceId, | ||
token, | ||
@@ -64,2 +72,3 @@ createLogs: getCreateLogsFunction( | ||
localLogger: this.localLogger, | ||
flushIntervalMs: remoteFlushIntervalMs, | ||
}); | ||
@@ -102,5 +111,5 @@ } | ||
(level === LogLevel.Warn || level === LogLevel.Error) && | ||
this.shouldRemoteLog(initDataHash, nodePath, message) | ||
this.shouldRemoteNodeLog(initDataHash, nodePath, message) | ||
) { | ||
this.remoteLogger.log( | ||
this.remoteLogger?.log( | ||
LogType.SdkNode, | ||
@@ -118,4 +127,4 @@ level, | ||
this.localLogger(LogLevel.Error, errorMessage, logMetadata); | ||
if (this.shouldRemoteLog(initDataHash, nodePath, message)) { | ||
this.remoteLogger.log( | ||
if (this.shouldRemoteNodeLog(initDataHash, nodePath, message)) { | ||
this.remoteLogger?.log( | ||
LogType.SdkNode, | ||
@@ -130,11 +139,11 @@ level, | ||
} | ||
if (this.shouldRemoteLog(initDataHash, nodePath, "evaluations")) { | ||
this.remoteLogger.evaluations(commitId, reductionLogs.evaluations); | ||
if (this.shouldRemoteNodeLog(initDataHash, nodePath, "evaluations")) { | ||
this.remoteLogger?.evaluations(commitId, reductionLogs.evaluations); | ||
} | ||
// We always log events to the backend | ||
this.remoteLogger.events(commitId, reductionLogs.events); | ||
this.remoteLogger?.events(commitId, reductionLogs.events); | ||
if (this.shouldRemoteLog(initDataHash, nodePath, "exposures")) { | ||
this.remoteLogger.exposures(commitId, reductionLogs.exposures); | ||
if (this.shouldRemoteNodeLog(initDataHash, nodePath, "exposures")) { | ||
this.remoteLogger?.exposures(commitId, reductionLogs.exposures); | ||
} | ||
@@ -144,3 +153,3 @@ } | ||
private shouldRemoteLog( | ||
private shouldRemoteNodeLog( | ||
initDataHash: string | null, | ||
@@ -175,2 +184,6 @@ nodePath: string, | ||
case "off": { | ||
return false; | ||
} | ||
default: { | ||
@@ -190,4 +203,7 @@ const neverLoggingMode: never = this.remoteLoggingMode; | ||
this.localLogger(level, message, metadata); | ||
if (level === LogLevel.Warn || level === LogLevel.Error) { | ||
this.remoteLogger.log(LogType.SdkMessage, level, commitId, message, { | ||
if ( | ||
this.remoteLoggingMode !== "off" && | ||
(level === LogLevel.Warn || level === LogLevel.Error) | ||
) { | ||
this.remoteLogger?.log(LogType.SdkMessage, level, commitId, message, { | ||
...metadata, | ||
@@ -200,4 +216,12 @@ sdkVersion, | ||
public flush(traceId: string): Promise<void> { | ||
return this.remoteLogger.flush(traceId); | ||
return this.remoteLogger | ||
? this.remoteLogger.flush(traceId) | ||
: Promise.resolve(); | ||
} | ||
public close(traceId: string): Promise<void> { | ||
return this.remoteLogger | ||
? this.remoteLogger.close(traceId) | ||
: Promise.resolve(); | ||
} | ||
} | ||
@@ -204,0 +228,0 @@ |
@@ -74,2 +74,8 @@ import { expect, test } from "vitest"; | ||
}); | ||
expect( | ||
merge<{ a: number; b: string; c?: string }>( | ||
{ a: 1, b: "str" }, | ||
{ c: "val" } | ||
) | ||
).toEqual({ a: 1, b: "str", c: "val" }); | ||
}); |
@@ -60,7 +60,14 @@ import { DeepPartial, ObjectValue, Value } from "../shared/types"; | ||
return Object.fromEntries( | ||
Object.entries(value).map(([fieldName, field]) => [ | ||
fieldName, | ||
mergeSource(field, (override as T)[fieldName]), | ||
]) | ||
Object.entries(value) | ||
.map(([fieldName, field]) => [ | ||
fieldName, | ||
mergeSource(field, (override as T)[fieldName]), | ||
]) | ||
.concat( | ||
// Add any additional fields from the override to the object. | ||
Object.entries(override as ObjectValue).filter( | ||
([fieldName]) => !(fieldName in value) | ||
) | ||
) | ||
) as T; | ||
} |
@@ -594,10 +594,14 @@ /* eslint-disable no-underscore-dangle */ | ||
close(): void { | ||
/** | ||
* Close flushes any remaining logs and stops all background processes | ||
* ensuring clean shutdown. | ||
*/ | ||
close(traceId = uniqueId()): Promise<void> { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(LogLevel.Error, "No context so cannot close."); | ||
return; | ||
return Promise.resolve(); | ||
} | ||
context.shouldClose = true; | ||
return context.close(traceId); | ||
} | ||
} |
export { default as asError } from "./asError"; | ||
export { default as BackendRemoteLogger } from "./BackendRemoteLogger"; | ||
export { default as RemoteLogger } from "./RemoteLogger"; | ||
export { default as emptyThrows } from "./emptyThrows"; | ||
@@ -4,0 +4,0 @@ export { |
@@ -760,16 +760,2 @@ /* eslint-disable capitalized-comments */ | ||
export type RemoteLogger = { | ||
log( | ||
type: LogType, | ||
level: LogLevel, | ||
commitId: string | null, | ||
message: string, | ||
metadata: object | ||
): void; | ||
evaluations(commitId: string, evaluations: CountMap): void; | ||
events(commitId: string, events: CountMap): void; | ||
exposures(commitId: string, exposures: CountMap): void; | ||
flush(traceId: string): Promise<void>; | ||
}; | ||
export type TracedFetch = ( | ||
@@ -833,3 +819,10 @@ traceId: string, | ||
initDataProvider?: InitDataProvider | null; | ||
/** | ||
* `lastDataProviderInitTime` is a timestamp specifying the last time | ||
* the provided `initData` was last checked for freshness using an | ||
* `InitDataProvider`. It is ignored when no `initData` was provided. | ||
*/ | ||
lastDataProviderInitTime?: number | null; | ||
/** | ||
* `initDataRefreshIntervalMs` specifies the interval, in milliseconds, at | ||
@@ -931,2 +924,3 @@ * which the SDK checks for updates. The minimum allowed value is 1000ms. | ||
initData: InitData; | ||
lastDataProviderInitTime: number | null; | ||
override: DeepPartial<TOverride> | null; | ||
@@ -933,0 +927,0 @@ variableValues: TVariableValues; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
714747
13057
303
2