hypertune
Advanced tools
Comparing version 2.1.2 to 2.2.0
# Changelog | ||
## 2.2.0 | ||
- Added the `isReady` function that checks whether the SDK is ready to log | ||
analytics events. | ||
- Deprecated the `initIntervalMs` option in `CreateOptions`. It is replaced by: | ||
- `initDataRefreshIntervalMs`: Specifies the interval at which the SDK checks | ||
for flag updates with a default value of 2000ms. | ||
- `shouldCheckForUpdates`: Specifies whether the SDK automatically checks for | ||
flag updates in the background. | ||
- You can now generate Vercel feature flag functions, defined via the | ||
`@vercel/flags/next` package, for the top-level boolean and enum flags in | ||
your Hypertune project. To enable this, set the `framework` option to | ||
`nextAppFlags` and specify the `getHypertuneImportPath` option with the relative | ||
path to your `getHypertune` function. | ||
- Bumped SDK request timeouts from 5s to 10s. | ||
- Removed retries when using the `initIfNeeded` method. | ||
## 2.1.2 | ||
@@ -4,0 +25,0 @@ |
@@ -14,2 +14,3 @@ import { CAC } from "cac"; | ||
edgeBaseUrl?: string; | ||
getHypertuneImportPath?: string; | ||
}; | ||
@@ -16,0 +17,0 @@ export declare const defaultOptions: { |
@@ -40,2 +40,3 @@ "use strict"; | ||
edgeBaseUrl: "string", | ||
getHypertuneImportPath: "string", | ||
}; | ||
@@ -56,18 +57,19 @@ function registerGenerateCommand(cli) { | ||
.option("--queryFilePath [value]", "File path to the GraphQL initialization query", { default: exports.defaultOptions.queryFilePath }) | ||
.option("--outputFilePath [value]", "Where to write the generated file") | ||
.option("--outputDirectoryPath [value]", "Where to write the generated files", { default: exports.defaultOptions.outputDirectoryPath }) | ||
.option("--includeToken", "Include your token in the generated code", { | ||
.option("--outputFilePath [value]", "(Deprecated) The path to write the generated file to") | ||
.option("--outputDirectoryPath [value]", "The directory to write the generated files to", { default: exports.defaultOptions.outputDirectoryPath }) | ||
.option("--includeToken", "Include the project token in the generated code", { | ||
default: exports.defaultOptions.includeToken, | ||
}) | ||
.option("--includeInitData", "Embed a snapshot of your configuration logic in the generated code so the SDK can reliably, locally initialize first, before fetching the latest logic from the server, and can work even if the server is unreachable", { | ||
.option("--includeInitData", "Embed a static snapshot of your flag logic in the generated code so the SDK can reliably, locally and instantly initialize first, before fetching the latest logic from the server, and can function even if the server is unreachable", { | ||
default: exports.defaultOptions.includeInitData, | ||
}) | ||
.option("--language [value]", "Target language (ts or js).", { | ||
.option("--language [value]", "Target language (ts or js)", { | ||
default: exports.defaultOptions.language, | ||
}) | ||
.option("--framework [value]", "Framework to use (nextApp, nextPages, react, remix or gatsby)."); | ||
.option("--framework [value]", "Framework (nextApp, nextAppFlags, nextPages, react, remix or gatsby)") | ||
.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 nextAppFlags framework"); | ||
} | ||
exports.registerGenerateCommand = registerGenerateCommand; | ||
exports.generate = (0, helpers_1.withValidation)(exports.generateOptionsSchema, (options) => __awaiter(void 0, void 0, void 0, function* () { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; | ||
console.log("Starting Hypertune code generation..."); | ||
@@ -82,3 +84,3 @@ try { | ||
queryCode: getQueryCode((_e = options.queryFilePath) !== null && _e !== void 0 ? _e : exports.defaultOptions.queryFilePath), | ||
outputFilePath: (_f = options.outputFilePath) !== null && _f !== void 0 ? _f : `${outputDirectory}/hypertune.${language}`, | ||
outputFilePath: (_f = options.outputFilePath) !== null && _f !== void 0 ? _f : node_path_1.default.join(outputDirectory, `hypertune.${language}`), | ||
includeToken: (_g = options.includeToken) !== null && _g !== void 0 ? _g : exports.defaultOptions.includeToken, | ||
@@ -88,2 +90,3 @@ includeFallback: (_h = options.includeInitData) !== null && _h !== void 0 ? _h : exports.defaultOptions.includeInitData, | ||
edgeBaseUrl: (_j = options.edgeBaseUrl) !== null && _j !== void 0 ? _j : shared_1.prodEdgeCdnBaseUrl, | ||
getHypertuneImportPath: (_k = options.getHypertuneImportPath) !== null && _k !== void 0 ? _k : "", | ||
}); | ||
@@ -97,4 +100,7 @@ console.log("Done"); | ||
})); | ||
function generateCode({ token, branchName, framework, queryCode, outputFilePath, includeToken, includeFallback, language, edgeBaseUrl, }) { | ||
function generateCode({ token, branchName, framework, queryCode, outputFilePath, includeToken, includeFallback, language, edgeBaseUrl, getHypertuneImportPath, }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (framework === "nextAppFlags" && !getHypertuneImportPath) { | ||
throw new Error(`The "getHypertuneImportPath" option must be specified when using the "nextAppFlags" framework.`); | ||
} | ||
const extension = node_path_1.default.extname(outputFilePath); | ||
@@ -113,2 +119,3 @@ const clientFileName = node_path_1.default.basename(outputFilePath.slice(0, outputFilePath.length - extension.length)); | ||
edgeBaseUrl, | ||
getHypertuneImportPath, | ||
tracedFetch: (_, url, init) => { | ||
@@ -122,7 +129,5 @@ return (0, cross_fetch_1.default)(url, init); | ||
} | ||
writeFile(outputFilePath, codegenResponse.code); | ||
if (codegenResponse.frameworkCode) { | ||
const frameworkFilePath = `${outputFilePath.slice(0, outputFilePath.length - extension.length)}.react${extension}x`; | ||
writeFile(frameworkFilePath, codegenResponse.frameworkCode); | ||
} | ||
codegenResponse.files.forEach(({ name, content }) => { | ||
writeFile(node_path_1.default.join(outputDirectoryPath, name), content); | ||
}); | ||
}); | ||
@@ -129,0 +134,0 @@ } |
@@ -12,5 +12,5 @@ import "regenerator-runtime/runtime"; | ||
*/ | ||
CreateOptions as InitOptions, DehydratedState, DeepPartial, merge, }; | ||
CreateOptions as InitOptions, DehydratedState, DeepPartial, }; | ||
export { LogLevel } from "./shared/types"; | ||
export { create, | ||
export { create, merge, | ||
/** | ||
@@ -17,0 +17,0 @@ * @deprecated use create directly instead. |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.VoidNode = exports.StringNode = exports.IntNode = exports.FloatNode = exports.BooleanNode = exports.VercelEdgeConfigInitDataProvider = exports.HypertuneEdgeInitDataProvider = exports.Node = exports.init = exports.create = exports.LogLevel = void 0; | ||
exports.VoidNode = exports.StringNode = exports.IntNode = exports.FloatNode = exports.BooleanNode = exports.VercelEdgeConfigInitDataProvider = exports.HypertuneEdgeInitDataProvider = exports.Node = exports.init = exports.merge = exports.create = exports.LogLevel = void 0; | ||
require("regenerator-runtime/runtime"); | ||
@@ -14,2 +14,4 @@ const create_1 = __importDefault(require("./lib/create")); | ||
exports.Node = Node_1.default; | ||
const merge_1 = __importDefault(require("./lib/merge")); | ||
exports.merge = merge_1.default; | ||
const HypertuneEdgeInitDataProvider_1 = __importDefault(require("./lib/initDataProviders/HypertuneEdgeInitDataProvider")); | ||
@@ -16,0 +18,0 @@ exports.HypertuneEdgeInitDataProvider = HypertuneEdgeInitDataProvider_1.default; |
@@ -7,3 +7,3 @@ import { InitData, Expression, Query, Value, ObjectValueWithVariables, UpdateListener, ReductionLogs, DehydratedState, DeepPartial, InitDataProvider, ObjectValue } from "../shared"; | ||
protected readonly initDataProvider: InitDataProvider | null; | ||
protected readonly initIntervalMs: number; | ||
protected readonly initDataRefreshIntervalMs: number; | ||
protected readonly query: Query<ObjectValueWithVariables>; | ||
@@ -24,6 +24,7 @@ protected readonly queryCode: string; | ||
override: DeepPartial<ObjectValue> | null; | ||
constructor({ initData, initDataProvider, initIntervalMs, query, queryCode, variableValues, logger, loggerFlushIntervalMs, cacheSize, override, }: { | ||
constructor({ initData, initDataProvider, initDataRefreshIntervalMs, shouldCheckForUpdates, query, queryCode, variableValues, logger, loggerFlushIntervalMs, cacheSize, override, }: { | ||
initData: InitData | null; | ||
initDataProvider: InitDataProvider | null; | ||
initIntervalMs: number; | ||
initDataRefreshIntervalMs: number; | ||
shouldCheckForUpdates: boolean; | ||
query: Query<ObjectValueWithVariables>; | ||
@@ -33,3 +34,3 @@ queryCode: string; | ||
logger: Logger; | ||
loggerFlushIntervalMs: number; | ||
loggerFlushIntervalMs: number | null; | ||
cacheSize: number; | ||
@@ -40,3 +41,3 @@ override: object | null; | ||
private init; | ||
initIfNeeded(traceId: string): Promise<void>; | ||
initIfNeeded(traceId: string, retries: number): Promise<void>; | ||
private initFromDataProvider; | ||
@@ -46,2 +47,3 @@ private getLatestInitDataHash; | ||
private initAndStartIntervals; | ||
isReady(): boolean; | ||
getStateHash(): string; | ||
@@ -52,3 +54,3 @@ addUpdateListener(listener: UpdateListener): void; | ||
setOverride<TOverride extends ObjectValue>(traceId: string, override: DeepPartial<TOverride> | null): void; | ||
dehydrate<TOverride extends object, TVariableValues extends ObjectValue>(query?: Query<ObjectValueWithVariables>, variableValues?: TVariableValues): DehydratedState<TOverride, TVariableValues> | null; | ||
dehydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>(query?: Query<ObjectValueWithVariables>, variableValues?: TVariableValues): DehydratedState<TOverride, TVariableValues> | null; | ||
hydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>(traceId: string, dehydratedState: DehydratedState<TOverride, TVariableValues>): void; | ||
@@ -55,0 +57,0 @@ reduce(query: Query<ObjectValueWithVariables> | null, expression: Expression): Expression; |
@@ -23,7 +23,7 @@ "use strict"; | ||
// eslint-disable-next-line max-params | ||
constructor({ initData, initDataProvider, initIntervalMs, query, queryCode, variableValues, logger, loggerFlushIntervalMs, cacheSize, override, }) { | ||
constructor({ initData, initDataProvider, initDataRefreshIntervalMs, shouldCheckForUpdates, query, queryCode, variableValues, logger, loggerFlushIntervalMs, cacheSize, override, }) { | ||
// @internal | ||
this.shouldClose = false; | ||
this.initDataProvider = initDataProvider; | ||
this.initIntervalMs = initIntervalMs; | ||
this.initDataRefreshIntervalMs = initDataRefreshIntervalMs; | ||
this.query = query; | ||
@@ -49,3 +49,3 @@ this.queryCode = queryCode; | ||
} | ||
this.initAndStartIntervals(loggerFlushIntervalMs); | ||
this.initAndStartIntervals(shouldCheckForUpdates, loggerFlushIntervalMs); | ||
if (environment_1.isBrowser) { | ||
@@ -105,4 +105,4 @@ window.addEventListener("beforeunload", () => __awaiter(this, void 0, void 0, function* () { | ||
// @internal | ||
initIfNeeded(traceId) { | ||
const { lastDataProviderInitTime, initDataProvider, initIntervalMs } = this; | ||
initIfNeeded(traceId, retries) { | ||
const { lastDataProviderInitTime, initDataProvider, initDataRefreshIntervalMs, } = this; | ||
if (!initDataProvider) { | ||
@@ -114,10 +114,10 @@ this.log(traceId, shared_1.LogLevel.Info, "Not initializing from data provider as it's null."); | ||
const msSinceLastDataProviderInit = Date.now() - lastDataProviderInitTime; | ||
if (msSinceLastDataProviderInit < initIntervalMs) { | ||
this.log(traceId, shared_1.LogLevel.Debug, `Not initializing from data provider as already did ${msSinceLastDataProviderInit}ms ago which is less than the update interval of ${initIntervalMs}ms.`); | ||
if (msSinceLastDataProviderInit < initDataRefreshIntervalMs) { | ||
this.log(traceId, shared_1.LogLevel.Debug, `Not initializing from data provider as already did ${msSinceLastDataProviderInit}ms ago which is less than the update interval of ${initDataRefreshIntervalMs}ms.`); | ||
return Promise.resolve(); | ||
} | ||
} | ||
return this.initFromDataProvider(traceId, initDataProvider); | ||
return this.initFromDataProvider(traceId, initDataProvider, retries); | ||
} | ||
initFromDataProvider(traceId, initDataProvider) { | ||
initFromDataProvider(traceId, initDataProvider, retries) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -132,9 +132,12 @@ const initSourceName = initDataProvider.getName(); | ||
if (initDataProvider.getInitDataHash) { | ||
latestInitDataHash = yield this.getLatestInitDataHash(traceId, initDataProvider.getInitDataHash.bind(initDataProvider)); | ||
latestInitDataHash = yield this.getLatestInitDataHash(traceId, initDataProvider.getInitDataHash.bind(initDataProvider), retries); | ||
} | ||
else { | ||
newInitData = yield this.getLatestInitData(traceId, initSourceName, initDataProvider.getInitData.bind(initDataProvider)); | ||
newInitData = yield this.getLatestInitData(traceId, initSourceName, initDataProvider.getInitData.bind(initDataProvider), retries); | ||
latestInitDataHash = newInitData.hash; | ||
} | ||
if (this.initData.hash === latestInitDataHash) { | ||
// Log this as latest init time as verifying that we already have | ||
// the latest commit is equivalent to initialization from server. | ||
this.lastDataProviderInitTime = Date.now(); | ||
this.log(traceId, shared_1.LogLevel.Debug, "Commit hash is already latest."); | ||
@@ -147,3 +150,3 @@ return; | ||
if (!newInitData) { | ||
newInitData = yield this.getLatestInitData(traceId, initSourceName, initDataProvider.getInitData.bind(initDataProvider)); | ||
newInitData = yield this.getLatestInitData(traceId, initSourceName, initDataProvider.getInitData.bind(initDataProvider), retries); | ||
} | ||
@@ -158,3 +161,3 @@ this.init(traceId, initSourceName, newInitData); | ||
} | ||
getLatestInitDataHash(traceId, getInitDataHash) { | ||
getLatestInitDataHash(traceId, getInitDataHash, retries) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -170,3 +173,3 @@ this.log(traceId, shared_1.LogLevel.Debug, "Getting latest commit hash..."); | ||
}, { | ||
retries: 10, | ||
retries, | ||
maxTimeout: 6000, | ||
@@ -183,3 +186,3 @@ onFailedAttempt: (error) => { | ||
} | ||
getLatestInitData(traceId, initSourceName, getInitData) { | ||
getLatestInitData(traceId, initSourceName, getInitData, retries) { | ||
return (0, p_retry_1.default)((attemptNumber) => { | ||
@@ -193,3 +196,3 @@ this.log(traceId, shared_1.LogLevel.Debug, `Attempt ${attemptNumber} to initialize from ${initSourceName}...`); | ||
}, { | ||
retries: 10, | ||
retries, | ||
maxTimeout: 6000, | ||
@@ -204,4 +207,4 @@ onFailedAttempt: (error) => { | ||
} | ||
initAndStartIntervals(loggerFlushIntervalMs) { | ||
if (!this.initDataProvider || this.initIntervalMs <= 0) { | ||
initAndStartIntervals(shouldCheckForUpdates, loggerFlushIntervalMs) { | ||
if (!this.initDataProvider || !shouldCheckForUpdates) { | ||
this.log(/* traceId */ "", shared_1.LogLevel.Info, "Not polling for updates."); | ||
@@ -213,3 +216,3 @@ } | ||
const traceId = (0, shared_1.uniqueId)(); | ||
this.initIfNeeded(traceId) | ||
this.initIfNeeded(traceId, shared_1.defaultRetries) | ||
.catch((error) => this.log(traceId, shared_1.LogLevel.Error, "Error updating from data provider.", (0, getMetadata_1.default)(error))) | ||
@@ -222,4 +225,4 @@ .finally(() => { | ||
} | ||
if (this.initIntervalMs > 0) { | ||
setTimeout(update, this.initIntervalMs); | ||
if (shouldCheckForUpdates) { | ||
setTimeout(update, this.initDataRefreshIntervalMs); | ||
} | ||
@@ -231,3 +234,3 @@ }); | ||
// eslint-disable-next-line @hypertune/prefer-early-returns | ||
if (loggerFlushIntervalMs > 0) { | ||
if (loggerFlushIntervalMs !== null) { | ||
// eslint-disable-next-line func-style | ||
@@ -253,2 +256,8 @@ const flushLogQueue = () => { | ||
// @internal | ||
isReady() { | ||
return this.initDataProvider | ||
? !!this.lastDataProviderInitTime | ||
: !!this.initData; | ||
} | ||
// @internal | ||
getStateHash() { | ||
@@ -313,2 +322,7 @@ var _a, _b; | ||
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.setOverride(traceId, override); | ||
@@ -315,0 +329,0 @@ } |
@@ -13,2 +13,4 @@ "use strict"; | ||
const HypertuneEdgeInitDataProvider_1 = __importDefault(require("./initDataProviders/HypertuneEdgeInitDataProvider")); | ||
const minIntervalMs = 1000; | ||
const defaultIntervalMs = 2000; | ||
function create({ NodeConstructor, token, query, queryCode, variableValues, override, options, }) { | ||
@@ -41,2 +43,3 @@ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; | ||
} | ||
const isNextJsBuildOrServer = (0, environment_1.isNextJsServer)() || (0, environment_1.isNextJsBuild)(); | ||
try { | ||
@@ -56,8 +59,23 @@ const context = new Context_1.default({ | ||
}), | ||
initIntervalMs: (_f = options.initIntervalMs) !== null && _f !== void 0 ? _f : 1000, | ||
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((_f = options.initDataRefreshIntervalMs) !== null && _f !== void 0 ? _f : (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), | ||
logger, | ||
loggerFlushIntervalMs: | ||
// Use provided option or otherwise disable flushing in | ||
// serverless environments or default to 1 second. | ||
((_h = (_g = options.remoteLogging) === null || _g === void 0 ? void 0 : _g.flushIntervalMs) !== null && _h !== void 0 ? _h : environment_1.isServerless) ? 0 : 1000, | ||
// Use the provided option else use the default interval but disable | ||
// flushing by default for Next.js servers. | ||
((_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) | ||
: isNextJsBuildOrServer | ||
? null | ||
: defaultIntervalMs, | ||
cacheSize: (_j = options.cacheSize) !== null && _j !== void 0 ? _j : shared_1.defaultCacheSize, | ||
@@ -87,2 +105,5 @@ override: override !== null && override !== void 0 ? override : null, | ||
exports.default = create; | ||
function clampInterval(intervalMs) { | ||
return Math.max(intervalMs, minIntervalMs); | ||
} | ||
//# sourceMappingURL=create.js.map |
import { CodegenRequestBody, CodegenResponseBody, GraphqlRequestBody, HashResponseBody, InitRequestBody, InitData, RequestType, TracedFetch, ObjectValue, CodegenFramework } from "../shared"; | ||
export declare function codegenRequest({ traceId, token, branchName, framework, clientFileName, query, includeToken, includeFallback, language, edgeBaseUrl, tracedFetch, }: { | ||
export declare function codegenRequest({ traceId, token, branchName, framework, clientFileName, getHypertuneImportPath, query, includeToken, includeFallback, language, edgeBaseUrl, tracedFetch, }: { | ||
traceId: string; | ||
@@ -8,2 +8,3 @@ token: string; | ||
clientFileName: string; | ||
getHypertuneImportPath: string; | ||
query: string | null; | ||
@@ -10,0 +11,0 @@ includeToken: boolean; |
@@ -14,9 +14,9 @@ "use strict"; | ||
const shared_1 = require("../shared"); | ||
function codegenRequest({ traceId, token, branchName, framework, clientFileName, query, includeToken, includeFallback, language, edgeBaseUrl, tracedFetch, }) { | ||
function codegenRequest({ traceId, token, branchName, framework, clientFileName, getHypertuneImportPath, query, includeToken, includeFallback, language, edgeBaseUrl, tracedFetch, }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const body = Object.assign({ query, | ||
const body = Object.assign(Object.assign({ query, | ||
includeToken, | ||
includeFallback, sdkType: "js", sdkVersion: shared_1.sdkVersion, | ||
language, | ||
clientFileName }, (framework ? { framework } : {})); | ||
clientFileName }, (framework ? { framework } : {})), (getHypertuneImportPath ? { getHypertuneImportPath } : {})); | ||
const responseString = yield edgeRequest({ | ||
@@ -23,0 +23,0 @@ traceId, |
export declare const isBrowser: boolean; | ||
export declare function isNextJsBuild(): boolean; | ||
export declare function isNextJsServer(): boolean; | ||
export declare const isReactNative: boolean; | ||
export declare const isBackendServer: boolean; | ||
export declare const isServerless: boolean; | ||
//# sourceMappingURL=environment.d.ts.map |
"use strict"; | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
var _a; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isServerless = exports.isBackendServer = exports.isReactNative = exports.isNextJsServer = exports.isBrowser = void 0; | ||
exports.isBackendServer = exports.isReactNative = exports.isNextJsServer = exports.isNextJsBuild = exports.isBrowser = void 0; | ||
exports.isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; | ||
@@ -12,2 +11,3 @@ function isNextJsBuild() { | ||
} | ||
exports.isNextJsBuild = isNextJsBuild; | ||
// Detect process.browser === false to determine if we're on a NextJS backend. | ||
@@ -28,10 +28,2 @@ // We prefer this over `typeof window` because it gives us a positive indicator | ||
exports.isBackendServer = !exports.isBrowser && !isNextJsBuild() && !isNextJsServer() && !exports.isReactNative; | ||
// @ts-ignore | ||
const isFastly = !!globalThis.fastly; | ||
// @ts-ignore | ||
const isEdgeLight = !!globalThis.EdgeRuntime; | ||
// @ts-ignore | ||
const isNetlify = !!globalThis.Netlify; | ||
const isWorkerd = ((_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.userAgent) === "Cloudflare-Workers"; | ||
exports.isServerless = isFastly || isEdgeLight || isNetlify || isWorkerd; | ||
//# sourceMappingURL=environment.js.map |
@@ -23,3 +23,3 @@ "use strict"; | ||
// eslint-disable-next-line max-params | ||
constructor({ baseUrl, token, branchName = null, localLogger = defaultLocalLogger, timeoutMs = 5000, }) { | ||
constructor({ baseUrl, token, branchName = null, localLogger = defaultLocalLogger, timeoutMs = 10000, }) { | ||
if (branchName !== null && branchName !== shared_1.defaultBranchName) { | ||
@@ -26,0 +26,0 @@ localLogger(types_1.LogLevel.Warn, `using non default branch name: "${branchName}"`, {}); |
@@ -39,3 +39,3 @@ import { Expression, ObjectValue, Query, Step, Value, UpdateListener, ReductionLogs, DehydratedState, DeepPartial, ObjectValueWithVariables } from "../shared"; | ||
*/ | ||
initIfNeeded(traceId?: string): Promise<void>; | ||
initIfNeeded(traceId?: string, retries?: number): Promise<void>; | ||
/** | ||
@@ -46,8 +46,12 @@ * Returns the timestamp of the last time the SDK was initialized from | ||
getLastDataProviderInitTime(): number | null; | ||
/** | ||
* Indicates whether the SDK is ready to evaluate flags and log events. | ||
*/ | ||
isReady(): boolean; | ||
flushLogs(traceId?: string): Promise<void>; | ||
setOverride<T extends ObjectValue>(override: DeepPartial<T>, traceId?: string): void; | ||
dehydrate<TOverride extends object, TVariableValues extends ObjectValue>(query?: Query<ObjectValueWithVariables>, variableValues?: TVariableValues): DehydratedState<TOverride, TVariableValues> | null; | ||
hydrate<TOverride extends object, TVariableValues extends ObjectValue>(dehydratedState: DehydratedState<TOverride, TVariableValues>, traceId?: string): void; | ||
dehydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>(query?: Query<ObjectValueWithVariables>, variableValues?: TVariableValues): DehydratedState<TOverride, TVariableValues> | null; | ||
hydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>(dehydratedState: DehydratedState<TOverride, TVariableValues>, traceId?: string): void; | ||
close(): void; | ||
} | ||
//# sourceMappingURL=Node.d.ts.map |
@@ -307,3 +307,3 @@ "use strict"; | ||
*/ | ||
initIfNeeded(traceId = (0, shared_1.uniqueId)()) { | ||
initIfNeeded(traceId = (0, shared_1.uniqueId)(), retries = 0) { | ||
const { context } = this.props; | ||
@@ -314,3 +314,3 @@ if (!context) { | ||
} | ||
return context.initIfNeeded(traceId); | ||
return context.initIfNeeded(traceId, retries); | ||
} | ||
@@ -329,2 +329,12 @@ /** | ||
} | ||
/** | ||
* Indicates whether the SDK is ready to evaluate flags and log events. | ||
*/ | ||
isReady() { | ||
const { context } = this.props; | ||
if (!context) { | ||
return false; | ||
} | ||
return context.isReady(); | ||
} | ||
flushLogs(traceId = (0, shared_1.uniqueId)()) { | ||
@@ -331,0 +341,0 @@ const { context } = this.props; |
@@ -17,2 +17,3 @@ export declare const logsCreationEndpoint = "/logs"; | ||
export declare const defaultArmKey = "default"; | ||
export declare const defaultRetries = 10; | ||
export declare const defaultCacheSize = 250; | ||
@@ -19,0 +20,0 @@ export declare const breakingSchemaChangesError = "If you've made breaking changes to your schema like adding a new field argument, you may need to re-run code generation and fix the type errors."; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultBranchName = exports.breakingSchemaChangesError = exports.defaultCacheSize = exports.defaultArmKey = exports.numHashBuckets = exports.fieldPathSeparator = exports.isQueryVariableKey = exports.isPartialObjectKey = exports.graphqlTypeNameKey = exports.tokenEnvironmentVariableName = exports.configFileName = exports.traceIdHeaderName = exports.prodLogsEndpointUrl = exports.prodEdgeCdnBaseUrl = exports.stagingLogsEndpointUrl = exports.stagingEdgeCdnBaseUrl = exports.localLogsEndpointUrl = exports.localEdgeWorkerBaseUrl = exports.logsCreationEndpoint = void 0; | ||
exports.defaultBranchName = exports.breakingSchemaChangesError = exports.defaultCacheSize = exports.defaultRetries = exports.defaultArmKey = exports.numHashBuckets = exports.fieldPathSeparator = exports.isQueryVariableKey = exports.isPartialObjectKey = exports.graphqlTypeNameKey = exports.tokenEnvironmentVariableName = exports.configFileName = exports.traceIdHeaderName = exports.prodLogsEndpointUrl = exports.prodEdgeCdnBaseUrl = exports.stagingLogsEndpointUrl = exports.stagingEdgeCdnBaseUrl = exports.localLogsEndpointUrl = exports.localEdgeWorkerBaseUrl = exports.logsCreationEndpoint = void 0; | ||
exports.logsCreationEndpoint = "/logs"; | ||
@@ -20,2 +20,3 @@ exports.localEdgeWorkerBaseUrl = "http://localhost:3002"; | ||
exports.defaultArmKey = "default"; | ||
exports.defaultRetries = 10; | ||
exports.defaultCacheSize = 250; | ||
@@ -22,0 +23,0 @@ exports.breakingSchemaChangesError = "If you've made breaking changes to your schema like adding a new field argument, you may need to re-run code generation and fix the type errors."; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** Replaced by the value in package.json on build */ | ||
exports.default = "2.1.2"; | ||
exports.default = "2.2.0"; | ||
//# sourceMappingURL=sdkVersion.js.map |
@@ -481,3 +481,3 @@ import { isQueryVariableKey } from "./constants"; | ||
export type Language = "ts" | "js" | "python" | "rust"; | ||
export type CodegenFramework = "nextApp" | "nextPages" | "react" | "remix" | "gatsby"; | ||
export type CodegenFramework = "nextApp" | "nextAppFlags" | "nextPages" | "react" | "remix" | "gatsby"; | ||
export type CodegenRequestBody = { | ||
@@ -487,2 +487,3 @@ query: string | null; | ||
clientFileName?: string; | ||
getHypertuneImportPath?: string; | ||
includeToken?: boolean; | ||
@@ -510,4 +511,11 @@ includeFallback?: boolean; | ||
}; | ||
export type CodegenFile = { | ||
name: string; | ||
content: string; | ||
}; | ||
export type CodegenResponseBody = { | ||
code: string; | ||
files: CodegenFile[]; | ||
/** @deprecated */ | ||
code?: string; | ||
/** @deprecated */ | ||
frameworkCode?: string; | ||
@@ -564,3 +572,3 @@ }; | ||
/** | ||
* CreateOptions contains options used to create the Hypertune source. Options | ||
* `CreateOptions` contains options used to create the Hypertune source. Options | ||
* with generated types are defined in your generated code. | ||
@@ -570,4 +578,4 @@ */ | ||
/** | ||
* branchName allows you to specify a Hypertune branch to initialize from. | ||
* This is useful for testing large logic and schema changes, especially | ||
* `branchName` specifies a Hypertune branch to initialize from. | ||
* This is useful for testing significant logic and schema changes, especially | ||
* breaking schema changes, as different schema versions can be served at the | ||
@@ -578,18 +586,17 @@ * same time on different branches during a migration. | ||
/** | ||
* initData specifies initial InitData for the SDK, so that it can be used | ||
* immediately. You can provide this via a build-time snapshot in your | ||
* generated client or via hydration or even manually, e.g. in unit tests. | ||
* `initData` specifies initial `InitData` for the SDK, allowing it to be used | ||
* immediately. This can be supplied via a static build-time snapshot in your | ||
* generated client, via hydration or even manually (e.g. in unit tests). | ||
*/ | ||
initData?: InitData; | ||
/** | ||
* initDataProvider specifies where the SDK should fetch its data from. | ||
* `initDataProvider` specifies where the SDK should fetch its data from. | ||
* | ||
* When set to null the SDK won't fetch any initialization data so it will | ||
* rely on a build-time snapshot in your generated client or hydration. | ||
* - When set to `null` the SDK won't fetch any initialization data, relying | ||
* only on a build-time snapshot in your generated client or hydration. | ||
* - When set to `undefined` it will default to | ||
* `HypertuneEdgeInitDataProvider`. | ||
* - For initialization from Vercel Edge Config, set this to a new instance of | ||
* `VercelEdgeConfigInitDataProvider`. | ||
* | ||
* When set to undefined it will default to HypertuneEdgeInitDataProvider. | ||
* | ||
* If you want to initialize from Vercel Edge Config, then set this to a new | ||
* instance of VercelEdgeConfigInitDataProvider. | ||
* | ||
* @default HypertuneEdgeInitDataProvider | ||
@@ -599,15 +606,31 @@ */ | ||
/** | ||
* initIntervalMs is an interval in milliseconds which determines how often | ||
* the SDK checks for updates. | ||
* `initDataRefreshIntervalMs` specifies the interval, in milliseconds, at | ||
* which the SDK checks for updates. The minimum allowed value is 1000ms. | ||
* | ||
* When set to 0 the SDK will only fetch your latest commit once on | ||
* initialization. | ||
* - When `initIfNeeded` is called, the SDK will make an initialization | ||
* request only if the last initialization occurred at least this interval | ||
* ago. | ||
* | ||
* When initDataProvider is set to null the SDK will never check for updates. | ||
* - When `shouldCheckForUpdates` is `true`, this value controls how often the | ||
* SDK checks for updates in the background. | ||
* | ||
* In a serverless environment, this value controls how often the SDK checks | ||
* for updates when you use the initIfNeeded method. | ||
* - When `initDataProvider` is null, the SDK will never check for updates. | ||
* | ||
* @default 1000 | ||
* @default 2000 | ||
*/ | ||
initDataRefreshIntervalMs?: number; | ||
/** | ||
* `shouldCheckForUpdates` specifies whether the SDK checks for flag updates | ||
* in the background. The frequency of checks is controlled by the | ||
* `initDataRefreshIntervalMs` option. | ||
* | ||
* When `initDataProvider` is `null` the SDK will never check for updates. | ||
* | ||
* @default true (false for Next.js servers) | ||
*/ | ||
shouldCheckForUpdates?: boolean; | ||
/** | ||
* @deprecated Use `initDataRefreshIntervalMs` and `shouldCheckForUpdates` | ||
* instead. | ||
*/ | ||
initIntervalMs?: number; | ||
@@ -624,13 +647,14 @@ /** | ||
/** | ||
* mode determines how log messages, expression evaluations, A/B test | ||
* `mode` determines how log messages, expression evaluations, A/B test | ||
* exposures and analytics events are sent to the remote server. | ||
* | ||
* When set to "normal", all data is sent to the remote server. | ||
* - When set to "normal", all data is sent to the remote server. | ||
* | ||
* When set to "off", no data is sent to the remote server. This is useful | ||
* - When set to "off", no data is sent to the remote server. This is useful | ||
* in development and test environments. | ||
* | ||
* When set to "session", log messages, expression evaluations and A/B test | ||
* exposures are deduplicated per session, based on the provided context. | ||
* However, all analytics events are still sent to the remote server. | ||
* - When set to "session", log messages, expression evaluations and A/B | ||
* test exposures are deduplicated per session, based on the provided | ||
* context. However, all analytics events are still sent to the remote | ||
* server. | ||
* | ||
@@ -641,6 +665,6 @@ * @default "normal" ("session" when the SDK is used in the browser) | ||
/** | ||
* endpointUrl allows you to send SDK log messages, expression evaluations, | ||
* A/B test exposures and analytics events to your own server. You can then | ||
* forward them to Hypertune Edge or your own analytics or observability | ||
* tools. | ||
* `endpointUrl` allows you to send SDK log messages, expression | ||
* evaluations, A/B test exposures and analytics events to your own server. | ||
* You can then forward them to Hypertune Edge or your own analytics or | ||
* observability tools. | ||
* | ||
@@ -651,11 +675,12 @@ * @default https://gcp.fasthorse.workers.dev/logs | ||
/** | ||
* flushIntervalMs is an interval in milliseconds which determines how often | ||
* the SDK flushes logs to the remote server. | ||
* `flushIntervalMs` specifies the interval, in milliseconds, at which the | ||
* SDK flushes logs to the remote server. The minimum allowed value is | ||
* 1000ms. | ||
* | ||
* When set to 0 the SDK will never automatically flush logs. In this case, | ||
* you would need to flush logs manually using the flushLogs method. | ||
* When set to `null`, the SDK will never automatically flush logs, so you | ||
* you will need to flush logs manually using the `flushLogs` method. | ||
* | ||
* @default 1000 (0 in serverless environments) | ||
* @default 2000 (null for Next.js servers) | ||
*/ | ||
flushIntervalMs?: number; | ||
flushIntervalMs?: number | null; | ||
}; | ||
@@ -662,0 +687,0 @@ localLogger?: LocalLogger; |
{ | ||
"name": "hypertune", | ||
"version": "2.1.2", | ||
"version": "2.2.0", | ||
"private": false, | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -25,2 +25,3 @@ import { CAC } from "cac"; | ||
edgeBaseUrl?: string; | ||
getHypertuneImportPath?: string; | ||
}; | ||
@@ -47,2 +48,3 @@ | ||
edgeBaseUrl: "string", | ||
getHypertuneImportPath: "string", | ||
}; | ||
@@ -68,14 +70,21 @@ | ||
) | ||
.option("--outputFilePath [value]", "Where to write the generated file") | ||
.option( | ||
"--outputFilePath [value]", | ||
"(Deprecated) The path to write the generated file to" | ||
) | ||
.option( | ||
"--outputDirectoryPath [value]", | ||
"Where to write the generated files", | ||
"The directory to write the generated files to", | ||
{ default: defaultOptions.outputDirectoryPath } | ||
) | ||
.option("--includeToken", "Include your token in the generated code", { | ||
default: defaultOptions.includeToken, | ||
}) | ||
.option( | ||
"--includeToken", | ||
"Include the project token in the generated code", | ||
{ | ||
default: defaultOptions.includeToken, | ||
} | ||
) | ||
.option( | ||
"--includeInitData", | ||
"Embed a snapshot of your configuration logic in the generated code so the SDK can reliably, locally initialize first, before fetching the latest logic from the server, and can work even if the server is unreachable", | ||
"Embed a static snapshot of your flag logic in the generated code so the SDK can reliably, locally and instantly initialize first, before fetching the latest logic from the server, and can function even if the server is unreachable", | ||
{ | ||
@@ -85,3 +94,3 @@ default: defaultOptions.includeInitData, | ||
) | ||
.option("--language [value]", "Target language (ts or js).", { | ||
.option("--language [value]", "Target language (ts or js)", { | ||
default: defaultOptions.language, | ||
@@ -91,3 +100,7 @@ }) | ||
"--framework [value]", | ||
"Framework to use (nextApp, nextPages, react, remix or gatsby)." | ||
"Framework (nextApp, nextAppFlags, nextPages, react, remix or gatsby)" | ||
) | ||
.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 nextAppFlags framework" | ||
); | ||
@@ -114,3 +127,4 @@ } | ||
outputFilePath: | ||
options.outputFilePath ?? `${outputDirectory}/hypertune.${language}`, | ||
options.outputFilePath ?? | ||
path.join(outputDirectory, `hypertune.${language}`), | ||
includeToken: options.includeToken ?? defaultOptions.includeToken, | ||
@@ -121,2 +135,3 @@ includeFallback: | ||
edgeBaseUrl: options.edgeBaseUrl ?? prodEdgeCdnBaseUrl, | ||
getHypertuneImportPath: options.getHypertuneImportPath ?? "", | ||
}); | ||
@@ -143,2 +158,3 @@ console.log("Done"); | ||
edgeBaseUrl, | ||
getHypertuneImportPath, | ||
}: { | ||
@@ -154,3 +170,10 @@ token: string; | ||
edgeBaseUrl: string; | ||
getHypertuneImportPath: string; | ||
}): Promise<void> { | ||
if (framework === "nextAppFlags" && !getHypertuneImportPath) { | ||
throw new Error( | ||
`The "getHypertuneImportPath" option must be specified when using the "nextAppFlags" framework.` | ||
); | ||
} | ||
const extension = path.extname(outputFilePath); | ||
@@ -172,2 +195,3 @@ const clientFileName = path.basename( | ||
edgeBaseUrl, | ||
getHypertuneImportPath, | ||
tracedFetch: (_, url, init) => { | ||
@@ -183,12 +207,5 @@ return fetch(url, init); | ||
writeFile(outputFilePath, codegenResponse.code); | ||
if (codegenResponse.frameworkCode) { | ||
const frameworkFilePath = `${outputFilePath.slice( | ||
0, | ||
outputFilePath.length - extension.length | ||
)}.react${extension}x`; | ||
writeFile(frameworkFilePath, codegenResponse.frameworkCode); | ||
} | ||
codegenResponse.files.forEach(({ name, content }) => { | ||
writeFile(path.join(outputDirectoryPath, name), content); | ||
}); | ||
} | ||
@@ -195,0 +212,0 @@ |
@@ -31,3 +31,2 @@ import "regenerator-runtime/runtime"; | ||
DeepPartial, | ||
merge, | ||
}; | ||
@@ -39,2 +38,3 @@ | ||
create, | ||
merge, | ||
/** | ||
@@ -41,0 +41,0 @@ * @deprecated use create directly instead. |
@@ -24,2 +24,3 @@ import pRetry from "p-retry"; | ||
GetInitDataFunction, | ||
defaultRetries, | ||
} from "../shared"; | ||
@@ -34,3 +35,3 @@ import Logger from "./Logger"; | ||
protected readonly initDataProvider: InitDataProvider | null; | ||
protected readonly initIntervalMs: number; | ||
protected readonly initDataRefreshIntervalMs: number; | ||
protected readonly query: Query<ObjectValueWithVariables>; | ||
@@ -64,3 +65,4 @@ protected readonly queryCode: string; | ||
initDataProvider, | ||
initIntervalMs, | ||
initDataRefreshIntervalMs, | ||
shouldCheckForUpdates, | ||
query, | ||
@@ -76,3 +78,4 @@ queryCode, | ||
initDataProvider: InitDataProvider | null; | ||
initIntervalMs: number; | ||
initDataRefreshIntervalMs: number; | ||
shouldCheckForUpdates: boolean; | ||
query: Query<ObjectValueWithVariables>; | ||
@@ -82,3 +85,3 @@ queryCode: string; | ||
logger: Logger; | ||
loggerFlushIntervalMs: number; | ||
loggerFlushIntervalMs: number | null; | ||
cacheSize: number; | ||
@@ -88,3 +91,3 @@ override: object | null; | ||
this.initDataProvider = initDataProvider; | ||
this.initIntervalMs = initIntervalMs; | ||
this.initDataRefreshIntervalMs = initDataRefreshIntervalMs; | ||
this.query = query; | ||
@@ -113,3 +116,3 @@ this.queryCode = queryCode; | ||
} | ||
this.initAndStartIntervals(loggerFlushIntervalMs); | ||
this.initAndStartIntervals(shouldCheckForUpdates, loggerFlushIntervalMs); | ||
@@ -209,4 +212,8 @@ if (isBrowser) { | ||
// @internal | ||
initIfNeeded(traceId: string): Promise<void> { | ||
const { lastDataProviderInitTime, initDataProvider, initIntervalMs } = this; | ||
initIfNeeded(traceId: string, retries: number): Promise<void> { | ||
const { | ||
lastDataProviderInitTime, | ||
initDataProvider, | ||
initDataRefreshIntervalMs, | ||
} = this; | ||
@@ -224,7 +231,7 @@ if (!initDataProvider) { | ||
const msSinceLastDataProviderInit = Date.now() - lastDataProviderInitTime; | ||
if (msSinceLastDataProviderInit < initIntervalMs) { | ||
if (msSinceLastDataProviderInit < initDataRefreshIntervalMs) { | ||
this.log( | ||
traceId, | ||
LogLevel.Debug, | ||
`Not initializing from data provider as already did ${msSinceLastDataProviderInit}ms ago which is less than the update interval of ${initIntervalMs}ms.` | ||
`Not initializing from data provider as already did ${msSinceLastDataProviderInit}ms ago which is less than the update interval of ${initDataRefreshIntervalMs}ms.` | ||
); | ||
@@ -235,3 +242,3 @@ return Promise.resolve(); | ||
return this.initFromDataProvider(traceId, initDataProvider); | ||
return this.initFromDataProvider(traceId, initDataProvider, retries); | ||
} | ||
@@ -241,3 +248,4 @@ | ||
traceId: string, | ||
initDataProvider: InitDataProvider | ||
initDataProvider: InitDataProvider, | ||
retries: number | ||
): Promise<void> { | ||
@@ -257,3 +265,4 @@ const initSourceName = initDataProvider.getName(); | ||
traceId, | ||
initDataProvider.getInitDataHash.bind(initDataProvider) | ||
initDataProvider.getInitDataHash.bind(initDataProvider), | ||
retries | ||
); | ||
@@ -264,3 +273,4 @@ } else { | ||
initSourceName, | ||
initDataProvider.getInitData.bind(initDataProvider) | ||
initDataProvider.getInitData.bind(initDataProvider), | ||
retries | ||
); | ||
@@ -271,2 +281,5 @@ latestInitDataHash = newInitData.hash; | ||
if (this.initData.hash === latestInitDataHash) { | ||
// Log this as latest init time as verifying that we already have | ||
// the latest commit is equivalent to initialization from server. | ||
this.lastDataProviderInitTime = Date.now(); | ||
this.log(traceId, LogLevel.Debug, "Commit hash is already latest."); | ||
@@ -291,3 +304,4 @@ return; | ||
initSourceName, | ||
initDataProvider.getInitData.bind(initDataProvider) | ||
initDataProvider.getInitData.bind(initDataProvider), | ||
retries | ||
); | ||
@@ -310,3 +324,4 @@ } | ||
traceId: string, | ||
getInitDataHash: GetInitDataHashFunction | ||
getInitDataHash: GetInitDataHashFunction, | ||
retries: number | ||
): Promise<string> { | ||
@@ -329,3 +344,3 @@ this.log(traceId, LogLevel.Debug, "Getting latest commit hash..."); | ||
{ | ||
retries: 10, | ||
retries, | ||
maxTimeout: 6000, | ||
@@ -354,3 +369,4 @@ onFailedAttempt: (error) => { | ||
initSourceName: string, | ||
getInitData: GetInitDataFunction | ||
getInitData: GetInitDataFunction, | ||
retries: number | ||
): Promise<InitData> { | ||
@@ -371,3 +387,3 @@ return pRetry( | ||
{ | ||
retries: 10, | ||
retries, | ||
maxTimeout: 6000, | ||
@@ -391,4 +407,7 @@ onFailedAttempt: (error) => { | ||
private initAndStartIntervals(loggerFlushIntervalMs: number): void { | ||
if (!this.initDataProvider || this.initIntervalMs <= 0) { | ||
private initAndStartIntervals( | ||
shouldCheckForUpdates: boolean, | ||
loggerFlushIntervalMs: number | null | ||
): void { | ||
if (!this.initDataProvider || !shouldCheckForUpdates) { | ||
this.log(/* traceId */ "", LogLevel.Info, "Not polling for updates."); | ||
@@ -401,3 +420,3 @@ } | ||
this.initIfNeeded(traceId) | ||
this.initIfNeeded(traceId, defaultRetries) | ||
.catch((error) => | ||
@@ -420,4 +439,4 @@ this.log( | ||
} | ||
if (this.initIntervalMs > 0) { | ||
setTimeout(update, this.initIntervalMs); | ||
if (shouldCheckForUpdates) { | ||
setTimeout(update, this.initDataRefreshIntervalMs); | ||
} | ||
@@ -430,3 +449,3 @@ }); | ||
// eslint-disable-next-line @hypertune/prefer-early-returns | ||
if (loggerFlushIntervalMs > 0) { | ||
if (loggerFlushIntervalMs !== null) { | ||
// eslint-disable-next-line func-style | ||
@@ -460,2 +479,9 @@ const flushLogQueue = (): void => { | ||
// @internal | ||
isReady(): boolean { | ||
return this.initDataProvider | ||
? !!this.lastDataProviderInitTime | ||
: !!this.initData; | ||
} | ||
// @internal | ||
getStateHash(): string { | ||
@@ -504,3 +530,3 @@ const initDataHash = this.initData?.hash ?? null; | ||
// @internal | ||
dehydrate<TOverride extends object, TVariableValues extends ObjectValue>( | ||
dehydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>( | ||
query?: Query<ObjectValueWithVariables>, | ||
@@ -542,3 +568,3 @@ variableValues?: TVariableValues | ||
initData, | ||
override: override as DeepPartial<TOverride>, | ||
override: override as DeepPartial<TOverride> | null, | ||
variableValues: dehydrateQueryVariableValues, | ||
@@ -560,2 +586,7 @@ }; | ||
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.setOverride<TOverride>(traceId, override); | ||
@@ -562,0 +593,0 @@ } |
@@ -16,6 +16,9 @@ import { | ||
import Logger from "./Logger"; | ||
import { isBrowser, isServerless } from "./environment"; | ||
import { isBrowser, isNextJsBuild, isNextJsServer } from "./environment"; | ||
import getMetadata from "../shared/helpers/getMetadata"; | ||
import HypertuneEdgeInitDataProvider from "./initDataProviders/HypertuneEdgeInitDataProvider"; | ||
const minIntervalMs = 1_000; | ||
const defaultIntervalMs = 2_000; | ||
export default function create<T extends Node>({ | ||
@@ -69,2 +72,3 @@ NodeConstructor, | ||
const isNextJsBuildOrServer = isNextJsServer() || isNextJsBuild(); | ||
try { | ||
@@ -85,8 +89,27 @@ const context = new Context({ | ||
}), | ||
initIntervalMs: options.initIntervalMs ?? 1_000, | ||
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) | ||
), | ||
shouldCheckForUpdates: | ||
// Prioritize the new option else if the old option is set to 0, | ||
// disable, else enable except for Next.js servers. | ||
options.shouldCheckForUpdates ?? | ||
(options.initIntervalMs === 0 ? false : !isNextJsBuildOrServer), | ||
logger, | ||
loggerFlushIntervalMs: | ||
// Use provided option or otherwise disable flushing in | ||
// serverless environments or default to 1 second. | ||
options.remoteLogging?.flushIntervalMs ?? isServerless ? 0 : 1_000, | ||
// Use the provided option else use the default interval but disable | ||
// flushing by default for Next.js servers. | ||
options.remoteLogging?.flushIntervalMs !== undefined | ||
? options.remoteLogging.flushIntervalMs === null || | ||
options.remoteLogging.flushIntervalMs === 0 // TODO: Deprecate | ||
? null | ||
: clampInterval(options.remoteLogging.flushIntervalMs) | ||
: isNextJsBuildOrServer | ||
? null | ||
: defaultIntervalMs, | ||
cacheSize: options.cacheSize ?? defaultCacheSize, | ||
@@ -119,1 +142,5 @@ override: override ?? null, | ||
} | ||
function clampInterval(intervalMs: number): number { | ||
return Math.max(intervalMs, minIntervalMs); | ||
} |
@@ -21,2 +21,3 @@ import { | ||
clientFileName, | ||
getHypertuneImportPath, | ||
query, | ||
@@ -34,2 +35,3 @@ includeToken, | ||
clientFileName: string; | ||
getHypertuneImportPath: string; | ||
query: string | null; | ||
@@ -51,2 +53,3 @@ includeToken: boolean; | ||
...(framework ? { framework } : {}), | ||
...(getHypertuneImportPath ? { getHypertuneImportPath } : {}), | ||
}; | ||
@@ -53,0 +56,0 @@ |
@@ -6,3 +6,3 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
function isNextJsBuild(): boolean { | ||
export function isNextJsBuild(): boolean { | ||
return ( | ||
@@ -35,11 +35,1 @@ typeof process !== "undefined" && | ||
!isBrowser && !isNextJsBuild() && !isNextJsServer() && !isReactNative; | ||
// @ts-ignore | ||
const isFastly = !!globalThis.fastly; | ||
// @ts-ignore | ||
const isEdgeLight = !!globalThis.EdgeRuntime; | ||
// @ts-ignore | ||
const isNetlify = !!globalThis.Netlify; | ||
const isWorkerd = globalThis.navigator?.userAgent === "Cloudflare-Workers"; | ||
export const isServerless = isFastly || isEdgeLight || isNetlify || isWorkerd; |
@@ -27,3 +27,3 @@ import { | ||
localLogger = defaultLocalLogger, | ||
timeoutMs = 5_000, | ||
timeoutMs = 10_000, | ||
}: { | ||
@@ -30,0 +30,0 @@ baseUrl: string; |
@@ -510,3 +510,3 @@ /* eslint-disable no-underscore-dangle */ | ||
*/ | ||
initIfNeeded(traceId = uniqueId()): Promise<void> { | ||
initIfNeeded(traceId = uniqueId(), retries = 0): Promise<void> { | ||
const { context } = this.props; | ||
@@ -520,3 +520,3 @@ if (!context) { | ||
} | ||
return context.initIfNeeded(traceId); | ||
return context.initIfNeeded(traceId, retries); | ||
} | ||
@@ -540,2 +540,13 @@ | ||
/** | ||
* Indicates whether the SDK is ready to evaluate flags and log events. | ||
*/ | ||
isReady(): boolean { | ||
const { context } = this.props; | ||
if (!context) { | ||
return false; | ||
} | ||
return context.isReady(); | ||
} | ||
flushLogs(traceId = uniqueId()): Promise<void> { | ||
@@ -562,3 +573,3 @@ const { context } = this.props; | ||
dehydrate<TOverride extends object, TVariableValues extends ObjectValue>( | ||
dehydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>( | ||
query?: Query<ObjectValueWithVariables>, | ||
@@ -575,3 +586,3 @@ variableValues?: TVariableValues | ||
hydrate<TOverride extends object, TVariableValues extends ObjectValue>( | ||
hydrate<TOverride extends ObjectValue, TVariableValues extends ObjectValue>( | ||
dehydratedState: DehydratedState<TOverride, TVariableValues>, | ||
@@ -578,0 +589,0 @@ traceId = uniqueId() |
@@ -25,2 +25,3 @@ export const logsCreationEndpoint = "/logs"; | ||
export const defaultRetries = 10; | ||
export const defaultCacheSize = 250; | ||
@@ -27,0 +28,0 @@ |
@@ -676,2 +676,3 @@ /* eslint-disable capitalized-comments */ | ||
| "nextApp" | ||
| "nextAppFlags" | ||
| "nextPages" | ||
@@ -686,2 +687,3 @@ | "react" | ||
clientFileName?: string; | ||
getHypertuneImportPath?: string; | ||
includeToken?: boolean; | ||
@@ -713,4 +715,15 @@ includeFallback?: boolean; | ||
export type CodegenResponseBody = { code: string; frameworkCode?: string }; | ||
export type CodegenFile = { | ||
name: string; | ||
content: string; | ||
}; | ||
export type CodegenResponseBody = { | ||
files: CodegenFile[]; | ||
/** @deprecated */ | ||
code?: string; | ||
/** @deprecated */ | ||
frameworkCode?: string; | ||
}; | ||
export type InitData = { | ||
@@ -789,3 +802,3 @@ commitId: number; | ||
/** | ||
* CreateOptions contains options used to create the Hypertune source. Options | ||
* `CreateOptions` contains options used to create the Hypertune source. Options | ||
* with generated types are defined in your generated code. | ||
@@ -795,4 +808,4 @@ */ | ||
/** | ||
* branchName allows you to specify a Hypertune branch to initialize from. | ||
* This is useful for testing large logic and schema changes, especially | ||
* `branchName` specifies a Hypertune branch to initialize from. | ||
* This is useful for testing significant logic and schema changes, especially | ||
* breaking schema changes, as different schema versions can be served at the | ||
@@ -804,18 +817,17 @@ * same time on different branches during a migration. | ||
/** | ||
* initData specifies initial InitData for the SDK, so that it can be used | ||
* immediately. You can provide this via a build-time snapshot in your | ||
* generated client or via hydration or even manually, e.g. in unit tests. | ||
* `initData` specifies initial `InitData` for the SDK, allowing it to be used | ||
* immediately. This can be supplied via a static build-time snapshot in your | ||
* generated client, via hydration or even manually (e.g. in unit tests). | ||
*/ | ||
initData?: InitData; | ||
/** | ||
* initDataProvider specifies where the SDK should fetch its data from. | ||
* `initDataProvider` specifies where the SDK should fetch its data from. | ||
* | ||
* When set to null the SDK won't fetch any initialization data so it will | ||
* rely on a build-time snapshot in your generated client or hydration. | ||
* - When set to `null` the SDK won't fetch any initialization data, relying | ||
* only on a build-time snapshot in your generated client or hydration. | ||
* - When set to `undefined` it will default to | ||
* `HypertuneEdgeInitDataProvider`. | ||
* - For initialization from Vercel Edge Config, set this to a new instance of | ||
* `VercelEdgeConfigInitDataProvider`. | ||
* | ||
* When set to undefined it will default to HypertuneEdgeInitDataProvider. | ||
* | ||
* If you want to initialize from Vercel Edge Config, then set this to a new | ||
* instance of VercelEdgeConfigInitDataProvider. | ||
* | ||
* @default HypertuneEdgeInitDataProvider | ||
@@ -825,15 +837,31 @@ */ | ||
/** | ||
* initIntervalMs is an interval in milliseconds which determines how often | ||
* the SDK checks for updates. | ||
* `initDataRefreshIntervalMs` specifies the interval, in milliseconds, at | ||
* which the SDK checks for updates. The minimum allowed value is 1000ms. | ||
* | ||
* When set to 0 the SDK will only fetch your latest commit once on | ||
* initialization. | ||
* - When `initIfNeeded` is called, the SDK will make an initialization | ||
* request only if the last initialization occurred at least this interval | ||
* ago. | ||
* | ||
* When initDataProvider is set to null the SDK will never check for updates. | ||
* - When `shouldCheckForUpdates` is `true`, this value controls how often the | ||
* SDK checks for updates in the background. | ||
* | ||
* In a serverless environment, this value controls how often the SDK checks | ||
* for updates when you use the initIfNeeded method. | ||
* - When `initDataProvider` is null, the SDK will never check for updates. | ||
* | ||
* @default 1000 | ||
* @default 2000 | ||
*/ | ||
initDataRefreshIntervalMs?: number; | ||
/** | ||
* `shouldCheckForUpdates` specifies whether the SDK checks for flag updates | ||
* in the background. The frequency of checks is controlled by the | ||
* `initDataRefreshIntervalMs` option. | ||
* | ||
* When `initDataProvider` is `null` the SDK will never check for updates. | ||
* | ||
* @default true (false for Next.js servers) | ||
*/ | ||
shouldCheckForUpdates?: boolean; | ||
/** | ||
* @deprecated Use `initDataRefreshIntervalMs` and `shouldCheckForUpdates` | ||
* instead. | ||
*/ | ||
initIntervalMs?: number; | ||
@@ -851,13 +879,14 @@ /** | ||
/** | ||
* mode determines how log messages, expression evaluations, A/B test | ||
* `mode` determines how log messages, expression evaluations, A/B test | ||
* exposures and analytics events are sent to the remote server. | ||
* | ||
* When set to "normal", all data is sent to the remote server. | ||
* - When set to "normal", all data is sent to the remote server. | ||
* | ||
* When set to "off", no data is sent to the remote server. This is useful | ||
* - When set to "off", no data is sent to the remote server. This is useful | ||
* in development and test environments. | ||
* | ||
* When set to "session", log messages, expression evaluations and A/B test | ||
* exposures are deduplicated per session, based on the provided context. | ||
* However, all analytics events are still sent to the remote server. | ||
* - When set to "session", log messages, expression evaluations and A/B | ||
* test exposures are deduplicated per session, based on the provided | ||
* context. However, all analytics events are still sent to the remote | ||
* server. | ||
* | ||
@@ -868,6 +897,6 @@ * @default "normal" ("session" when the SDK is used in the browser) | ||
/** | ||
* endpointUrl allows you to send SDK log messages, expression evaluations, | ||
* A/B test exposures and analytics events to your own server. You can then | ||
* forward them to Hypertune Edge or your own analytics or observability | ||
* tools. | ||
* `endpointUrl` allows you to send SDK log messages, expression | ||
* evaluations, A/B test exposures and analytics events to your own server. | ||
* You can then forward them to Hypertune Edge or your own analytics or | ||
* observability tools. | ||
* | ||
@@ -878,11 +907,12 @@ * @default https://gcp.fasthorse.workers.dev/logs | ||
/** | ||
* flushIntervalMs is an interval in milliseconds which determines how often | ||
* the SDK flushes logs to the remote server. | ||
* `flushIntervalMs` specifies the interval, in milliseconds, at which the | ||
* SDK flushes logs to the remote server. The minimum allowed value is | ||
* 1000ms. | ||
* | ||
* When set to 0 the SDK will never automatically flush logs. In this case, | ||
* you would need to flush logs manually using the flushLogs method. | ||
* When set to `null`, the SDK will never automatically flush logs, so you | ||
* you will need to flush logs manually using the `flushLogs` method. | ||
* | ||
* @default 1000 (0 in serverless environments) | ||
* @default 2000 (null for Next.js servers) | ||
*/ | ||
flushIntervalMs?: number; | ||
flushIntervalMs?: number | null; | ||
}; | ||
@@ -889,0 +919,0 @@ localLogger?: LocalLogger; |
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
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
705183
12861