hypertune
Advanced tools
Comparing version 1.9.0 to 1.10.0
# Changelog | ||
## 1.10.0 | ||
- Added a `setOverride()` method you can use to set a local override that the | ||
SDK should use when evaluating your flags. | ||
- Added `dehydrate()` and `hydrate()` methods you can use to pass the state of | ||
the SDK from the server to the client during server-side rendering. These | ||
replace the `getInitData()` and `initFromData()` methods which are now | ||
deprecated. | ||
- Added a `getStateHash()` method which replaces the `getCommitHash()` method | ||
which is now deprecated. | ||
## 1.9.0 | ||
@@ -80,4 +91,4 @@ | ||
- Added `addUpdateListener` and `removeUpdateListener` methods on SDK nodes so | ||
you can be notified when the SDK fetches an update. | ||
- Added `addUpdateListener(listener)` and `removeUpdateListener(listener)` | ||
methods on SDK nodes so you can be notified when the SDK fetches an update. | ||
@@ -84,0 +95,0 @@ ## 1.6.5 |
import "regenerator-runtime/runtime"; | ||
import type { InitResponseBody, ObjectValueWithVariables, Query, LoggingMode, InitializeOptions, CustomLogger, InternalInitializeOptions, VercelEdgeConfigClient } from "./shared/types"; | ||
import type { InitResponseBody, ObjectValueWithVariables, Query, LoggingMode, InitializeOptions, CustomLogger, InternalInitializeOptions, VercelEdgeConfigClient, DehydratedState } from "./shared/types"; | ||
import initialize from "./lib/initialize"; | ||
import Node from "./lib/Node"; | ||
export type { InitResponseBody, ObjectValueWithVariables, Query, CustomLogger, VercelEdgeConfigClient, LoggingMode, InitializeOptions, InternalInitializeOptions, }; | ||
export type { InitResponseBody, ObjectValueWithVariables, Query, CustomLogger, VercelEdgeConfigClient, LoggingMode, InitializeOptions, InternalInitializeOptions, DehydratedState, }; | ||
export { LogLevel } from "./generated/graphql"; | ||
@@ -7,0 +7,0 @@ export { initialize, Node }; |
@@ -1,2 +0,2 @@ | ||
import { InitResponseBody, Expression, Query, Value, ObjectValueWithVariables, InitSource, VercelEdgeConfigClient, Endpoints, UpdateListener, Fetch, ReductionLogs } from "../shared"; | ||
import { InitResponseBody, Expression, Query, Value, ObjectValueWithVariables, InitSource, VercelEdgeConfigClient, Endpoints, UpdateListener, Fetch, ReductionLogs, DehydratedState, DeepPartial } from "../shared"; | ||
import Logger from "./Logger"; | ||
@@ -31,2 +31,3 @@ import LRUCache from "../shared/helpers/LRUCache"; | ||
readonly updateListeners: Map<UpdateListener, boolean>; | ||
override: Value | null; | ||
constructor(token: string, queryCode: string, variableValues: { | ||
@@ -44,4 +45,9 @@ [variableName: string]: Value; | ||
private getUpdateIntervalMs; | ||
getStateHash(): string; | ||
addUpdateListener(listener: UpdateListener): void; | ||
removeUpdateListener(listener: UpdateListener): void; | ||
private notifyUpdateListeners; | ||
setOverride<T>(override: DeepPartial<T>): void; | ||
dehydrate(): DehydratedState; | ||
hydrate(dehydratedState: DehydratedState): void; | ||
reduce(query: Query<ObjectValueWithVariables> | null, expression: Expression): Expression; | ||
@@ -48,0 +54,0 @@ private log; |
@@ -48,2 +48,3 @@ "use strict"; | ||
this.updateListeners = new Map(); | ||
this.override = null; | ||
if (cacheSize > 0) { | ||
@@ -79,2 +80,3 @@ this.getFieldCache = new LRUCache_1.default(cacheSize); | ||
} | ||
// @internal | ||
initFromData(initData) { | ||
@@ -94,3 +96,3 @@ const initSourceName = (0, getInitSourceName_1.default)("local"); | ||
if (newInitData.commitId <= ((_b = (_a = this.initData) === null || _a === void 0 ? void 0 : _a.commitId) !== null && _b !== void 0 ? _b : -1)) { | ||
this.log(graphql_1.LogLevel.Info, `Skipped initialization from ${initSourceName} as commit isn't newer.`); | ||
this.log(graphql_1.LogLevel.Info, `Skipped initialization from ${initSourceName} as commit with id "${newInitData.commitId}" isn't newer.`); | ||
return; | ||
@@ -114,5 +116,3 @@ } | ||
(_e = this.evaluateCache) === null || _e === void 0 ? void 0 : _e.purge(); | ||
this.updateListeners.forEach((_, listener) => { | ||
listener(newInitData.commitHash); | ||
}); | ||
this.notifyUpdateListeners(); | ||
} | ||
@@ -123,2 +123,3 @@ catch (error) { | ||
} | ||
// @internal | ||
initFromServerIfNeeded() { | ||
@@ -279,8 +280,45 @@ const { lastServerInitTime, shouldGetUpdatesFromServer } = this; | ||
} | ||
// @internal | ||
getStateHash() { | ||
var _a, _b; | ||
const commitHash = (_b = (_a = this.initData) === null || _a === void 0 ? void 0 : _a.commitHash) !== null && _b !== void 0 ? _b : null; | ||
const ssOverride = (0, shared_1.stableStringify)(this.override); | ||
return (0, shared_1.hash)(`${commitHash}/${ssOverride}`).toString(); | ||
} | ||
// @internal | ||
addUpdateListener(listener) { | ||
this.updateListeners.set(listener, true); | ||
} | ||
// @internal | ||
removeUpdateListener(listener) { | ||
this.updateListeners.delete(listener); | ||
} | ||
notifyUpdateListeners() { | ||
const stateHash = this.getStateHash(); | ||
this.updateListeners.forEach((_, listener) => { | ||
listener(stateHash); | ||
}); | ||
} | ||
// @internal | ||
setOverride(override) { | ||
if ((0, shared_1.stableStringify)(override) === (0, shared_1.stableStringify)(this.override)) { | ||
this.log(graphql_1.LogLevel.Info, "Skipped setting override as it's equal to the one already set."); | ||
return; | ||
} | ||
this.override = override; | ||
this.log(graphql_1.LogLevel.Info, "Set override.", { override }); | ||
this.notifyUpdateListeners(); | ||
} | ||
// @internal | ||
dehydrate() { | ||
const { initData, override } = this; | ||
return { initData, override }; | ||
} | ||
// @internal | ||
hydrate(dehydratedState) { | ||
this.log(graphql_1.LogLevel.Info, "Hydrating..."); | ||
this.initFromData(dehydratedState.initData); | ||
this.setOverride(dehydratedState.override); | ||
} | ||
// @internal | ||
reduce(query, expression) { | ||
@@ -287,0 +325,0 @@ const { splits, eventTypes, commitConfig } = (0, shared_1.nullThrows)(this.initData, "No init data so cannot reduce expression."); |
@@ -5,6 +5,5 @@ "use strict"; | ||
function getNodeCacheKey(commitHash, nodePath, suffix) { | ||
return (0, shared_1.hash)(`${commitHash}/${nodePath}/${suffix} | ||
)}`).toString(); | ||
return (0, shared_1.hash)(`${commitHash}/${nodePath}/${suffix}`).toString(); | ||
} | ||
exports.default = getNodeCacheKey; | ||
//# sourceMappingURL=getNodeCacheKey.js.map |
@@ -23,2 +23,3 @@ "use strict"; | ||
const fetchMaxKeepAliveRequestSizeBytes = 64000; | ||
// TODO: Merge with BackendLogger | ||
class Logger { | ||
@@ -42,3 +43,4 @@ constructor({ token, loggingMode, customLogger, endpoints, fetchFunction, }) { | ||
nodePath, | ||
nodeExpression }, metadata); | ||
nodeExpression, | ||
reductionLogs }, metadata); | ||
this.localLog(level, logMessage, logMetadata); | ||
@@ -45,0 +47,0 @@ if ((level === graphql_1.LogLevel.Warn || level === graphql_1.LogLevel.Error) && |
@@ -1,2 +0,2 @@ | ||
import { Expression, ObjectValue, Query, Step, Value, InitResponseBody, UpdateListener, ReductionLogs } from "../shared"; | ||
import { Expression, ObjectValue, Query, Step, Value, InitResponseBody, UpdateListener, ReductionLogs, DehydratedState, DeepPartial } from "../shared"; | ||
import Context from "./Context"; | ||
@@ -22,2 +22,3 @@ import Logger from "./Logger"; | ||
protected evaluate(query: Query<ObjectValue> | null, fallback: Value): Value; | ||
private getNodeOverride; | ||
protected getValueAndLogsWithCache(query: Query<ObjectValue> | null): { | ||
@@ -33,2 +34,6 @@ value: Value; | ||
private log; | ||
getStateHash(): string | null; | ||
/** | ||
* @deprecated Use getStateHash() instead | ||
*/ | ||
getCommitHash(): string | null; | ||
@@ -50,3 +55,12 @@ addUpdateListener(listener: UpdateListener): void; | ||
flushLogs(): Promise<void>; | ||
setOverride<T>(override: DeepPartial<T>): void; | ||
dehydrate(): DehydratedState | null; | ||
hydrate(dehydratedState: DehydratedState): void; | ||
/** | ||
* @deprecated Use dehydrate() instead | ||
*/ | ||
getInitData(): InitResponseBody | null; | ||
/** | ||
* @deprecated Use hydrate() instead | ||
*/ | ||
initFromData(initData: InitResponseBody): void; | ||
@@ -53,0 +67,0 @@ close(): void; |
@@ -167,2 +167,6 @@ "use strict"; | ||
evaluate(query, fallback) { | ||
const nodeOverride = this.getNodeOverride(); | ||
if (nodeOverride !== null && nodeOverride !== undefined) { | ||
return nodeOverride; | ||
} | ||
const valueAndLogs = this.getValueAndLogsWithCache(query); | ||
@@ -172,7 +176,33 @@ if (!valueAndLogs) { | ||
} | ||
const { value, logs } = valueAndLogs; | ||
const { value, logs: reductionLogs } = valueAndLogs; | ||
this.log(graphql_1.LogLevel.Debug, `Evaluated to ${JSON.stringify(value)}`, | ||
/* metadata */ { query }, logs); | ||
/* metadata */ { query }, reductionLogs); | ||
return value; | ||
} | ||
getNodeOverride() { | ||
var _a, _b, _c; | ||
const { context, parent, step } = this.props; | ||
if (!context) { | ||
return null; | ||
} | ||
if (context.override === null || context.override === undefined) { | ||
// Short-circuit if no override in context | ||
return null; | ||
} | ||
if (!parent) { | ||
// We're the Query node | ||
return (_a = context.override) !== null && _a !== void 0 ? _a : null; | ||
} | ||
if (!step) { | ||
return null; | ||
} | ||
const parentOverride = parent.getNodeOverride(); | ||
if (parentOverride === null || parentOverride === undefined) { | ||
return null; | ||
} | ||
if (step.type === "GetFieldStep") { | ||
return (_b = parentOverride[step.fieldName]) !== null && _b !== void 0 ? _b : null; | ||
} | ||
return (_c = parentOverride[step.index]) !== null && _c !== void 0 ? _c : null; | ||
} | ||
// @internal | ||
@@ -256,2 +286,13 @@ getValueAndLogsWithCache(query) { | ||
} | ||
getStateHash() { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(graphql_1.LogLevel.Error, "No context so cannot get state hash."); | ||
return null; | ||
} | ||
return context.getStateHash(); | ||
} | ||
/** | ||
* @deprecated Use getStateHash() instead | ||
*/ | ||
getCommitHash() { | ||
@@ -318,2 +359,29 @@ var _a, _b; | ||
} | ||
setOverride(override) { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(graphql_1.LogLevel.Error, "No context so cannot set override."); | ||
return; | ||
} | ||
context.setOverride(override); | ||
} | ||
dehydrate() { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(graphql_1.LogLevel.Error, "No context so cannot dehydrate."); | ||
return null; | ||
} | ||
return context.dehydrate(); | ||
} | ||
hydrate(dehydratedState) { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(graphql_1.LogLevel.Error, "No context so cannot hydrate."); | ||
return; | ||
} | ||
context.hydrate(dehydratedState); | ||
} | ||
/** | ||
* @deprecated Use dehydrate() instead | ||
*/ | ||
getInitData() { | ||
@@ -327,2 +395,5 @@ const { context } = this.props; | ||
} | ||
/** | ||
* @deprecated Use hydrate() instead | ||
*/ | ||
initFromData(initData) { | ||
@@ -329,0 +400,0 @@ const { context } = this.props; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** Replaced by the value in package.json on build */ | ||
exports.default = "1.9.0"; | ||
exports.default = "1.10.0"; | ||
//# sourceMappingURL=sdkVersion.js.map |
@@ -587,3 +587,3 @@ import { z } from "zod"; | ||
export type InitSource = "none" | "local" | "hypertuneEdge" | "vercelEdgeConfig"; | ||
export type UpdateListener = (newCommitHash: string) => void; | ||
export type UpdateListener = (newStateHash: string) => void; | ||
export type Fetch = typeof fetch; | ||
@@ -623,2 +623,9 @@ export type InitializeOptions = { | ||
}; | ||
export type DehydratedState = { | ||
initData: InitResponseBody | null; | ||
override: Value | null; | ||
}; | ||
export type DeepPartial<T> = T extends (infer U)[] ? DeepPartial<U>[] | undefined : T extends object ? { | ||
[P in keyof T]?: DeepPartial<T[P]>; | ||
} : T; | ||
export type CodegenArguments = { | ||
@@ -625,0 +632,0 @@ token: string; |
{ | ||
"name": "hypertune", | ||
"version": "1.9.0", | ||
"version": "1.10.0", | ||
"private": false, | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -11,2 +11,3 @@ import "regenerator-runtime/runtime"; | ||
VercelEdgeConfigClient, | ||
DehydratedState, | ||
} from "./shared/types"; | ||
@@ -25,2 +26,3 @@ import initialize from "./lib/initialize"; | ||
InternalInitializeOptions, | ||
DehydratedState, | ||
}; | ||
@@ -27,0 +29,0 @@ |
@@ -17,2 +17,6 @@ import pRetry from "p-retry"; | ||
ReductionLogs, | ||
DehydratedState, | ||
DeepPartial, | ||
hash, | ||
stableStringify, | ||
} from "../shared"; | ||
@@ -53,2 +57,3 @@ import { hashRequest, initRequest } from "./edge"; | ||
public readonly updateListeners: Map<UpdateListener, boolean>; | ||
public override: Value | null; | ||
@@ -93,2 +98,3 @@ // eslint-disable-next-line max-params | ||
this.updateListeners = new Map(); | ||
this.override = null; | ||
@@ -128,2 +134,3 @@ if (cacheSize > 0) { | ||
// @internal | ||
initFromData(initData: InitResponseBody | null): void { | ||
@@ -152,3 +159,3 @@ const initSourceName = getInitSourceName("local"); | ||
LogLevel.Info, | ||
`Skipped initialization from ${initSourceName} as commit isn't newer.` | ||
`Skipped initialization from ${initSourceName} as commit with id "${newInitData.commitId}" isn't newer.` | ||
); | ||
@@ -191,5 +198,4 @@ return; | ||
this.evaluateCache?.purge(); | ||
this.updateListeners.forEach((_, listener) => { | ||
listener(newInitData.commitHash); | ||
}); | ||
this.notifyUpdateListeners(); | ||
} catch (error) { | ||
@@ -204,2 +210,3 @@ this.log( | ||
// @internal | ||
initFromServerIfNeeded(): Promise<void> { | ||
@@ -455,10 +462,55 @@ const { lastServerInitTime, shouldGetUpdatesFromServer } = this; | ||
public addUpdateListener(listener: UpdateListener): void { | ||
// @internal | ||
getStateHash(): string { | ||
const commitHash = this.initData?.commitHash ?? null; | ||
const ssOverride = stableStringify(this.override); | ||
return hash(`${commitHash}/${ssOverride}`).toString(); | ||
} | ||
// @internal | ||
addUpdateListener(listener: UpdateListener): void { | ||
this.updateListeners.set(listener, true); | ||
} | ||
public removeUpdateListener(listener: UpdateListener): void { | ||
// @internal | ||
removeUpdateListener(listener: UpdateListener): void { | ||
this.updateListeners.delete(listener); | ||
} | ||
private notifyUpdateListeners(): void { | ||
const stateHash = this.getStateHash(); | ||
this.updateListeners.forEach((_, listener) => { | ||
listener(stateHash); | ||
}); | ||
} | ||
// @internal | ||
setOverride<T>(override: DeepPartial<T>): void { | ||
if (stableStringify(override) === stableStringify(this.override)) { | ||
this.log( | ||
LogLevel.Info, | ||
"Skipped setting override as it's equal to the one already set." | ||
); | ||
return; | ||
} | ||
this.override = override as Value | null; | ||
this.log(LogLevel.Info, "Set override.", { override }); | ||
this.notifyUpdateListeners(); | ||
} | ||
// @internal | ||
dehydrate(): DehydratedState { | ||
const { initData, override } = this; | ||
return { initData, override }; | ||
} | ||
// @internal | ||
hydrate(dehydratedState: DehydratedState): void { | ||
this.log(LogLevel.Info, "Hydrating..."); | ||
this.initFromData(dehydratedState.initData); | ||
this.setOverride(dehydratedState.override); | ||
} | ||
// @internal | ||
reduce( | ||
@@ -465,0 +517,0 @@ query: Query<ObjectValueWithVariables> | null, |
@@ -8,6 +8,3 @@ import { hash } from "../shared"; | ||
): string { | ||
return hash( | ||
`${commitHash}/${nodePath}/${suffix} | ||
)}` | ||
).toString(); | ||
return hash(`${commitHash}/${nodePath}/${suffix}`).toString(); | ||
} |
@@ -21,2 +21,3 @@ import { | ||
// TODO: Merge with BackendLogger | ||
export default class Logger { | ||
@@ -82,2 +83,3 @@ public readonly id: string; | ||
nodeExpression, | ||
reductionLogs, | ||
...metadata, | ||
@@ -84,0 +86,0 @@ }; |
@@ -22,2 +22,4 @@ /* eslint-disable no-underscore-dangle */ | ||
asError, | ||
DehydratedState, | ||
DeepPartial, | ||
} from "../shared"; | ||
@@ -286,2 +288,7 @@ import getDeepestZodIssue from "../shared/helpers/getDeepestZodIssue"; | ||
protected evaluate(query: Query<ObjectValue> | null, fallback: Value): Value { | ||
const nodeOverride = this.getNodeOverride(); | ||
if (nodeOverride !== null && nodeOverride !== undefined) { | ||
return nodeOverride; | ||
} | ||
const valueAndLogs = this.getValueAndLogsWithCache(query); | ||
@@ -293,3 +300,3 @@ | ||
const { value, logs } = valueAndLogs; | ||
const { value, logs: reductionLogs } = valueAndLogs; | ||
@@ -300,3 +307,3 @@ this.log( | ||
/* metadata */ { query }, | ||
logs | ||
reductionLogs | ||
); | ||
@@ -307,2 +314,36 @@ | ||
private getNodeOverride(): Value | null { | ||
const { context, parent, step } = this.props; | ||
if (!context) { | ||
return null; | ||
} | ||
if (context.override === null || context.override === undefined) { | ||
// Short-circuit if no override in context | ||
return null; | ||
} | ||
if (!parent) { | ||
// We're the Query node | ||
return context.override ?? null; | ||
} | ||
if (!step) { | ||
return null; | ||
} | ||
const parentOverride = parent.getNodeOverride(); | ||
if (parentOverride === null || parentOverride === undefined) { | ||
return null; | ||
} | ||
if (step.type === "GetFieldStep") { | ||
return (parentOverride as ObjectValue)[step.fieldName] ?? null; | ||
} | ||
return (parentOverride as Value[])[step.index] ?? null; | ||
} | ||
// @internal | ||
@@ -447,2 +488,14 @@ protected getValueAndLogsWithCache( | ||
getStateHash(): string | null { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(LogLevel.Error, "No context so cannot get state hash."); | ||
return null; | ||
} | ||
return context.getStateHash(); | ||
} | ||
/** | ||
* @deprecated Use getStateHash() instead | ||
*/ | ||
getCommitHash(): string | null { | ||
@@ -521,2 +574,32 @@ const { context } = this.props; | ||
setOverride<T>(override: DeepPartial<T>): void { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(LogLevel.Error, "No context so cannot set override."); | ||
return; | ||
} | ||
context.setOverride<T>(override); | ||
} | ||
dehydrate(): DehydratedState | null { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(LogLevel.Error, "No context so cannot dehydrate."); | ||
return null; | ||
} | ||
return context.dehydrate(); | ||
} | ||
hydrate(dehydratedState: DehydratedState): void { | ||
const { context } = this.props; | ||
if (!context) { | ||
this.log(LogLevel.Error, "No context so cannot hydrate."); | ||
return; | ||
} | ||
context.hydrate(dehydratedState); | ||
} | ||
/** | ||
* @deprecated Use dehydrate() instead | ||
*/ | ||
getInitData(): InitResponseBody | null { | ||
@@ -531,2 +614,5 @@ const { context } = this.props; | ||
/** | ||
* @deprecated Use hydrate() instead | ||
*/ | ||
initFromData(initData: InitResponseBody): void { | ||
@@ -533,0 +619,0 @@ const { context } = this.props; |
@@ -745,3 +745,3 @@ /* eslint-disable capitalized-comments */ | ||
export type UpdateListener = (newCommitHash: string) => void; | ||
export type UpdateListener = (newStateHash: string) => void; | ||
@@ -791,2 +791,15 @@ export type Fetch = typeof fetch; | ||
export type DehydratedState = { | ||
initData: InitResponseBody | null; | ||
override: Value | null; | ||
}; | ||
export type DeepPartial<T> = T extends (infer U)[] | ||
? DeepPartial<U>[] | undefined | ||
: T extends object | ||
? { | ||
[P in keyof T]?: DeepPartial<T[P]>; | ||
} | ||
: T; | ||
// Codegen Command | ||
@@ -793,0 +806,0 @@ |
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
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
702344
13046