hypertune
Advanced tools
Comparing version 1.7.23 to 1.8.0
# Changelog | ||
## 1.8.0 | ||
- Performance improvements. | ||
## 1.7.23 | ||
@@ -4,0 +8,0 @@ |
@@ -22,5 +22,2 @@ export type Maybe<T> = T | null; | ||
}; | ||
export type AcceptInviteInput = { | ||
inviteToken: Scalars['String']; | ||
}; | ||
export type AddVercelAuthInput = { | ||
@@ -46,2 +43,3 @@ businessId: Scalars['ID']; | ||
id: Scalars['ID']; | ||
inviteToken?: Maybe<Scalars['String']>; | ||
invites: Array<Invite>; | ||
@@ -52,3 +50,2 @@ memberCount: Scalars['Int']; | ||
projects: Array<Project>; | ||
publicToken: Scalars['String']; | ||
type: BusinessType; | ||
@@ -85,4 +82,7 @@ users: Array<User>; | ||
export type CreateBusinessInput = { | ||
inviteEmails: Array<Scalars['String']>; | ||
inviteToken?: InputMaybe<Scalars['String']>; | ||
name: Scalars['String']; | ||
skipSwitch: Scalars['Boolean']; | ||
transferProjectIds: Array<Scalars['ID']>; | ||
}; | ||
@@ -273,3 +273,2 @@ export type CreateCommitInput = { | ||
__typename?: 'Mutation'; | ||
acceptInvite: Scalars['ID']; | ||
addVercelAuth: Scalars['ID']; | ||
@@ -289,2 +288,3 @@ cloneProject: Scalars['ID']; | ||
revokeInvite: Scalars['ID']; | ||
sendVerifyAndInviteEmail: Scalars['ID']; | ||
updateAnalyticsView: Scalars['ID']; | ||
@@ -295,6 +295,4 @@ updateBusiness: Scalars['ID']; | ||
updateWebhook: Scalars['ID']; | ||
verifyEmailAndAcceptInvite: VerifyEmailAndAcceptInviteResponse; | ||
}; | ||
export type MutationAcceptInviteArgs = { | ||
input: AcceptInviteInput; | ||
}; | ||
export type MutationAddVercelAuthArgs = { | ||
@@ -339,2 +337,5 @@ input: AddVercelAuthInput; | ||
}; | ||
export type MutationSendVerifyAndInviteEmailArgs = { | ||
input: SendVerifyAndInviteEmailInput; | ||
}; | ||
export type MutationUpdateAnalyticsViewArgs = { | ||
@@ -355,2 +356,5 @@ input: UpdateAnalyticsViewInput; | ||
}; | ||
export type MutationVerifyEmailAndAcceptInviteArgs = { | ||
input: VerifyEmailAndAcceptInviteInput; | ||
}; | ||
export type MutationResponseMessage = { | ||
@@ -376,2 +380,3 @@ __typename?: 'MutationResponseMessage'; | ||
name: Scalars['String']; | ||
setupCompleted: Scalars['Boolean']; | ||
showOnboarding: Scalars['Boolean']; | ||
@@ -422,2 +427,5 @@ token: Scalars['String']; | ||
}; | ||
export type SendVerifyAndInviteEmailInput = { | ||
verifyEmailAndInviteToken?: InputMaybe<Scalars['String']>; | ||
}; | ||
export type UpdateAnalyticsViewInput = { | ||
@@ -431,2 +439,3 @@ funnelStepsJson?: InputMaybe<Scalars['String']>; | ||
id: Scalars['ID']; | ||
inviteToken?: InputMaybe<Scalars['String']>; | ||
name?: InputMaybe<Scalars['String']>; | ||
@@ -457,2 +466,3 @@ vercelEdgeConfigConnectionsJson?: InputMaybe<Scalars['String']>; | ||
email: Scalars['String']; | ||
emailVerified: Scalars['Boolean']; | ||
id: Scalars['ID']; | ||
@@ -462,2 +472,12 @@ imageUrl: Scalars['String']; | ||
}; | ||
export type VerifyEmailAndAcceptInviteInput = { | ||
businessInviteToken?: InputMaybe<Scalars['String']>; | ||
inviteToken?: InputMaybe<Scalars['String']>; | ||
verifyEmailAndInviteToken?: InputMaybe<Scalars['String']>; | ||
}; | ||
export type VerifyEmailAndAcceptInviteResponse = { | ||
__typename?: 'VerifyEmailAndAcceptInviteResponse'; | ||
joinedBusinessesCount: Scalars['Int']; | ||
verifiedEmail: Scalars['Boolean']; | ||
}; | ||
export type Webhook = { | ||
@@ -464,0 +484,0 @@ __typename?: 'Webhook'; |
import { InitResponseBody, Expression, Query, Value, ObjectValueWithVariables, InitSource, VercelEdgeConfigClient, Endpoints, UpdateListener, Fetch } from "../shared"; | ||
import Logger from "./Logger"; | ||
import LRUCache from "../shared/helpers/LRUCache"; | ||
/** @internal: Not part of the Hypertune public API */ | ||
@@ -23,6 +24,9 @@ export default class Context { | ||
lastServerInitTime: number | null; | ||
readonly getFieldCache: LRUCache<Expression> | null; | ||
readonly getItemsCache: LRUCache<Expression[]> | null; | ||
readonly evaluateCache: LRUCache<Value> | null; | ||
readonly updateListeners: Map<UpdateListener, boolean>; | ||
constructor(token: string, queryCode: string, variableValues: { | ||
[variableName: string]: Value; | ||
}, schemaVersion: string | null, shouldInitializeFromServer: boolean, shouldStartIntervals: boolean, shouldGetUpdatesFromServer: boolean, logger: Logger, fetchFunction: Fetch, vercelEdgeConfigClient: VercelEdgeConfigClient | null, vercelEdgeConfigItemKey: string | null, endpoints: Endpoints, query: Query<ObjectValueWithVariables>, fallbackInitData: InitResponseBody | null); | ||
}, schemaVersion: string | null, shouldInitializeFromServer: boolean, shouldStartIntervals: boolean, shouldGetUpdatesFromServer: boolean, logger: Logger, fetchFunction: Fetch, vercelEdgeConfigClient: VercelEdgeConfigClient | null, vercelEdgeConfigItemKey: string | null, cacheSize: number, endpoints: Endpoints, query: Query<ObjectValueWithVariables>, fallbackInitData: InitResponseBody | null); | ||
initFromData(initData: InitResponseBody | null): void; | ||
@@ -29,0 +33,0 @@ private init; |
@@ -21,6 +21,7 @@ "use strict"; | ||
const environment_1 = require("./environment"); | ||
const LRUCache_1 = __importDefault(require("../shared/helpers/LRUCache")); | ||
/** @internal: Not part of the Hypertune public API */ | ||
class Context { | ||
// eslint-disable-next-line max-params | ||
constructor(token, queryCode, variableValues, schemaVersion, shouldInitializeFromServer, shouldStartIntervals, shouldGetUpdatesFromServer, logger, fetchFunction, vercelEdgeConfigClient, vercelEdgeConfigItemKey, endpoints, query, fallbackInitData) { | ||
constructor(token, queryCode, variableValues, schemaVersion, shouldInitializeFromServer, shouldStartIntervals, shouldGetUpdatesFromServer, logger, fetchFunction, vercelEdgeConfigClient, vercelEdgeConfigItemKey, cacheSize, endpoints, query, fallbackInitData) { | ||
this.shouldClose = false; | ||
@@ -42,3 +43,11 @@ this.token = token; | ||
this.lastServerInitTime = null; | ||
this.getFieldCache = null; | ||
this.getItemsCache = null; | ||
this.evaluateCache = null; | ||
this.updateListeners = new Map(); | ||
if (cacheSize > 0) { | ||
this.getFieldCache = new LRUCache_1.default(cacheSize); | ||
this.getItemsCache = new LRUCache_1.default(cacheSize); | ||
this.evaluateCache = new LRUCache_1.default(cacheSize); | ||
} | ||
if (fallbackInitData) { | ||
@@ -79,3 +88,3 @@ this.initFromData(fallbackInitData); | ||
init(initSource, newInitData) { | ||
var _a, _b; | ||
var _a, _b, _c, _d, _e; | ||
const initSourceName = (0, getInitSourceName_1.default)(initSource); | ||
@@ -99,2 +108,5 @@ try { | ||
this.logger.info(`Initialized successfully from ${initSourceName}.`); | ||
(_c = this.getFieldCache) === null || _c === void 0 ? void 0 : _c.purge(); | ||
(_d = this.getItemsCache) === null || _d === void 0 ? void 0 : _d.purge(); | ||
(_e = this.evaluateCache) === null || _e === void 0 ? void 0 : _e.purge(); | ||
this.updateListeners.forEach((_, listener) => { | ||
@@ -101,0 +113,0 @@ listener(newInitData.commitHash); |
@@ -16,3 +16,3 @@ "use strict"; | ||
function initialize(NodeConstructor, 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; | ||
let token; | ||
@@ -61,3 +61,3 @@ let endpoints; | ||
// native apps | ||
((0, environment_1.isNextJsServer)() || environment_1.isBackendServer || environment_1.isReactNative), logger, fetchFunction, (_g = options.vercelEdgeConfigClient) !== null && _g !== void 0 ? _g : null, (_h = options.vercelEdgeConfigItemKey) !== null && _h !== void 0 ? _h : null, endpoints, options.query, (_j = options.fallbackInitData) !== null && _j !== void 0 ? _j : null); | ||
((0, environment_1.isNextJsServer)() || environment_1.isBackendServer || environment_1.isReactNative), logger, fetchFunction, (_g = options.vercelEdgeConfigClient) !== null && _g !== void 0 ? _g : null, (_h = options.vercelEdgeConfigItemKey) !== null && _h !== void 0 ? _h : null, (_j = options.cacheSize) !== null && _j !== void 0 ? _j : shared_1.defaultCacheSize, endpoints, options.query, (_k = options.fallbackInitData) !== null && _k !== void 0 ? _k : null); | ||
return new NodeConstructor({ | ||
@@ -68,3 +68,3 @@ context, | ||
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, | ||
}); | ||
@@ -71,0 +71,0 @@ } |
@@ -18,6 +18,9 @@ import { Expression, ObjectValue, Query, Step, Value, InitResponseBody, UpdateListener } from "../shared"; | ||
protected getField(fieldName: string, fieldArguments: ObjectValue): Props; | ||
private getFieldFallback; | ||
private getReducedFieldExpression; | ||
_getItems(fallbackLength: number): Props[]; | ||
private _getItemsFallback; | ||
private _getItemExpressions; | ||
protected evaluate(query: Query<ObjectValue> | null, fallback: Value): Value; | ||
private getValue; | ||
private createProps; | ||
private createPropsArray; | ||
_logUnexpectedTypeError(): void; | ||
@@ -29,3 +32,2 @@ protected logUnexpectedValueError(value: Value): void; | ||
private nodeError; | ||
private getPath; | ||
getCommitHash(): string | null; | ||
@@ -32,0 +34,0 @@ addUpdateListener(listener: UpdateListener): void; |
@@ -61,2 +61,25 @@ "use strict"; | ||
getField(fieldName, fieldArguments) { | ||
var _a; | ||
const step = { type: "GetFieldStep", fieldName, fieldArguments }; | ||
const { context } = this.props; | ||
const commitHash = (_a = context === null || context === void 0 ? void 0 : context.initData) === null || _a === void 0 ? void 0 : _a.commitHash; | ||
if (!commitHash || !context.getFieldCache) { | ||
// No caching if the sdk hasn't been initialized or there is no cache. | ||
return this.createProps(step, this.getReducedFieldExpression(fieldName, fieldArguments)); | ||
} | ||
const cacheKey = getCacheKey(commitHash, | ||
/* parent */ this, step, | ||
/* suffix */ ""); | ||
const cachedReducedFieldExpression = context.getFieldCache.get(cacheKey); | ||
if (cachedReducedFieldExpression) { | ||
return this.createProps(step, cachedReducedFieldExpression); | ||
} | ||
const reducedFieldExpression = this.getReducedFieldExpression(fieldName, fieldArguments); | ||
if (reducedFieldExpression) { | ||
context.getFieldCache.set(cacheKey, reducedFieldExpression); | ||
} | ||
return this.createProps(step, reducedFieldExpression); | ||
} | ||
// @internal | ||
getReducedFieldExpression(fieldName, fieldArguments) { | ||
try { | ||
@@ -71,6 +94,6 @@ // As fieldArguments are passed in by users, perform extra validation to | ||
this.updateIfNeeded(); | ||
const { logger, expression } = this.props; | ||
const { expression } = this.props; | ||
if (!expression) { | ||
this.debug(`Using fallback for field "${fieldName}" as expression is null. This is expected while initializing.`); | ||
return this.getFieldFallback(fieldName, fieldArguments); | ||
return null; | ||
} | ||
@@ -93,32 +116,37 @@ if (expression.type !== "ObjectExpression") { | ||
reducedFieldExpression.logs = (0, shared_1.mergeLogs)(reducedObjectExpression.logs, (0, shared_1.getEvaluationLogs)(reducedObjectExpression), reducedFieldExpression.logs); | ||
return { | ||
context, | ||
parent: this, | ||
step: { type: "GetFieldStep", fieldName, fieldArguments }, | ||
expression: reducedFieldExpression, | ||
logger, | ||
}; | ||
return reducedFieldExpression; | ||
} | ||
catch (error) { | ||
this.nodeError({ type: "GetField", fieldName, fieldArguments }, error); | ||
return this.getFieldFallback(fieldName, fieldArguments); | ||
return null; | ||
} | ||
} | ||
getFieldFallback(fieldName, fieldArguments) { | ||
const { logger, context } = this.props; | ||
return { | ||
context, | ||
parent: this, | ||
step: { type: "GetFieldStep", fieldName, fieldArguments }, | ||
expression: null, | ||
logger, | ||
}; | ||
_getItems(fallbackLength) { | ||
var _a; | ||
const { context } = this.props; | ||
const commitHash = (_a = context === null || context === void 0 ? void 0 : context.initData) === null || _a === void 0 ? void 0 : _a.commitHash; | ||
if (!commitHash || !context.getItemsCache) { | ||
// No caching if the sdk hasn't been initialized or there is no cache. | ||
return this.createPropsArray(this._getItemExpressions(), fallbackLength); | ||
} | ||
const cacheKey = getCacheKey(commitHash, this.props.parent, this.props.step, | ||
/* suffix */ ""); | ||
const cachedItemExpressions = context.getItemsCache.get(cacheKey); | ||
if (cachedItemExpressions) { | ||
return this.createPropsArray(cachedItemExpressions, fallbackLength); | ||
} | ||
const itemExpressions = this._getItemExpressions(); | ||
if (itemExpressions) { | ||
context.getItemsCache.set(cacheKey, itemExpressions); | ||
} | ||
return this.createPropsArray(itemExpressions, fallbackLength); | ||
} | ||
_getItems(fallbackLength) { | ||
// @internal | ||
_getItemExpressions() { | ||
try { | ||
this.updateIfNeeded(); | ||
const { context, logger, expression } = this.props; | ||
const { expression } = this.props; | ||
if (!expression) { | ||
this.debug("Using fallback for array items as expression is null. This is expected while initializing."); | ||
return this._getItemsFallback(fallbackLength); | ||
return null; | ||
} | ||
@@ -132,9 +160,3 @@ if (expression.type !== "ListExpression") { | ||
itemExpression.logs = (0, shared_1.mergeLogs)(listLogs, itemExpression.logs); | ||
return { | ||
context, | ||
parent: this, | ||
step: { type: "GetItemStep", index, fallbackLength }, | ||
expression: itemExpression, | ||
logger, | ||
}; | ||
return itemExpression; | ||
}); | ||
@@ -145,18 +167,27 @@ return result; | ||
this.nodeError({ type: "GetItems" }, error); | ||
return this._getItemsFallback(fallbackLength); | ||
return null; | ||
} | ||
} | ||
_getItemsFallback(fallbackLength) { | ||
const { context, logger } = this.props; | ||
return Array(fallbackLength) | ||
.fill(0) | ||
.map((_, index) => ({ | ||
context, | ||
parent: this, | ||
step: { type: "GetItemStep", index, fallbackLength }, | ||
expression: null, | ||
logger, | ||
})); | ||
evaluate(query, fallback) { | ||
var _a, _b; | ||
const { context } = this.props; | ||
const commitHash = (_a = context === null || context === void 0 ? void 0 : context.initData) === null || _a === void 0 ? void 0 : _a.commitHash; | ||
if (!commitHash || !context.evaluateCache) { | ||
// No caching if the sdk hasn't been initialized or there is no cache. | ||
return (_b = this.getValue(query)) !== null && _b !== void 0 ? _b : fallback; | ||
} | ||
const cacheKey = getCacheKey(commitHash, this.props.parent, this.props.step, | ||
/* suffix */ JSON.stringify(query)); | ||
const cachedValue = context.evaluateCache.get(cacheKey); | ||
if (cachedValue !== null && cachedValue !== undefined) { | ||
return cachedValue; | ||
} | ||
const value = this.getValue(query); | ||
if (value !== null && value !== undefined) { | ||
context.evaluateCache.set(cacheKey, value); | ||
} | ||
return value !== null && value !== void 0 ? value : fallback; | ||
} | ||
evaluate(query, fallback) { | ||
// @internal | ||
getValue(query) { | ||
try { | ||
@@ -167,3 +198,3 @@ this.updateIfNeeded(); | ||
this.debug(`Using fallback while evaluating as expression is null. This is expected while initializing.`); | ||
return fallback; | ||
return null; | ||
} | ||
@@ -174,3 +205,4 @@ const context = (0, shared_1.nullThrows)(this.props.context, "Cannot evaluate as context is null."); | ||
const { value, logs } = (0, shared_1.prefixError)(() => (0, shared_1.evaluate)(reducedExpression), "Evaluation error: "); | ||
context.logger.nodeEvaluation(this.getPath(), { type: "Evaluate", query }, commitHash, this.typeName, logs, expression, value); | ||
const { parent, step } = this.props; | ||
context.logger.nodeEvaluation(getPath(parent, step), { type: "Evaluate", query }, commitHash, this.typeName, logs, expression, value); | ||
return value; | ||
@@ -180,5 +212,14 @@ } | ||
this.nodeError({ type: "Evaluate", query }, error); | ||
return fallback; | ||
return null; | ||
} | ||
} | ||
// @internal | ||
createProps(step, expression) { | ||
const { context, logger } = this.props; | ||
return { step, parent: this, context, expression, logger }; | ||
} | ||
// @internal | ||
createPropsArray(itemExpressions, fallbackLength) { | ||
return (itemExpressions || Array(fallbackLength).fill(null)).map((expression, index) => this.createProps({ type: "GetItemStep", index, fallbackLength }, expression)); | ||
} | ||
_logUnexpectedTypeError() { | ||
@@ -227,19 +268,10 @@ if (!this.props.expression) { | ||
var _a; | ||
const { logger, expression } = this.props; | ||
const { parent, step, logger, expression } = this.props; | ||
if (!logger) { | ||
// eslint-disable-next-line no-console | ||
console.error(`Logger is null so cannot remote log error for ${this.typeName} node at ${this.getPath()}`, error); | ||
console.error(`Logger is null so cannot remote log error for ${this.typeName} node at ${getPath(parent, step)}`, error); | ||
return; | ||
} | ||
logger.nodeError(this.getPath(), nodeOp, this.commitHash, this.typeName, expression, error instanceof Error ? error.message : JSON.stringify(error), error instanceof Error ? (_a = error.stack) !== null && _a !== void 0 ? _a : null : null); | ||
logger.nodeError(getPath(parent, step), nodeOp, this.commitHash, this.typeName, expression, error instanceof Error ? error.message : JSON.stringify(error), error instanceof Error ? (_a = error.stack) !== null && _a !== void 0 ? _a : null : null); | ||
} | ||
// @internal | ||
getPath() { | ||
const { parent, step } = this.props; | ||
return `${parent ? `${parent.getPath()} > ` : ""}${!step | ||
? "{}" | ||
: step.type === "GetFieldStep" | ||
? `${step.fieldName}(${JSON.stringify(step.fieldArguments)})` | ||
: `[${step.index}]`}`; | ||
} | ||
getCommitHash() { | ||
@@ -332,2 +364,13 @@ var _a, _b; | ||
exports.default = Node; | ||
function getCacheKey(commitHash, parent, step, suffix) { | ||
return (0, shared_1.hash)(`${commitHash}/${getPath(parent, step)}/${suffix} | ||
)}`).toString(); | ||
} | ||
function getPath(parent, step) { | ||
return `${parent ? `${getPath(parent.props.parent, parent.props.step)} > ` : ""}${!step | ||
? "{}" | ||
: step.type === "GetFieldStep" | ||
? `${step.fieldName}(${JSON.stringify(step.fieldArguments)})` | ||
: `[${step.index}]`}`; | ||
} | ||
//# sourceMappingURL=Node.js.map |
@@ -19,3 +19,4 @@ export declare const localBackendBaseUrl = "http://localhost:3001"; | ||
export declare const defaultArmKey = "default"; | ||
export declare const defaultCacheSize = 250; | ||
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."; | ||
//# sourceMappingURL=constants.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.breakingSchemaChangesError = exports.defaultArmKey = exports.numHashBuckets = exports.fieldPathSeparator = exports.isQueryVariableKey = exports.isPartialObjectKey = exports.graphqlTypeNameKey = exports.tokenEnvironmentVariableName = exports.configFileName = exports.backendGraphqlEndpoint = exports.prodLogsBaseUrl = exports.prodEdgeBaseUrl = exports.prodBackendBaseUrl = exports.stagingLogsBaseUrl = exports.stagingEdgeBaseUrl = exports.stagingBackendBaseUrl = exports.localLogsBaseUrl = exports.localEdgeBaseUrl = exports.localBackendBaseUrl = void 0; | ||
exports.breakingSchemaChangesError = exports.defaultCacheSize = exports.defaultArmKey = exports.numHashBuckets = exports.fieldPathSeparator = exports.isQueryVariableKey = exports.isPartialObjectKey = exports.graphqlTypeNameKey = exports.tokenEnvironmentVariableName = exports.configFileName = exports.backendGraphqlEndpoint = exports.prodLogsBaseUrl = exports.prodEdgeBaseUrl = exports.prodBackendBaseUrl = exports.stagingLogsBaseUrl = exports.stagingEdgeBaseUrl = exports.stagingBackendBaseUrl = exports.localLogsBaseUrl = exports.localEdgeBaseUrl = exports.localBackendBaseUrl = void 0; | ||
exports.localBackendBaseUrl = "http://localhost:3001"; | ||
@@ -22,3 +22,4 @@ exports.localEdgeBaseUrl = "http://[::1]:3002"; | ||
exports.defaultArmKey = "default"; | ||
exports.defaultCacheSize = 250; | ||
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."; | ||
//# sourceMappingURL=constants.js.map |
@@ -920,14 +920,12 @@ "use strict"; | ||
} | ||
function getField(object, fieldPath) { | ||
if (object.type !== "ObjectExpression") { | ||
return null; | ||
function getField(startObject, fieldPath) { | ||
const fieldPathParts = fieldPath.split(constants_1.fieldPathSeparator); | ||
let field = startObject; | ||
for (let i = 0; i < fieldPathParts.length; i += 1) { | ||
if (!field || field.type !== "ObjectExpression") { | ||
return null; | ||
} | ||
const fieldName = fieldPathParts[i]; | ||
field = field.fields[fieldName] || null; | ||
} | ||
const [fieldName, ...rest] = fieldPath.split(constants_1.fieldPathSeparator); | ||
const field = object.fields[fieldName]; | ||
if (!field) { | ||
return null; | ||
} | ||
if (rest.length > 0) { | ||
return getField(field, rest.join(constants_1.fieldPathSeparator)); | ||
} | ||
return field; | ||
@@ -934,0 +932,0 @@ } |
@@ -47,12 +47,12 @@ "use strict"; | ||
return { | ||
evaluations: mergeCountMaps(a.evaluations, bs.map((b) => b.evaluations)), | ||
events: mergeCountMaps(a.events, bs.map((b) => b.events)), | ||
exposures: mergeCountMaps(a.exposures, bs.map((b) => b.exposures)), | ||
evaluations: mergeCountMapsFromLogs("evaluations", a, bs), | ||
events: mergeCountMapsFromLogs("events", a, bs), | ||
exposures: mergeCountMapsFromLogs("exposures", a, bs), | ||
}; | ||
} | ||
exports.mergeLogs = mergeLogs; | ||
function mergeCountMaps(a, bs) { | ||
const result = Object.assign({}, a); | ||
function mergeCountMapsFromLogs(countMapKey, a, bs) { | ||
const result = Object.assign({}, a[countMapKey]); | ||
bs.forEach((b) => { | ||
Object.entries(b).forEach(([key, value]) => { | ||
Object.entries(b[countMapKey]).forEach(([key, value]) => { | ||
result[key] = (result[key] || 0) + value; | ||
@@ -59,0 +59,0 @@ }); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** Replaced by the value in package.json on build */ | ||
exports.default = "1.7.23"; | ||
exports.default = "1.8.0"; | ||
//# sourceMappingURL=sdkVersion.js.map |
@@ -609,2 +609,3 @@ import { z } from "zod"; | ||
vercelEdgeConfigItemKey?: string; | ||
cacheSize?: number; | ||
/** Hypertune internal use only */ | ||
@@ -611,0 +612,0 @@ _endpoints?: EndpointsPreset | Endpoints; |
{ | ||
"name": "hypertune", | ||
"version": "1.7.23", | ||
"version": "1.8.0", | ||
"private": false, | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -8,3 +8,3 @@ /* eslint-disable no-underscore-dangle */ | ||
Options extends object = object, | ||
OptionsInput extends object = object | ||
OptionsInput extends object = object, | ||
> = z.ZodObject<z.ZodRawShape, "strip", z.ZodTypeAny, Options, OptionsInput>; | ||
@@ -56,3 +56,3 @@ | ||
OptionsInput extends object, | ||
Result | ||
Result, | ||
>( | ||
@@ -181,3 +181,3 @@ handler: Handler<Options, Result>, | ||
OptionsInput extends object, | ||
HandlerResult | ||
HandlerResult, | ||
>( | ||
@@ -184,0 +184,0 @@ schema: Schema<Options, OptionsInput>, |
@@ -15,6 +15,2 @@ export type Maybe<T> = T | null; | ||
export type AcceptInviteInput = { | ||
inviteToken: Scalars['String']; | ||
}; | ||
export type AddVercelAuthInput = { | ||
@@ -43,2 +39,3 @@ businessId: Scalars['ID']; | ||
id: Scalars['ID']; | ||
inviteToken?: Maybe<Scalars['String']>; | ||
invites: Array<Invite>; | ||
@@ -49,3 +46,2 @@ memberCount: Scalars['Int']; | ||
projects: Array<Project>; | ||
publicToken: Scalars['String']; | ||
type: BusinessType; | ||
@@ -87,4 +83,7 @@ users: Array<User>; | ||
export type CreateBusinessInput = { | ||
inviteEmails: Array<Scalars['String']>; | ||
inviteToken?: InputMaybe<Scalars['String']>; | ||
name: Scalars['String']; | ||
skipSwitch: Scalars['Boolean']; | ||
transferProjectIds: Array<Scalars['ID']>; | ||
}; | ||
@@ -304,3 +303,2 @@ | ||
__typename?: 'Mutation'; | ||
acceptInvite: Scalars['ID']; | ||
addVercelAuth: Scalars['ID']; | ||
@@ -320,2 +318,3 @@ cloneProject: Scalars['ID']; | ||
revokeInvite: Scalars['ID']; | ||
sendVerifyAndInviteEmail: Scalars['ID']; | ||
updateAnalyticsView: Scalars['ID']; | ||
@@ -326,10 +325,6 @@ updateBusiness: Scalars['ID']; | ||
updateWebhook: Scalars['ID']; | ||
verifyEmailAndAcceptInvite: VerifyEmailAndAcceptInviteResponse; | ||
}; | ||
export type MutationAcceptInviteArgs = { | ||
input: AcceptInviteInput; | ||
}; | ||
export type MutationAddVercelAuthArgs = { | ||
@@ -400,2 +395,7 @@ input: AddVercelAuthInput; | ||
export type MutationSendVerifyAndInviteEmailArgs = { | ||
input: SendVerifyAndInviteEmailInput; | ||
}; | ||
export type MutationUpdateAnalyticsViewArgs = { | ||
@@ -425,2 +425,7 @@ input: UpdateAnalyticsViewInput; | ||
export type MutationVerifyEmailAndAcceptInviteArgs = { | ||
input: VerifyEmailAndAcceptInviteInput; | ||
}; | ||
export type MutationResponseMessage = { | ||
@@ -448,2 +453,3 @@ __typename?: 'MutationResponseMessage'; | ||
name: Scalars['String']; | ||
setupCompleted: Scalars['Boolean']; | ||
showOnboarding: Scalars['Boolean']; | ||
@@ -510,2 +516,6 @@ token: Scalars['String']; | ||
export type SendVerifyAndInviteEmailInput = { | ||
verifyEmailAndInviteToken?: InputMaybe<Scalars['String']>; | ||
}; | ||
export type UpdateAnalyticsViewInput = { | ||
@@ -520,2 +530,3 @@ funnelStepsJson?: InputMaybe<Scalars['String']>; | ||
id: Scalars['ID']; | ||
inviteToken?: InputMaybe<Scalars['String']>; | ||
name?: InputMaybe<Scalars['String']>; | ||
@@ -550,2 +561,3 @@ vercelEdgeConfigConnectionsJson?: InputMaybe<Scalars['String']>; | ||
email: Scalars['String']; | ||
emailVerified: Scalars['Boolean']; | ||
id: Scalars['ID']; | ||
@@ -556,2 +568,14 @@ imageUrl: Scalars['String']; | ||
export type VerifyEmailAndAcceptInviteInput = { | ||
businessInviteToken?: InputMaybe<Scalars['String']>; | ||
inviteToken?: InputMaybe<Scalars['String']>; | ||
verifyEmailAndInviteToken?: InputMaybe<Scalars['String']>; | ||
}; | ||
export type VerifyEmailAndAcceptInviteResponse = { | ||
__typename?: 'VerifyEmailAndAcceptInviteResponse'; | ||
joinedBusinessesCount: Scalars['Int']; | ||
verifiedEmail: Scalars['Boolean']; | ||
}; | ||
export type Webhook = { | ||
@@ -558,0 +582,0 @@ __typename?: 'Webhook'; |
@@ -23,2 +23,3 @@ import pRetry from "p-retry"; | ||
import { isBrowser } from "./environment"; | ||
import LRUCache from "../shared/helpers/LRUCache"; | ||
@@ -43,2 +44,5 @@ /** @internal: Not part of the Hypertune public API */ | ||
public lastServerInitTime: number | null; | ||
public readonly getFieldCache: LRUCache<Expression> | null; | ||
public readonly getItemsCache: LRUCache<Expression[]> | null; | ||
public readonly evaluateCache: LRUCache<Value> | null; | ||
public readonly updateListeners: Map<UpdateListener, boolean>; | ||
@@ -59,2 +63,3 @@ | ||
vercelEdgeConfigItemKey: string | null, | ||
cacheSize: number, | ||
endpoints: Endpoints, | ||
@@ -80,4 +85,13 @@ query: Query<ObjectValueWithVariables>, | ||
this.lastServerInitTime = null; | ||
this.getFieldCache = null; | ||
this.getItemsCache = null; | ||
this.evaluateCache = null; | ||
this.updateListeners = new Map(); | ||
if (cacheSize > 0) { | ||
this.getFieldCache = new LRUCache(cacheSize); | ||
this.getItemsCache = new LRUCache(cacheSize); | ||
this.evaluateCache = new LRUCache(cacheSize); | ||
} | ||
if (fallbackInitData) { | ||
@@ -162,2 +176,5 @@ this.initFromData(fallbackInitData); | ||
this.getFieldCache?.purge(); | ||
this.getItemsCache?.purge(); | ||
this.evaluateCache?.purge(); | ||
this.updateListeners.forEach((_, listener) => { | ||
@@ -387,12 +404,15 @@ listener(newInitData.commitHash); | ||
const flushLogQueue = (): void => { | ||
setTimeout(async () => { | ||
if (this.shouldClose) { | ||
this.logger.debug("Stopped flushing log queue."); | ||
return; | ||
} | ||
setTimeout( | ||
async () => { | ||
if (this.shouldClose) { | ||
this.logger.debug("Stopped flushing log queue."); | ||
return; | ||
} | ||
await this.logger.flush(); | ||
await this.logger.flush(); | ||
flushLogQueue(); | ||
}, this.initData?.sdkConfig.flushLogsInterval ?? 1000); | ||
flushLogQueue(); | ||
}, | ||
this.initData?.sdkConfig.flushLogsInterval ?? 1000 | ||
); | ||
}; | ||
@@ -422,2 +442,3 @@ flushLogQueue(); | ||
); | ||
return prefixError( | ||
@@ -424,0 +445,0 @@ () => |
@@ -8,2 +8,3 @@ import { fetch } from "cross-fetch"; | ||
Fetch, | ||
defaultCacheSize, | ||
} from "../shared"; | ||
@@ -90,2 +91,3 @@ import { InternalInitializeOptions, LogLevel } from ".."; | ||
options.vercelEdgeConfigItemKey ?? null, | ||
options.cacheSize ?? defaultCacheSize, | ||
endpoints, | ||
@@ -92,0 +94,0 @@ options.query, |
@@ -20,2 +20,3 @@ /* eslint-disable no-underscore-dangle */ | ||
UpdateListener, | ||
hash, | ||
} from "../shared"; | ||
@@ -108,2 +109,40 @@ import getDeepestZodIssue from "../shared/helpers/getDeepestZodIssue"; | ||
protected getField(fieldName: string, fieldArguments: ObjectValue): Props { | ||
const step: Step = { type: "GetFieldStep", fieldName, fieldArguments }; | ||
const { context } = this.props; | ||
const commitHash = context?.initData?.commitHash; | ||
if (!commitHash || !context.getFieldCache) { | ||
// No caching if the sdk hasn't been initialized or there is no cache. | ||
return this.createProps( | ||
step, | ||
this.getReducedFieldExpression(fieldName, fieldArguments) | ||
); | ||
} | ||
const cacheKey = getCacheKey( | ||
commitHash, | ||
/* parent */ this, | ||
step, | ||
/* suffix */ "" | ||
); | ||
const cachedReducedFieldExpression = context.getFieldCache.get(cacheKey); | ||
if (cachedReducedFieldExpression) { | ||
return this.createProps(step, cachedReducedFieldExpression); | ||
} | ||
const reducedFieldExpression = this.getReducedFieldExpression( | ||
fieldName, | ||
fieldArguments | ||
); | ||
if (reducedFieldExpression) { | ||
context.getFieldCache.set(cacheKey, reducedFieldExpression); | ||
} | ||
return this.createProps(step, reducedFieldExpression); | ||
} | ||
// @internal | ||
private getReducedFieldExpression( | ||
fieldName: string, | ||
fieldArguments: ObjectValue | ||
): Expression | null { | ||
try { | ||
@@ -122,3 +161,3 @@ // As fieldArguments are passed in by users, perform extra validation to | ||
const { logger, expression } = this.props; | ||
const { expression } = this.props; | ||
@@ -129,3 +168,3 @@ if (!expression) { | ||
); | ||
return this.getFieldFallback(fieldName, fieldArguments); | ||
return null; | ||
} | ||
@@ -168,35 +207,42 @@ | ||
return { | ||
context, | ||
parent: this, | ||
step: { type: "GetFieldStep", fieldName, fieldArguments }, | ||
expression: reducedFieldExpression, | ||
logger, | ||
}; | ||
return reducedFieldExpression; | ||
} catch (error) { | ||
this.nodeError({ type: "GetField", fieldName, fieldArguments }, error); | ||
return this.getFieldFallback(fieldName, fieldArguments); | ||
return null; | ||
} | ||
} | ||
private getFieldFallback( | ||
fieldName: string, | ||
fieldArguments: ObjectValue | ||
): Props { | ||
const { logger, context } = this.props; | ||
_getItems(fallbackLength: number): Props[] { | ||
const { context } = this.props; | ||
const commitHash = context?.initData?.commitHash; | ||
return { | ||
context, | ||
parent: this, | ||
step: { type: "GetFieldStep", fieldName, fieldArguments }, | ||
expression: null, | ||
logger, | ||
}; | ||
if (!commitHash || !context.getItemsCache) { | ||
// No caching if the sdk hasn't been initialized or there is no cache. | ||
return this.createPropsArray(this._getItemExpressions(), fallbackLength); | ||
} | ||
const cacheKey = getCacheKey( | ||
commitHash, | ||
this.props.parent, | ||
this.props.step, | ||
/* suffix */ "" | ||
); | ||
const cachedItemExpressions = context.getItemsCache.get(cacheKey); | ||
if (cachedItemExpressions) { | ||
return this.createPropsArray(cachedItemExpressions, fallbackLength); | ||
} | ||
const itemExpressions = this._getItemExpressions(); | ||
if (itemExpressions) { | ||
context.getItemsCache.set(cacheKey, itemExpressions); | ||
} | ||
return this.createPropsArray(itemExpressions, fallbackLength); | ||
} | ||
_getItems(fallbackLength: number): Props[] { | ||
// @internal | ||
private _getItemExpressions(): Expression[] | null { | ||
try { | ||
this.updateIfNeeded(); | ||
const { context, logger, expression } = this.props; | ||
const { expression } = this.props; | ||
@@ -207,3 +253,3 @@ if (!expression) { | ||
); | ||
return this._getItemsFallback(fallbackLength); | ||
return null; | ||
} | ||
@@ -222,3 +268,3 @@ | ||
const result: Props[] = expression.items.map((item, index) => { | ||
const result: Expression[] = expression.items.map((item, index) => { | ||
const itemExpression = nullThrows( | ||
@@ -230,9 +276,3 @@ item, | ||
return { | ||
context, | ||
parent: this, | ||
step: { type: "GetItemStep", index, fallbackLength }, | ||
expression: itemExpression, | ||
logger, | ||
}; | ||
return itemExpression; | ||
}); | ||
@@ -243,23 +283,35 @@ | ||
this.nodeError({ type: "GetItems" }, error); | ||
return this._getItemsFallback(fallbackLength); | ||
return null; | ||
} | ||
} | ||
private _getItemsFallback(fallbackLength: number): Props[] { | ||
const { context, logger } = this.props; | ||
protected evaluate(query: Query<ObjectValue> | null, fallback: Value): Value { | ||
const { context } = this.props; | ||
const commitHash = context?.initData?.commitHash; | ||
return Array(fallbackLength) | ||
.fill(0) | ||
.map( | ||
(_, index): Props => ({ | ||
context, | ||
parent: this, | ||
step: { type: "GetItemStep", index, fallbackLength }, | ||
expression: null, | ||
logger, | ||
}) | ||
); | ||
if (!commitHash || !context.evaluateCache) { | ||
// No caching if the sdk hasn't been initialized or there is no cache. | ||
return this.getValue(query) ?? fallback; | ||
} | ||
const cacheKey = getCacheKey( | ||
commitHash, | ||
this.props.parent, | ||
this.props.step, | ||
/* suffix */ JSON.stringify(query) | ||
); | ||
const cachedValue = context.evaluateCache.get(cacheKey); | ||
if (cachedValue !== null && cachedValue !== undefined) { | ||
return cachedValue; | ||
} | ||
const value = this.getValue(query); | ||
if (value !== null && value !== undefined) { | ||
context.evaluateCache.set(cacheKey, value); | ||
} | ||
return value ?? fallback; | ||
} | ||
protected evaluate(query: Query<ObjectValue> | null, fallback: Value): Value { | ||
// @internal | ||
private getValue(query: Query<ObjectValue> | null): Value | null { | ||
try { | ||
@@ -273,3 +325,3 @@ this.updateIfNeeded(); | ||
); | ||
return fallback; | ||
return null; | ||
} | ||
@@ -294,4 +346,5 @@ | ||
const { parent, step } = this.props; | ||
context.logger.nodeEvaluation( | ||
this.getPath(), | ||
getPath(parent, step), | ||
{ type: "Evaluate", query }, | ||
@@ -309,6 +362,26 @@ commitHash, | ||
return fallback; | ||
return null; | ||
} | ||
} | ||
// @internal | ||
private createProps(step: Step, expression: Expression | null): Props { | ||
const { context, logger } = this.props; | ||
return { step, parent: this, context, expression, logger }; | ||
} | ||
// @internal | ||
private createPropsArray( | ||
itemExpressions: Expression[] | null, | ||
fallbackLength: number | ||
): Props[] { | ||
return (itemExpressions || Array(fallbackLength).fill(null)).map( | ||
(expression, index) => | ||
this.createProps( | ||
{ type: "GetItemStep", index, fallbackLength }, | ||
expression | ||
) | ||
); | ||
} | ||
_logUnexpectedTypeError(): void { | ||
@@ -370,3 +443,3 @@ if (!this.props.expression) { | ||
private nodeError(nodeOp: DbNodeOp | null, error: unknown): void { | ||
const { logger, expression } = this.props; | ||
const { parent, step, logger, expression } = this.props; | ||
if (!logger) { | ||
@@ -377,3 +450,3 @@ // eslint-disable-next-line no-console | ||
this.typeName | ||
} node at ${this.getPath()}`, | ||
} node at ${getPath(parent, step)}`, | ||
error | ||
@@ -385,3 +458,3 @@ ); | ||
logger.nodeError( | ||
this.getPath(), | ||
getPath(parent, step), | ||
nodeOp, | ||
@@ -396,14 +469,2 @@ this.commitHash, | ||
// @internal | ||
private getPath(): string { | ||
const { parent, step } = this.props; | ||
return `${parent ? `${parent.getPath()} > ` : ""}${ | ||
!step | ||
? "{}" | ||
: step.type === "GetFieldStep" | ||
? `${step.fieldName}(${JSON.stringify(step.fieldArguments)})` | ||
: `[${step.index}]` | ||
}`; | ||
} | ||
getCommitHash(): string | null { | ||
@@ -505,1 +566,25 @@ const { context } = this.props; | ||
} | ||
function getCacheKey( | ||
commitHash: string, | ||
parent: Node | null, | ||
step: Step | null, | ||
suffix: string | ||
): string { | ||
return hash( | ||
`${commitHash}/${getPath(parent, step)}/${suffix} | ||
)}` | ||
).toString(); | ||
} | ||
function getPath(parent: Node | null, step: Step | null): string { | ||
return `${ | ||
parent ? `${getPath(parent.props.parent, parent.props.step)} > ` : "" | ||
}${ | ||
!step | ||
? "{}" | ||
: step.type === "GetFieldStep" | ||
? `${step.fieldName}(${JSON.stringify(step.fieldArguments)})` | ||
: `[${step.index}]` | ||
}`; | ||
} |
@@ -26,3 +26,5 @@ export const localBackendBaseUrl = "http://localhost:3001"; | ||
export const defaultCacheSize = 250; | ||
export 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."; |
@@ -448,4 +448,4 @@ /* eslint-disable capitalized-comments */ | ||
: innerQuery[objectTypeName] | ||
? innerQuery[objectTypeName].selection | ||
: {}; | ||
? innerQuery[objectTypeName].selection | ||
: {}; | ||
@@ -1700,14 +1700,16 @@ Object.keys(expression.updates).forEach((fieldName) => { | ||
function getField(object: Expression, fieldPath: string): Expression | null { | ||
if (object.type !== "ObjectExpression") { | ||
return null; | ||
function getField( | ||
startObject: Expression, | ||
fieldPath: string | ||
): Expression | null { | ||
const fieldPathParts = fieldPath.split(fieldPathSeparator); | ||
let field: Expression | null = startObject; | ||
for (let i = 0; i < fieldPathParts.length; i += 1) { | ||
if (!field || field.type !== "ObjectExpression") { | ||
return null; | ||
} | ||
const fieldName = fieldPathParts[i]; | ||
field = field.fields[fieldName] || null; | ||
} | ||
const [fieldName, ...rest] = fieldPath.split(fieldPathSeparator); | ||
const field = object.fields[fieldName]; | ||
if (!field) { | ||
return null; | ||
} | ||
if (rest.length > 0) { | ||
return getField(field, rest.join(fieldPathSeparator)); | ||
} | ||
return field; | ||
@@ -1714,0 +1716,0 @@ } |
@@ -46,21 +46,16 @@ import stableStringify from "./stableStringify"; | ||
return { | ||
evaluations: mergeCountMaps( | ||
a.evaluations, | ||
bs.map((b) => b.evaluations) | ||
), | ||
events: mergeCountMaps( | ||
a.events, | ||
bs.map((b) => b.events) | ||
), | ||
exposures: mergeCountMaps( | ||
a.exposures, | ||
bs.map((b) => b.exposures) | ||
), | ||
evaluations: mergeCountMapsFromLogs("evaluations", a, bs), | ||
events: mergeCountMapsFromLogs("events", a, bs), | ||
exposures: mergeCountMapsFromLogs("exposures", a, bs), | ||
}; | ||
} | ||
function mergeCountMaps(a: CountMap, bs: CountMap[]): CountMap { | ||
const result: CountMap = { ...a }; | ||
function mergeCountMapsFromLogs<K extends keyof ReductionLogs>( | ||
countMapKey: K, | ||
a: ReductionLogs, | ||
bs: ReductionLogs[] | ||
): CountMap { | ||
const result: CountMap = { ...a[countMapKey] }; | ||
bs.forEach((b) => { | ||
Object.entries(b).forEach(([key, value]) => { | ||
Object.entries(b[countMapKey]).forEach(([key, value]) => { | ||
result[key] = (result[key] || 0) + value; | ||
@@ -67,0 +62,0 @@ }); |
@@ -565,3 +565,3 @@ /* eslint-disable capitalized-comments */ | ||
export type Query< | ||
TFieldArguments extends ObjectValueWithVariables | ObjectExpression | ||
TFieldArguments extends ObjectValueWithVariables | ObjectExpression, | ||
> = { | ||
@@ -572,3 +572,3 @@ [objectTypeName: string]: Fragment<TFieldArguments>; | ||
export type Fragment< | ||
TFieldArguments extends ObjectValueWithVariables | ObjectExpression | ||
TFieldArguments extends ObjectValueWithVariables | ObjectExpression, | ||
> = { | ||
@@ -580,3 +580,3 @@ objectTypeName: string; | ||
export type Selection< | ||
TFieldArguments extends ObjectValueWithVariables | ObjectExpression | ||
TFieldArguments extends ObjectValueWithVariables | ObjectExpression, | ||
> = { | ||
@@ -769,2 +769,3 @@ [fieldName: string]: { | ||
vercelEdgeConfigItemKey?: string; | ||
cacheSize?: number; // Defaults to 250. Setting it to 0 disables caching. | ||
@@ -771,0 +772,0 @@ /** Hypertune internal use only */ |
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
687724
288
12808