mixpanel-browser 2.78.0 → 2.79.0
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/.claude/settings.local.json +6 -11
- package/.eslintrc.json +12 -0
- package/.github/workflows/openfeature-provider-tests.yml +31 -0
- package/CHANGELOG.md +8 -1
- package/build.sh +2 -2
- package/dist/async-modules/{mixpanel-recorder-BjSlYaNJ.min.js → mixpanel-recorder-D5HJyV2E.min.js} +2 -2
- package/dist/async-modules/mixpanel-recorder-D5HJyV2E.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-recorder-zMBXIyeG.js → mixpanel-recorder-P6SEnnPV.js} +57 -33
- package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-targeting-UHf4eBfC.js → mixpanel-targeting-BBMVbgJF.js} +24 -13
- package/dist/mixpanel-core.cjs.d.ts +45 -1
- package/dist/mixpanel-core.cjs.js +565 -197
- package/dist/mixpanel-recorder.js +57 -33
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +24 -13
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +45 -1
- package/dist/mixpanel-with-async-modules.cjs.js +567 -199
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +45 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +567 -199
- package/dist/mixpanel-with-recorder.d.ts +45 -1
- package/dist/mixpanel-with-recorder.js +490 -122
- package/dist/mixpanel-with-recorder.min.d.ts +45 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +45 -1
- package/dist/mixpanel.amd.js +490 -122
- package/dist/mixpanel.cjs.d.ts +45 -1
- package/dist/mixpanel.cjs.js +490 -122
- package/dist/mixpanel.globals.js +567 -199
- package/dist/mixpanel.min.js +199 -189
- package/dist/mixpanel.module.d.ts +45 -1
- package/dist/mixpanel.module.js +490 -122
- package/dist/mixpanel.umd.d.ts +45 -1
- package/dist/mixpanel.umd.js +490 -122
- package/package.json +1 -1
- package/packages/openfeature-web-provider/README.md +357 -0
- package/packages/openfeature-web-provider/package-lock.json +1636 -0
- package/packages/openfeature-web-provider/package.json +51 -0
- package/packages/openfeature-web-provider/rollup.config.browser.mjs +26 -0
- package/packages/openfeature-web-provider/src/MixpanelProvider.ts +302 -0
- package/packages/openfeature-web-provider/src/index.ts +1 -0
- package/packages/openfeature-web-provider/src/types.ts +72 -0
- package/packages/openfeature-web-provider/test/MixpanelProvider.spec.ts +484 -0
- package/packages/openfeature-web-provider/tsconfig.json +15 -0
- package/src/autocapture/index.js +7 -2
- package/src/config.js +1 -1
- package/src/flags/flags-persistence.js +176 -0
- package/src/flags/index.js +174 -23
- package/src/index.d.ts +45 -1
- package/src/mixpanel-core.js +24 -7
- package/src/recorder/idb-config.js +16 -0
- package/src/recorder/recording-registry.js +7 -2
- package/src/recorder/session-recording.js +9 -4
- package/src/recorder-manager.js +7 -2
- package/src/request-queue.js +1 -2
- package/src/shared-lock.js +2 -3
- package/src/storage/indexed-db.js +16 -15
- package/src/storage/local-storage.js +5 -3
- package/src/utils.js +25 -12
- package/tsconfig.base.json +9 -0
- package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +0 -1
- package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js +0 -2
- package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js.map +0 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mixpanel/openfeature-web-provider",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenFeature Web Provider for Mixpanel feature flags",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"build:browser": "npx rollup -c rollup.config.browser.mjs",
|
|
13
|
+
"build:all": "npm run build && npm run build:browser",
|
|
14
|
+
"test": "mocha --require ts-node/register test/**/*.spec.ts",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@openfeature/web-sdk": "^1.0.0",
|
|
19
|
+
"mixpanel-browser": "^2.78.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@openfeature/web-sdk": "1.7.2",
|
|
23
|
+
"@types/chai": "4.3.20",
|
|
24
|
+
"@types/mocha": "10.0.10",
|
|
25
|
+
"@types/node": "20.19.33",
|
|
26
|
+
"@types/sinon": "17.0.4",
|
|
27
|
+
"@types/sinon-chai": "3.2.12",
|
|
28
|
+
"chai": "4.5.0",
|
|
29
|
+
"mixpanel-browser": "^2.78.0",
|
|
30
|
+
"mocha": "10.8.2",
|
|
31
|
+
"sinon": "17.0.1",
|
|
32
|
+
"sinon-chai": "3.7.0",
|
|
33
|
+
"ts-node": "10.9.2",
|
|
34
|
+
"typescript": "5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/mixpanel/mixpanel-js.git",
|
|
39
|
+
"directory": "packages/openfeature-web-provider"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"license": "Apache-2.0",
|
|
45
|
+
"keywords": [
|
|
46
|
+
"openfeature",
|
|
47
|
+
"mixpanel",
|
|
48
|
+
"feature-flags",
|
|
49
|
+
"provider"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
3
|
+
import esbuild from 'rollup-plugin-esbuild';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
input: 'src/index.ts',
|
|
7
|
+
output: {
|
|
8
|
+
file: 'dist/browser.js',
|
|
9
|
+
format: 'iife',
|
|
10
|
+
name: 'MixpanelOpenFeatureProvider',
|
|
11
|
+
globals: {
|
|
12
|
+
'@openfeature/web-sdk': 'OpenFeature'
|
|
13
|
+
},
|
|
14
|
+
sourcemap: true
|
|
15
|
+
},
|
|
16
|
+
external: ['@openfeature/web-sdk'],
|
|
17
|
+
plugins: [
|
|
18
|
+
resolve({
|
|
19
|
+
extensions: ['.ts', '.js']
|
|
20
|
+
}),
|
|
21
|
+
commonjs(),
|
|
22
|
+
esbuild({
|
|
23
|
+
target: 'es2018'
|
|
24
|
+
})
|
|
25
|
+
]
|
|
26
|
+
};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EvaluationContext,
|
|
3
|
+
Provider,
|
|
4
|
+
ResolutionDetails,
|
|
5
|
+
Logger,
|
|
6
|
+
ProviderMetadata,
|
|
7
|
+
JsonValue,
|
|
8
|
+
} from '@openfeature/web-sdk';
|
|
9
|
+
import { ErrorCode } from '@openfeature/web-sdk';
|
|
10
|
+
import mixpanel from 'mixpanel-browser';
|
|
11
|
+
import type { Config, Mixpanel, FlagsVariant } from 'mixpanel-browser';
|
|
12
|
+
import {
|
|
13
|
+
FlagsManager,
|
|
14
|
+
isBoolean,
|
|
15
|
+
isString,
|
|
16
|
+
isNumber,
|
|
17
|
+
createResolutionDetails,
|
|
18
|
+
createErrorResolutionDetails,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
let _instanceCount = 0;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* OpenFeature Web Provider for Mixpanel feature flags.
|
|
25
|
+
*
|
|
26
|
+
* This provider wraps the Mixpanel SDK's feature flags, allowing users
|
|
27
|
+
* to use the standardized OpenFeature API with Mixpanel as the backend.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import mixpanel from 'mixpanel-browser';
|
|
32
|
+
* import { OpenFeature } from '@openfeature/web-sdk';
|
|
33
|
+
* import { MixpanelProvider } from '@mixpanel/openfeature-web-provider';
|
|
34
|
+
*
|
|
35
|
+
* // Initialize Mixpanel with flags and context
|
|
36
|
+
* mixpanel.init('TOKEN', {
|
|
37
|
+
* flags: {
|
|
38
|
+
* context: { plan: 'premium' }
|
|
39
|
+
* }
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // Register provider with flags manager
|
|
43
|
+
* await OpenFeature.setProviderAndWait(new MixpanelProvider(mixpanel.flags));
|
|
44
|
+
*
|
|
45
|
+
* // Use flags
|
|
46
|
+
* const client = OpenFeature.getClient();
|
|
47
|
+
* const showNewUI = client.getBooleanValue('new-ui', false);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export class MixpanelProvider implements Provider {
|
|
51
|
+
readonly metadata: ProviderMetadata = {
|
|
52
|
+
name: 'mixpanel-provider',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
readonly runsOn = 'client' as const;
|
|
56
|
+
|
|
57
|
+
private readonly flags: FlagsManager;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The underlying Mixpanel instance, set when using the static `create` method.
|
|
61
|
+
* Users need this to call `identify()` and `track()` on the underlying instance.
|
|
62
|
+
*/
|
|
63
|
+
mixpanel?: Mixpanel;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a MixpanelProvider by initializing a new Mixpanel instance internally.
|
|
67
|
+
*
|
|
68
|
+
* The created Mixpanel instance is accessible via the `mixpanel` property
|
|
69
|
+
* for calling `identify()`, `track()`, and other Mixpanel methods.
|
|
70
|
+
*
|
|
71
|
+
* @param token - Your Mixpanel project token
|
|
72
|
+
* @param config - Optional Mixpanel configuration options
|
|
73
|
+
* @returns A MixpanelProvider with an initialized Mixpanel instance
|
|
74
|
+
*/
|
|
75
|
+
static create(token: string, config?: Partial<Config>): MixpanelProvider {
|
|
76
|
+
const instanceName = `openfeature_${_instanceCount++}`;
|
|
77
|
+
const instance = mixpanel.init(token, config || {}, instanceName);
|
|
78
|
+
const flagsManager = (instance as any).flags as FlagsManager;
|
|
79
|
+
const provider = new MixpanelProvider(flagsManager);
|
|
80
|
+
provider.mixpanel = instance;
|
|
81
|
+
return provider;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates a new MixpanelProvider instance.
|
|
86
|
+
*
|
|
87
|
+
* @param flagsManager - The Mixpanel FlagsManager instance
|
|
88
|
+
*/
|
|
89
|
+
constructor(flagsManager: FlagsManager) {
|
|
90
|
+
if (!flagsManager) {
|
|
91
|
+
throw new Error('FlagsManager is required');
|
|
92
|
+
}
|
|
93
|
+
// Validate required methods
|
|
94
|
+
if (typeof flagsManager.are_flags_ready !== 'function' ||
|
|
95
|
+
typeof flagsManager.get_variant_sync !== 'function' ||
|
|
96
|
+
typeof flagsManager.update_context !== 'function' ||
|
|
97
|
+
typeof flagsManager.when_ready !== 'function') {
|
|
98
|
+
throw new Error('Invalid FlagsManager: missing required methods');
|
|
99
|
+
}
|
|
100
|
+
this.flags = flagsManager;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initialize the provider by waiting for Mixpanel's flags to be fetched.
|
|
105
|
+
*/
|
|
106
|
+
async initialize(context?: EvaluationContext): Promise<void> {
|
|
107
|
+
// If context is provided, update Mixpanel's flag context
|
|
108
|
+
if (context && Object.keys(context).length > 0) {
|
|
109
|
+
await this.flags.update_context(context, { replace: true });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Wait for the initial fetch to complete
|
|
113
|
+
await this.flags.when_ready();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Handle context changes by updating Mixpanel's flag context.
|
|
118
|
+
*/
|
|
119
|
+
async onContextChange(
|
|
120
|
+
oldContext: EvaluationContext,
|
|
121
|
+
newContext: EvaluationContext
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
// Pass the new context directly to Mixpanel (replace mode)
|
|
124
|
+
await this.flags.update_context(newContext, { replace: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clean up when the provider is closed.
|
|
129
|
+
*/
|
|
130
|
+
async onClose(): Promise<void> {
|
|
131
|
+
// No cleanup needed - Mixpanel SDK manages its own lifecycle
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve a boolean flag value.
|
|
136
|
+
*/
|
|
137
|
+
resolveBooleanEvaluation(
|
|
138
|
+
flagKey: string,
|
|
139
|
+
defaultValue: boolean,
|
|
140
|
+
_context: EvaluationContext,
|
|
141
|
+
_logger: Logger
|
|
142
|
+
): ResolutionDetails<boolean> {
|
|
143
|
+
try {
|
|
144
|
+
const result = this.resolveFlag(flagKey, defaultValue);
|
|
145
|
+
if (result.errorCode) {
|
|
146
|
+
return result as ResolutionDetails<boolean>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const value = result.value;
|
|
150
|
+
if (!isBoolean(value)) {
|
|
151
|
+
return createErrorResolutionDetails(
|
|
152
|
+
defaultValue,
|
|
153
|
+
ErrorCode.TYPE_MISMATCH,
|
|
154
|
+
`Flag "${flagKey}" value is not a boolean: ${typeof value}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return createResolutionDetails(value, result.variant);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return createErrorResolutionDetails(
|
|
161
|
+
defaultValue,
|
|
162
|
+
ErrorCode.GENERAL,
|
|
163
|
+
e instanceof Error ? e.message : String(e)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Resolve a string flag value.
|
|
170
|
+
*/
|
|
171
|
+
resolveStringEvaluation(
|
|
172
|
+
flagKey: string,
|
|
173
|
+
defaultValue: string,
|
|
174
|
+
_context: EvaluationContext,
|
|
175
|
+
_logger: Logger
|
|
176
|
+
): ResolutionDetails<string> {
|
|
177
|
+
try {
|
|
178
|
+
const result = this.resolveFlag(flagKey, defaultValue);
|
|
179
|
+
if (result.errorCode) {
|
|
180
|
+
return result as ResolutionDetails<string>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const value = result.value;
|
|
184
|
+
if (!isString(value)) {
|
|
185
|
+
return createErrorResolutionDetails(
|
|
186
|
+
defaultValue,
|
|
187
|
+
ErrorCode.TYPE_MISMATCH,
|
|
188
|
+
`Flag "${flagKey}" value is not a string: ${typeof value}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return createResolutionDetails(value, result.variant);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
return createErrorResolutionDetails(
|
|
195
|
+
defaultValue,
|
|
196
|
+
ErrorCode.GENERAL,
|
|
197
|
+
e instanceof Error ? e.message : String(e)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a number flag value.
|
|
204
|
+
*/
|
|
205
|
+
resolveNumberEvaluation(
|
|
206
|
+
flagKey: string,
|
|
207
|
+
defaultValue: number,
|
|
208
|
+
_context: EvaluationContext,
|
|
209
|
+
_logger: Logger
|
|
210
|
+
): ResolutionDetails<number> {
|
|
211
|
+
try {
|
|
212
|
+
const result = this.resolveFlag(flagKey, defaultValue);
|
|
213
|
+
if (result.errorCode) {
|
|
214
|
+
return result as ResolutionDetails<number>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const value = result.value;
|
|
218
|
+
if (!isNumber(value)) {
|
|
219
|
+
return createErrorResolutionDetails(
|
|
220
|
+
defaultValue,
|
|
221
|
+
ErrorCode.TYPE_MISMATCH,
|
|
222
|
+
`Flag "${flagKey}" value is not a number: ${typeof value}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return createResolutionDetails(value, result.variant);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
return createErrorResolutionDetails(
|
|
229
|
+
defaultValue,
|
|
230
|
+
ErrorCode.GENERAL,
|
|
231
|
+
e instanceof Error ? e.message : String(e)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Resolve an object flag value.
|
|
238
|
+
*/
|
|
239
|
+
resolveObjectEvaluation<T extends JsonValue>(
|
|
240
|
+
flagKey: string,
|
|
241
|
+
defaultValue: T,
|
|
242
|
+
_context: EvaluationContext,
|
|
243
|
+
_logger: Logger
|
|
244
|
+
): ResolutionDetails<T> {
|
|
245
|
+
try {
|
|
246
|
+
const result = this.resolveFlag(flagKey, defaultValue);
|
|
247
|
+
if (result.errorCode) {
|
|
248
|
+
return result as ResolutionDetails<T>;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return createResolutionDetails(result.value as T, result.variant);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
return createErrorResolutionDetails(
|
|
254
|
+
defaultValue,
|
|
255
|
+
ErrorCode.GENERAL,
|
|
256
|
+
e instanceof Error ? e.message : String(e)
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Internal method to resolve a flag value from Mixpanel.
|
|
263
|
+
*/
|
|
264
|
+
private resolveFlag<T>(
|
|
265
|
+
flagKey: string,
|
|
266
|
+
defaultValue: T
|
|
267
|
+
): ResolutionDetails<any> {
|
|
268
|
+
// Check if flags are ready
|
|
269
|
+
if (!this.flags.are_flags_ready()) {
|
|
270
|
+
return createErrorResolutionDetails(
|
|
271
|
+
defaultValue,
|
|
272
|
+
ErrorCode.PROVIDER_NOT_READY,
|
|
273
|
+
'Mixpanel flags have not been loaded yet'
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Create a fallback variant to detect if flag wasn't found
|
|
278
|
+
const fallbackVariant: FlagsVariant = {
|
|
279
|
+
key: flagKey,
|
|
280
|
+
value: defaultValue,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Use get_variant_sync which triggers exposure tracking
|
|
284
|
+
const variant = this.flags.get_variant_sync(flagKey, fallbackVariant);
|
|
285
|
+
|
|
286
|
+
// Check if we got our fallback back (flag not found)
|
|
287
|
+
if (variant === fallbackVariant) {
|
|
288
|
+
return {
|
|
289
|
+
value: defaultValue,
|
|
290
|
+
errorCode: ErrorCode.FLAG_NOT_FOUND,
|
|
291
|
+
errorMessage: `Flag "${flagKey}" not found`,
|
|
292
|
+
reason: 'DEFAULT',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
value: variant.value,
|
|
298
|
+
variant: variant.key,
|
|
299
|
+
reason: 'TARGETING_MATCH',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MixpanelProvider } from './MixpanelProvider';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResolutionDetails,
|
|
3
|
+
ErrorCode,
|
|
4
|
+
} from '@openfeature/web-sdk';
|
|
5
|
+
import type { FlagsManager as MixpanelFlagsManager } from 'mixpanel-browser';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extended FlagsManager interface that includes when_ready().
|
|
9
|
+
* TODO: Remove this once mixpanel-browser exports when_ready() in its type definitions,
|
|
10
|
+
* and re-export FlagsManager directly from mixpanel-browser.
|
|
11
|
+
*/
|
|
12
|
+
export interface FlagsManager extends MixpanelFlagsManager {
|
|
13
|
+
when_ready(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type guard to check if a value is a boolean
|
|
18
|
+
*/
|
|
19
|
+
export function isBoolean(value: unknown): value is boolean {
|
|
20
|
+
return typeof value === 'boolean';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Type guard to check if a value is a string
|
|
25
|
+
*/
|
|
26
|
+
export function isString(value: unknown): value is string {
|
|
27
|
+
return typeof value === 'string';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type guard to check if a value is a number
|
|
32
|
+
*/
|
|
33
|
+
export function isNumber(value: unknown): value is number {
|
|
34
|
+
return typeof value === 'number';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Type guard to check if a value is an object (non-null)
|
|
39
|
+
*/
|
|
40
|
+
export function isObject(value: unknown): value is Record<string, unknown> {
|
|
41
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Helper to create a successful ResolutionDetails
|
|
46
|
+
*/
|
|
47
|
+
export function createResolutionDetails<T>(
|
|
48
|
+
value: T,
|
|
49
|
+
variant?: string
|
|
50
|
+
): ResolutionDetails<T> {
|
|
51
|
+
return {
|
|
52
|
+
value,
|
|
53
|
+
variant,
|
|
54
|
+
reason: 'TARGETING_MATCH',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Helper to create an error ResolutionDetails
|
|
60
|
+
*/
|
|
61
|
+
export function createErrorResolutionDetails<T>(
|
|
62
|
+
defaultValue: T,
|
|
63
|
+
errorCode: ErrorCode,
|
|
64
|
+
errorMessage?: string
|
|
65
|
+
): ResolutionDetails<T> {
|
|
66
|
+
return {
|
|
67
|
+
value: defaultValue,
|
|
68
|
+
errorCode,
|
|
69
|
+
errorMessage,
|
|
70
|
+
reason: 'ERROR',
|
|
71
|
+
};
|
|
72
|
+
}
|