@statsig/js-client 0.0.1-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +34 -0
- package/README.md +12 -0
- package/jest.config.ts +10 -0
- package/package.json +11 -0
- package/project.json +52 -0
- package/src/EvaluationData.ts +19 -0
- package/src/EvaluationResponseDeltas.ts +108 -0
- package/src/EvaluationStore.ts +99 -0
- package/src/Network.ts +112 -0
- package/src/StatsigClient.ts +251 -0
- package/src/StatsigEvaluationsDataAdapter.ts +36 -0
- package/src/StatsigMetadataAdditions.ts +7 -0
- package/src/StatsigOptions.ts +25 -0
- package/src/__tests__/CacheEviction.test.ts +55 -0
- package/src/__tests__/ClientErrrorBoundary.test.ts +29 -0
- package/src/__tests__/EvaluationCallbacks.test.ts +118 -0
- package/src/__tests__/EvaluationsDataAdapter.test.ts +131 -0
- package/src/__tests__/InitStrategyAwaited.test.ts +48 -0
- package/src/__tests__/InitStrategyBootstrap.test.ts +67 -0
- package/src/__tests__/InitStrategyDelayed.test.ts +65 -0
- package/src/__tests__/InitializeNetworkBadResponse.test.ts +77 -0
- package/src/__tests__/InitializeNetworkFailures.test.ts +91 -0
- package/src/__tests__/MockLocalStorage.ts +38 -0
- package/src/__tests__/PrecomputedEvaluationsClient.test.ts +27 -0
- package/src/__tests__/RacingUpdates.test.ts +108 -0
- package/src/__tests__/StatsigMetadata.test.ts +30 -0
- package/src/__tests__/initialize.json +117 -0
- package/src/index.ts +22 -0
- package/tsconfig.json +14 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +17 -0
- package/webpack.config.js +55 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../.eslintrc.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
7
|
+
"rules": {}
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"files": ["*.ts", "*.tsx"],
|
|
11
|
+
"rules": {}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"files": ["*.js", "*.jsx"],
|
|
15
|
+
"rules": {}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"files": ["*.json"],
|
|
19
|
+
"parser": "jsonc-eslint-parser",
|
|
20
|
+
"rules": {
|
|
21
|
+
"@nx/dependency-checks": [
|
|
22
|
+
"error",
|
|
23
|
+
{
|
|
24
|
+
"ignoredDependencies": [
|
|
25
|
+
"jest-fetch-mock",
|
|
26
|
+
"@nx/webpack",
|
|
27
|
+
"statsig-test-helpers"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Statsig - Precomputed Evaluations
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> This version of the SDK is still in beta. The non-beta version can be found [here](https://github.com/statsig-io/js-client).
|
|
5
|
+
|
|
6
|
+
The JavaScript SDK for single user client environments. If you need a SDK for another language or server environment, check out our [other SDKs](https://docs.statsig.com/#sdks).
|
|
7
|
+
|
|
8
|
+
Statsig helps you move faster with feature gates (feature flags), and/or dynamic configs. It also allows you to run A/B/n tests to validate your new features and understand their impact on your KPIs. If you're new to Statsig, check out our product and create an account at [statsig.com](https://www.statsig.com).
|
|
9
|
+
|
|
10
|
+
## Getting Started
|
|
11
|
+
|
|
12
|
+
Check out our [SDK docs](https://docs.statsig.com/client/javascript-sdk) to get started.
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
export default {
|
|
3
|
+
displayName: 'js-client',
|
|
4
|
+
preset: '../../jest.preset.js',
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
|
7
|
+
},
|
|
8
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
9
|
+
coverageDirectory: '../../coverage/packages/js-client',
|
|
10
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@statsig/js-client",
|
|
3
|
+
"version": "0.0.1-beta.10",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@statsig/client-core": "0.0.1-beta.10"
|
|
6
|
+
},
|
|
7
|
+
"jsdelivr": "./build/js-client.min.js",
|
|
8
|
+
"type": "commonjs",
|
|
9
|
+
"main": "./src/index.js",
|
|
10
|
+
"typings": "./src/index.d.ts"
|
|
11
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "js-client",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/js-client/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"targets": {
|
|
7
|
+
"build": {
|
|
8
|
+
"command": ":",
|
|
9
|
+
"dependsOn": ["^build", "build-webpack"]
|
|
10
|
+
},
|
|
11
|
+
"publish": {
|
|
12
|
+
"command": "ts-node ./tools/scripts/publish.ts js-client",
|
|
13
|
+
"dependsOn": ["build", "^build-webpack"]
|
|
14
|
+
},
|
|
15
|
+
"lint": {
|
|
16
|
+
"executor": "@nx/eslint:lint",
|
|
17
|
+
"outputs": ["{options.outputFile}"]
|
|
18
|
+
},
|
|
19
|
+
"test": {
|
|
20
|
+
"executor": "@nx/jest:jest",
|
|
21
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
22
|
+
"options": {
|
|
23
|
+
"jestConfig": "packages/js-client/jest.config.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"build-ts": {
|
|
27
|
+
"executor": "@nx/js:tsc",
|
|
28
|
+
"outputs": ["{options.outputPath}"],
|
|
29
|
+
"options": {
|
|
30
|
+
"outputPath": "dist/packages/js-client",
|
|
31
|
+
"main": "packages/js-client/src/index.ts",
|
|
32
|
+
"tsConfig": "packages/js-client/tsconfig.lib.json",
|
|
33
|
+
"assets": ["packages/js-client/*.md"]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"build-webpack": {
|
|
37
|
+
"executor": "@nx/webpack:webpack",
|
|
38
|
+
"outputs": ["{options.outputPath}"],
|
|
39
|
+
"defaultConfiguration": "production",
|
|
40
|
+
"options": {
|
|
41
|
+
"statsJson": true,
|
|
42
|
+
"verbose": true,
|
|
43
|
+
"outputPath": "overridden in webpack config",
|
|
44
|
+
"main": "overridden in webpack config",
|
|
45
|
+
"tsConfig": "packages/js-client/tsconfig.lib.json",
|
|
46
|
+
"webpackConfig": "packages/js-client/webpack.config.js"
|
|
47
|
+
},
|
|
48
|
+
"dependsOn": ["build-ts"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"tags": []
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DynamicConfigEvaluation,
|
|
3
|
+
GateEvaluation,
|
|
4
|
+
LayerEvaluation,
|
|
5
|
+
} from '@statsig/client-core';
|
|
6
|
+
|
|
7
|
+
export type EvaluationResponseWithUpdates = {
|
|
8
|
+
feature_gates: Record<string, GateEvaluation>;
|
|
9
|
+
dynamic_configs: Record<string, DynamicConfigEvaluation>;
|
|
10
|
+
layer_configs: Record<string, LayerEvaluation>;
|
|
11
|
+
time: number;
|
|
12
|
+
has_updates: true;
|
|
13
|
+
hash_used: 'none' | 'sha256' | 'djb2';
|
|
14
|
+
derived_fields?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type EvaluationResponse =
|
|
18
|
+
| EvaluationResponseWithUpdates
|
|
19
|
+
| { has_updates: false };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { DJB2Object, typedJsonParse } from '@statsig/client-core';
|
|
2
|
+
|
|
3
|
+
import { EvaluationResponseWithUpdates } from './EvaluationData';
|
|
4
|
+
|
|
5
|
+
type DeltasEvaluationResponse = EvaluationResponseWithUpdates & {
|
|
6
|
+
deleted_configs?: string[];
|
|
7
|
+
deleted_gates?: string[];
|
|
8
|
+
deleted_layers?: string[];
|
|
9
|
+
is_delta: true;
|
|
10
|
+
has_updates: true;
|
|
11
|
+
checksum: string;
|
|
12
|
+
deltas_full_response?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type DeltasFailureInfo = {
|
|
16
|
+
hadBadDeltaChecksum: boolean;
|
|
17
|
+
badChecksum?: string;
|
|
18
|
+
badMergedConfigs?: Record<string, unknown>;
|
|
19
|
+
badFullResponse?: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type DeltasResult = string | DeltasFailureInfo | null;
|
|
23
|
+
|
|
24
|
+
export function resolveDeltasResponse(
|
|
25
|
+
cache: EvaluationResponseWithUpdates,
|
|
26
|
+
deltasString: string,
|
|
27
|
+
): DeltasResult {
|
|
28
|
+
const deltas = typedJsonParse<DeltasEvaluationResponse>(
|
|
29
|
+
deltasString,
|
|
30
|
+
'checksum',
|
|
31
|
+
'Failed to parse DeltasEvaluationResponse',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (!deltas) {
|
|
35
|
+
return {
|
|
36
|
+
hadBadDeltaChecksum: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const merged = _mergeDeltasIntoCache(cache, deltas);
|
|
41
|
+
const resolved = _handleDeletedEntries(merged);
|
|
42
|
+
|
|
43
|
+
const actualChecksum = DJB2Object({
|
|
44
|
+
feature_gates: resolved.feature_gates,
|
|
45
|
+
dynamic_configs: resolved.dynamic_configs,
|
|
46
|
+
layer_configs: resolved.layer_configs,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const isMatch = actualChecksum === deltas.checksum;
|
|
50
|
+
if (!isMatch) {
|
|
51
|
+
return {
|
|
52
|
+
hadBadDeltaChecksum: true,
|
|
53
|
+
badChecksum: actualChecksum,
|
|
54
|
+
badMergedConfigs: resolved,
|
|
55
|
+
badFullResponse: deltas.deltas_full_response,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return JSON.stringify(resolved);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _mergeDeltasIntoCache(
|
|
63
|
+
cache: EvaluationResponseWithUpdates,
|
|
64
|
+
deltas: DeltasEvaluationResponse,
|
|
65
|
+
): DeltasEvaluationResponse {
|
|
66
|
+
return {
|
|
67
|
+
...cache,
|
|
68
|
+
...deltas,
|
|
69
|
+
feature_gates: {
|
|
70
|
+
...cache.feature_gates,
|
|
71
|
+
...deltas.feature_gates,
|
|
72
|
+
},
|
|
73
|
+
layer_configs: {
|
|
74
|
+
...cache.layer_configs,
|
|
75
|
+
...deltas.layer_configs,
|
|
76
|
+
},
|
|
77
|
+
dynamic_configs: {
|
|
78
|
+
...cache.dynamic_configs,
|
|
79
|
+
...deltas.dynamic_configs,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function _handleDeletedEntries(
|
|
85
|
+
deltas: DeltasEvaluationResponse,
|
|
86
|
+
): EvaluationResponseWithUpdates {
|
|
87
|
+
const result = deltas;
|
|
88
|
+
|
|
89
|
+
_deleteEntriesInRecord(deltas.deleted_gates, result.feature_gates);
|
|
90
|
+
delete result.deleted_gates;
|
|
91
|
+
|
|
92
|
+
_deleteEntriesInRecord(deltas.deleted_configs, result.dynamic_configs);
|
|
93
|
+
delete result.deleted_configs;
|
|
94
|
+
|
|
95
|
+
_deleteEntriesInRecord(deltas.deleted_layers, result.layer_configs);
|
|
96
|
+
delete result.deleted_layers;
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _deleteEntriesInRecord(
|
|
102
|
+
keys: string[] | undefined,
|
|
103
|
+
values: Record<string, unknown>,
|
|
104
|
+
) {
|
|
105
|
+
keys?.forEach((key) => {
|
|
106
|
+
delete values[key];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnyEvaluation,
|
|
3
|
+
DataAdapterResult,
|
|
4
|
+
DataSource,
|
|
5
|
+
DetailedEvaluation,
|
|
6
|
+
DynamicConfigEvaluation,
|
|
7
|
+
EvaluationDetails,
|
|
8
|
+
GateEvaluation,
|
|
9
|
+
LayerEvaluation,
|
|
10
|
+
typedJsonParse,
|
|
11
|
+
} from '@statsig/client-core';
|
|
12
|
+
|
|
13
|
+
import { EvaluationResponse } from './EvaluationData';
|
|
14
|
+
|
|
15
|
+
type EvaluationStoreValues = EvaluationResponse & { has_updates: true };
|
|
16
|
+
|
|
17
|
+
export default class EvaluationStore {
|
|
18
|
+
private _values: EvaluationStoreValues | null = null;
|
|
19
|
+
private _source: DataSource = 'Uninitialized';
|
|
20
|
+
private _lcut = 0;
|
|
21
|
+
private _receivedAt = 0;
|
|
22
|
+
|
|
23
|
+
constructor(private _sdkKey: string) {}
|
|
24
|
+
|
|
25
|
+
reset(): void {
|
|
26
|
+
this._values = null;
|
|
27
|
+
this._source = 'Loading';
|
|
28
|
+
this._lcut = 0;
|
|
29
|
+
this._receivedAt = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
finalize(): void {
|
|
33
|
+
if (this._values) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this._source = 'NoValues';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setValuesFromDataAdapter(result: DataAdapterResult | null): void {
|
|
41
|
+
if (!result) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const values = typedJsonParse<EvaluationResponse>(
|
|
46
|
+
result.data,
|
|
47
|
+
'has_updates',
|
|
48
|
+
'Failed to parse EvaluationResponse',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (values?.has_updates !== true) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._lcut = values.time;
|
|
56
|
+
this._receivedAt = result.receivedAt;
|
|
57
|
+
this._source = result.source;
|
|
58
|
+
this._values = values;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getGate(name: string): DetailedEvaluation<GateEvaluation> {
|
|
62
|
+
const evaluation = this._values?.feature_gates[name] ?? null;
|
|
63
|
+
return this._makeDetailedEvaluation(evaluation);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getConfig(name: string): DetailedEvaluation<DynamicConfigEvaluation> {
|
|
67
|
+
const evaluation = this._values?.dynamic_configs[name] ?? null;
|
|
68
|
+
return this._makeDetailedEvaluation(evaluation);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getLayer(name: string): DetailedEvaluation<LayerEvaluation> {
|
|
72
|
+
const evaluation = this._values?.layer_configs[name] ?? null;
|
|
73
|
+
return this._makeDetailedEvaluation(evaluation);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _makeDetailedEvaluation<T extends AnyEvaluation>(
|
|
77
|
+
evaluation: T | null,
|
|
78
|
+
): DetailedEvaluation<T> {
|
|
79
|
+
return {
|
|
80
|
+
evaluation,
|
|
81
|
+
details: this._getDetails(evaluation == null),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private _getDetails(isUnrecognized: boolean): EvaluationDetails {
|
|
86
|
+
if (this._source === 'Uninitialized' || this._source === 'NoValues') {
|
|
87
|
+
return { reason: this._source };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const subreason = isUnrecognized ? 'Unrecognized' : 'Recognized';
|
|
91
|
+
const reason = `${this._source}:${subreason}`;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
reason,
|
|
95
|
+
lcut: this._lcut,
|
|
96
|
+
receivedAt: this._receivedAt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/Network.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NetworkCore,
|
|
3
|
+
StatsigClientEmitEventFunc,
|
|
4
|
+
StatsigUser,
|
|
5
|
+
_getOverridableUrl,
|
|
6
|
+
typedJsonParse,
|
|
7
|
+
} from '@statsig/client-core';
|
|
8
|
+
|
|
9
|
+
import { EvaluationResponse } from './EvaluationData';
|
|
10
|
+
import { resolveDeltasResponse } from './EvaluationResponseDeltas';
|
|
11
|
+
import { StatsigOptions } from './StatsigOptions';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_API = 'https://api.statsig.com/v1';
|
|
14
|
+
const DEFAULT_ENDPOINT = '/initialize';
|
|
15
|
+
|
|
16
|
+
type EvaluationsFetchArgs = {
|
|
17
|
+
hash: 'djb2' | 'sha256' | 'none';
|
|
18
|
+
deltasResponseRequested: boolean;
|
|
19
|
+
user?: StatsigUser;
|
|
20
|
+
sinceTime?: number;
|
|
21
|
+
previousDerivedFields?: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default class StatsigNetwork extends NetworkCore {
|
|
25
|
+
private _initializeUrl: string;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
options: StatsigOptions | null,
|
|
29
|
+
emitter?: StatsigClientEmitEventFunc,
|
|
30
|
+
) {
|
|
31
|
+
super(options, emitter);
|
|
32
|
+
|
|
33
|
+
this._initializeUrl = _getOverridableUrl(
|
|
34
|
+
options?.initializeUrl,
|
|
35
|
+
options?.api,
|
|
36
|
+
DEFAULT_ENDPOINT,
|
|
37
|
+
DEFAULT_API,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async fetchEvaluations(
|
|
42
|
+
sdkKey: string,
|
|
43
|
+
current: string | null,
|
|
44
|
+
user?: StatsigUser,
|
|
45
|
+
): Promise<string | null> {
|
|
46
|
+
const cache = current
|
|
47
|
+
? typedJsonParse<EvaluationResponse>(
|
|
48
|
+
current,
|
|
49
|
+
'has_updates',
|
|
50
|
+
'Failed to parse cached EvaluationResponse',
|
|
51
|
+
)
|
|
52
|
+
: null;
|
|
53
|
+
|
|
54
|
+
let data: EvaluationsFetchArgs = {
|
|
55
|
+
user,
|
|
56
|
+
hash: 'djb2',
|
|
57
|
+
deltasResponseRequested: false,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (cache?.has_updates) {
|
|
61
|
+
data = {
|
|
62
|
+
...data,
|
|
63
|
+
sinceTime: cache.time,
|
|
64
|
+
previousDerivedFields:
|
|
65
|
+
'derived_fields' in cache ? cache.derived_fields : {},
|
|
66
|
+
deltasResponseRequested: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this._fetchEvaluations(sdkKey, cache, data);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async _fetchEvaluations(
|
|
74
|
+
sdkKey: string,
|
|
75
|
+
cache: EvaluationResponse | null,
|
|
76
|
+
data: EvaluationsFetchArgs,
|
|
77
|
+
): Promise<string | null> {
|
|
78
|
+
const response = await this.post({
|
|
79
|
+
sdkKey,
|
|
80
|
+
url: this._initializeUrl,
|
|
81
|
+
data,
|
|
82
|
+
retries: 2,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (response?.code === 204) {
|
|
86
|
+
return '{"has_updates": false}';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (response?.code !== 200) {
|
|
90
|
+
return response?.body ?? null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
cache?.has_updates !== true ||
|
|
95
|
+
response.body?.includes('"is_delta":true') !== true
|
|
96
|
+
) {
|
|
97
|
+
return response.body;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = resolveDeltasResponse(cache, response.body);
|
|
101
|
+
if (typeof result === 'string') {
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// retry without deltas
|
|
106
|
+
return this._fetchEvaluations(sdkKey, cache, {
|
|
107
|
+
...data,
|
|
108
|
+
...result,
|
|
109
|
+
deltasResponseRequested: false,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|