@databuddy/sdk 2.3.0 → 2.3.2
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/dist/ai/vercel/index.d.mts +146 -7
- package/dist/ai/vercel/index.d.ts +146 -7
- package/dist/ai/vercel/index.mjs +221 -34
- package/dist/core/index.d.mts +3 -43
- package/dist/core/index.d.ts +3 -43
- package/dist/core/index.mjs +1 -1
- package/dist/node/index.d.mts +121 -10
- package/dist/node/index.d.ts +121 -10
- package/dist/node/index.mjs +257 -11
- package/dist/react/index.d.mts +57 -17
- package/dist/react/index.d.ts +57 -17
- package/dist/react/index.mjs +177 -85
- package/dist/shared/@databuddy/sdk.3kaCzyfu.mjs +199 -0
- package/dist/shared/@databuddy/sdk.B6nwxnPC.d.mts +128 -0
- package/dist/shared/@databuddy/sdk.B6nwxnPC.d.ts +128 -0
- package/dist/shared/@databuddy/{sdk.CeYE_kaj.d.mts → sdk.C9b3SVYK.d.mts} +10 -10
- package/dist/shared/@databuddy/{sdk.CeYE_kaj.d.ts → sdk.C9b3SVYK.d.ts} +10 -10
- package/dist/shared/@databuddy/sdk.Du7SE7-M.mjs +479 -0
- package/dist/shared/@databuddy/sdk.F8Xt1uF7.mjs +35 -0
- package/dist/vue/index.d.mts +11 -2
- package/dist/vue/index.d.ts +11 -2
- package/dist/vue/index.mjs +19 -9
- package/package.json +3 -4
- package/dist/shared/@databuddy/sdk.Cn2tDA8H.mjs +0 -471
- package/dist/shared/@databuddy/sdk.OK9Nbqlf.d.mts +0 -64
- package/dist/shared/@databuddy/sdk.OK9Nbqlf.d.ts +0 -64
package/dist/react/index.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { D as DatabuddyConfig } from '../shared/@databuddy/sdk.
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import * as jotai_vanilla_internals from 'jotai/vanilla/internals';
|
|
5
|
-
import { d as FlagsConfig, c as FlagState } from '../shared/@databuddy/sdk.OK9Nbqlf.js';
|
|
6
|
-
export { b as FlagResult, e as FlagsContext } from '../shared/@databuddy/sdk.OK9Nbqlf.js';
|
|
1
|
+
import { D as DatabuddyConfig } from '../shared/@databuddy/sdk.C9b3SVYK.js';
|
|
2
|
+
import React, { ReactNode } from 'react';
|
|
3
|
+
import { F as FlagsConfig, a as FeatureState, b as FlagState, c as FlagsContext } from '../shared/@databuddy/sdk.B6nwxnPC.js';
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* React/Next.js component that injects the Databuddy tracking script.
|
|
@@ -56,18 +53,61 @@ export { b as FlagResult, e as FlagsContext } from '../shared/@databuddy/sdk.OK9
|
|
|
56
53
|
*/
|
|
57
54
|
declare function Databuddy(props: DatabuddyConfig): null;
|
|
58
55
|
|
|
56
|
+
/** biome-ignore-all lint/correctness/noUnusedImports: we need to import React to use the createContext function */
|
|
57
|
+
|
|
59
58
|
interface FlagsProviderProps extends FlagsConfig {
|
|
60
59
|
children: ReactNode;
|
|
61
60
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
declare function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Flags provider component
|
|
63
|
+
* Creates a manager instance and provides flag methods to children
|
|
64
|
+
*/
|
|
65
|
+
declare function FlagsProvider({ children, ...config }: FlagsProviderProps): React.JSX.Element;
|
|
66
|
+
/**
|
|
67
|
+
* Access the full flags context
|
|
68
|
+
* @example
|
|
69
|
+
* const { isOn, getFlag, refresh } = useFlags();
|
|
70
|
+
*/
|
|
71
|
+
declare function useFlags(): FlagsContext;
|
|
72
|
+
/**
|
|
73
|
+
* Get a flag's full state with loading/error handling
|
|
74
|
+
* @example
|
|
75
|
+
* const flag = useFlag("my-feature");
|
|
76
|
+
* if (flag.loading) return <Skeleton />;
|
|
77
|
+
* return flag.on ? <NewFeature /> : <OldFeature />;
|
|
78
|
+
*/
|
|
79
|
+
declare function useFlag(key: string): FlagState;
|
|
80
|
+
/**
|
|
81
|
+
* Simple feature check - returns { on, loading, value, variant }
|
|
82
|
+
* @example
|
|
83
|
+
* const { on, loading } = useFeature("dark-mode");
|
|
84
|
+
* if (loading) return <Skeleton />;
|
|
85
|
+
* return on ? <DarkTheme /> : <LightTheme />;
|
|
86
|
+
*/
|
|
87
|
+
declare function useFeature(key: string): FeatureState;
|
|
88
|
+
/**
|
|
89
|
+
* Boolean-only feature check with default value
|
|
90
|
+
* Useful for SSR-safe rendering where you need a boolean immediately
|
|
91
|
+
* @example
|
|
92
|
+
* const isDarkMode = useFeatureOn("dark-mode", false);
|
|
93
|
+
* return isDarkMode ? <DarkTheme /> : <LightTheme />;
|
|
94
|
+
*/
|
|
95
|
+
declare function useFeatureOn(key: string, defaultValue?: boolean): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Get a flag's typed value
|
|
98
|
+
* @example
|
|
99
|
+
* const maxItems = useFlagValue("max-items", 10);
|
|
100
|
+
* const theme = useFlagValue<"light" | "dark">("theme", "light");
|
|
101
|
+
*/
|
|
102
|
+
declare function useFlagValue<T extends boolean | string | number = boolean>(key: string, defaultValue?: T): T;
|
|
103
|
+
/**
|
|
104
|
+
* Get variant for A/B testing
|
|
105
|
+
* @example
|
|
106
|
+
* const variant = useVariant("checkout-experiment");
|
|
107
|
+
* if (variant === "control") return <OldCheckout />;
|
|
108
|
+
* if (variant === "treatment-a") return <NewCheckoutA />;
|
|
109
|
+
* return <NewCheckoutB />;
|
|
110
|
+
*/
|
|
111
|
+
declare function useVariant(key: string): string | undefined;
|
|
72
112
|
|
|
73
|
-
export { Databuddy,
|
|
113
|
+
export { Databuddy, FlagsProvider, useFeature, useFeatureOn, useFlag, useFlagValue, useFlags, useVariant };
|
package/dist/react/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { i as isScriptInjected, c as createScript
|
|
3
|
+
import { i as isScriptInjected, c as createScript } from '../shared/@databuddy/sdk.F8Xt1uF7.mjs';
|
|
4
4
|
import { d as detectClientId } from '../shared/@databuddy/sdk.BUsPV0LH.mjs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import React, { useRef, useMemo, useEffect, useSyncExternalStore, createContext, useContext } from 'react';
|
|
6
|
+
import { B as BrowserFlagStorage, C as CoreFlagsManager } from '../shared/@databuddy/sdk.Du7SE7-M.mjs';
|
|
7
|
+
import { l as logger } from '../shared/@databuddy/sdk.3kaCzyfu.mjs';
|
|
7
8
|
|
|
8
9
|
function Databuddy(props) {
|
|
9
10
|
const clientId = detectClientId(props.clientId);
|
|
@@ -22,101 +23,192 @@ function Databuddy(props) {
|
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const FlagsReactContext = createContext(null);
|
|
27
|
+
function createFlagState(result, isLoading, isPending) {
|
|
28
|
+
if (isPending) {
|
|
29
|
+
return {
|
|
30
|
+
on: false,
|
|
31
|
+
enabled: false,
|
|
32
|
+
status: "pending",
|
|
33
|
+
loading: true,
|
|
34
|
+
isLoading: true,
|
|
35
|
+
isReady: false
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (isLoading || !result) {
|
|
39
|
+
return {
|
|
40
|
+
on: false,
|
|
41
|
+
enabled: false,
|
|
42
|
+
status: "loading",
|
|
43
|
+
loading: true,
|
|
44
|
+
isLoading: true,
|
|
45
|
+
isReady: false
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const status = result.reason === "ERROR" ? "error" : "ready";
|
|
49
|
+
return {
|
|
50
|
+
on: result.enabled,
|
|
51
|
+
enabled: result.enabled,
|
|
52
|
+
status,
|
|
53
|
+
loading: false,
|
|
54
|
+
isLoading: false,
|
|
55
|
+
isReady: true,
|
|
56
|
+
value: result.value,
|
|
57
|
+
variant: result.variant
|
|
58
|
+
};
|
|
59
|
+
}
|
|
28
60
|
function FlagsProvider({ children, ...config }) {
|
|
29
|
-
const
|
|
30
|
-
|
|
61
|
+
const storeRef = useRef({ flags: {}, isReady: false });
|
|
62
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
63
|
+
const manager = useMemo(() => {
|
|
31
64
|
const storage = config.skipStorage ? void 0 : new BrowserFlagStorage();
|
|
32
|
-
|
|
65
|
+
return new CoreFlagsManager({
|
|
33
66
|
config,
|
|
34
67
|
storage,
|
|
35
68
|
onFlagsUpdate: (flags) => {
|
|
36
|
-
|
|
69
|
+
storeRef.current = { ...storeRef.current, flags };
|
|
70
|
+
for (const listener of listenersRef.current) {
|
|
71
|
+
listener();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
onReady: () => {
|
|
75
|
+
storeRef.current = { ...storeRef.current, isReady: true };
|
|
76
|
+
for (const listener of listenersRef.current) {
|
|
77
|
+
listener();
|
|
78
|
+
}
|
|
37
79
|
}
|
|
38
80
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return () => {
|
|
42
|
-
managerRef.current = null;
|
|
43
|
-
flagsStore.set(managerAtom, null);
|
|
44
|
-
};
|
|
45
|
-
}, [
|
|
46
|
-
config.clientId,
|
|
47
|
-
config.apiUrl,
|
|
48
|
-
config.user?.userId,
|
|
49
|
-
config.user?.email,
|
|
50
|
-
config.disabled,
|
|
51
|
-
config.debug,
|
|
52
|
-
config.skipStorage,
|
|
53
|
-
config.isPending,
|
|
54
|
-
config.autoFetch
|
|
55
|
-
]);
|
|
81
|
+
}, [config.clientId]);
|
|
82
|
+
const prevConfigRef = useRef(config);
|
|
56
83
|
useEffect(() => {
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
const prevConfig = prevConfigRef.current;
|
|
85
|
+
const configChanged = prevConfig.apiUrl !== config.apiUrl || prevConfig.isPending !== config.isPending || prevConfig.user?.userId !== config.user?.userId || prevConfig.user?.email !== config.user?.email || prevConfig.environment !== config.environment || prevConfig.disabled !== config.disabled || prevConfig.autoFetch !== config.autoFetch || prevConfig.cacheTtl !== config.cacheTtl || prevConfig.staleTime !== config.staleTime;
|
|
86
|
+
if (configChanged) {
|
|
87
|
+
prevConfigRef.current = config;
|
|
88
|
+
manager.updateConfig(config);
|
|
59
89
|
}
|
|
60
|
-
}, [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
}, [manager, config]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
return () => {
|
|
93
|
+
manager.destroy();
|
|
94
|
+
};
|
|
95
|
+
}, [manager]);
|
|
96
|
+
const subscribe = useMemo(
|
|
97
|
+
() => (callback) => {
|
|
98
|
+
listenersRef.current.add(callback);
|
|
99
|
+
return () => {
|
|
100
|
+
listenersRef.current.delete(callback);
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
[]
|
|
104
|
+
);
|
|
105
|
+
const getSnapshot = useMemo(() => () => storeRef.current, []);
|
|
106
|
+
const store = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
107
|
+
const contextValue = useMemo(
|
|
108
|
+
() => ({
|
|
109
|
+
// Cleaner API: getFlag returns FlagState
|
|
110
|
+
getFlag: (key) => {
|
|
111
|
+
const result = store.flags[key];
|
|
112
|
+
const managerState = manager.isEnabled(key);
|
|
113
|
+
return createFlagState(
|
|
114
|
+
result,
|
|
115
|
+
managerState.isLoading,
|
|
116
|
+
config.isPending ?? false
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
// Simple boolean check
|
|
120
|
+
isOn: (key) => {
|
|
121
|
+
const result = store.flags[key];
|
|
122
|
+
if (result) {
|
|
123
|
+
return result.enabled;
|
|
124
|
+
}
|
|
125
|
+
const state = manager.isEnabled(key);
|
|
126
|
+
return state.enabled;
|
|
127
|
+
},
|
|
128
|
+
// Get typed value
|
|
129
|
+
getValue: (key, defaultValue) => {
|
|
130
|
+
const result = store.flags[key];
|
|
131
|
+
if (result) {
|
|
132
|
+
return result.value;
|
|
133
|
+
}
|
|
134
|
+
return manager.getValue(key, defaultValue);
|
|
135
|
+
},
|
|
136
|
+
// Async fetch
|
|
137
|
+
fetchFlag: (key) => manager.getFlag(key),
|
|
138
|
+
fetchAllFlags: () => manager.fetchAllFlags(),
|
|
139
|
+
updateUser: (user) => manager.updateUser(user),
|
|
140
|
+
refresh: (forceClear = false) => manager.refresh(forceClear),
|
|
141
|
+
isReady: store.isReady,
|
|
142
|
+
// Deprecated: kept for backwards compatibility
|
|
143
|
+
isEnabled: (key) => {
|
|
144
|
+
const result = store.flags[key];
|
|
145
|
+
const managerState = manager.isEnabled(key);
|
|
146
|
+
return createFlagState(
|
|
147
|
+
result,
|
|
148
|
+
managerState.isLoading,
|
|
149
|
+
config.isPending ?? false
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}),
|
|
153
|
+
[manager, store, config.isPending]
|
|
154
|
+
);
|
|
155
|
+
return /* @__PURE__ */ React.createElement(FlagsReactContext.Provider, { value: contextValue }, children);
|
|
72
156
|
}
|
|
73
157
|
function useFlags() {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const isEnabled = (key) => {
|
|
84
|
-
if (!manager) {
|
|
85
|
-
return {
|
|
158
|
+
const context = useContext(FlagsReactContext);
|
|
159
|
+
if (!context) {
|
|
160
|
+
logger.warn("useFlags called outside FlagsProvider");
|
|
161
|
+
return {
|
|
162
|
+
isEnabled: () => createFlagState(void 0, false, false),
|
|
163
|
+
getFlag: () => createFlagState(void 0, false, false),
|
|
164
|
+
isOn: () => false,
|
|
165
|
+
getValue: (_key, defaultValue) => defaultValue ?? false,
|
|
166
|
+
fetchFlag: async () => ({
|
|
86
167
|
enabled: false,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
if (!manager) {
|
|
109
|
-
logger.warn("No manager for refresh");
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
manager.refresh(forceClear);
|
|
113
|
-
};
|
|
168
|
+
value: false,
|
|
169
|
+
payload: null,
|
|
170
|
+
reason: "NO_PROVIDER"
|
|
171
|
+
}),
|
|
172
|
+
fetchAllFlags: async () => {
|
|
173
|
+
},
|
|
174
|
+
updateUser: () => {
|
|
175
|
+
},
|
|
176
|
+
refresh: async () => {
|
|
177
|
+
},
|
|
178
|
+
isReady: false
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return context;
|
|
182
|
+
}
|
|
183
|
+
function useFlag(key) {
|
|
184
|
+
const { getFlag } = useFlags();
|
|
185
|
+
return getFlag(key);
|
|
186
|
+
}
|
|
187
|
+
function useFeature(key) {
|
|
188
|
+
const flag = useFlag(key);
|
|
114
189
|
return {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
190
|
+
on: flag.on,
|
|
191
|
+
loading: flag.loading,
|
|
192
|
+
status: flag.status,
|
|
193
|
+
value: flag.value,
|
|
194
|
+
variant: flag.variant
|
|
119
195
|
};
|
|
120
196
|
}
|
|
197
|
+
function useFeatureOn(key, defaultValue = false) {
|
|
198
|
+
const { isOn, isReady } = useFlags();
|
|
199
|
+
const flag = useFlag(key);
|
|
200
|
+
if (flag.loading || !isReady) {
|
|
201
|
+
return defaultValue;
|
|
202
|
+
}
|
|
203
|
+
return isOn(key);
|
|
204
|
+
}
|
|
205
|
+
function useFlagValue(key, defaultValue) {
|
|
206
|
+
const { getValue } = useFlags();
|
|
207
|
+
return getValue(key, defaultValue);
|
|
208
|
+
}
|
|
209
|
+
function useVariant(key) {
|
|
210
|
+
const flag = useFlag(key);
|
|
211
|
+
return flag.variant;
|
|
212
|
+
}
|
|
121
213
|
|
|
122
|
-
export { Databuddy, FlagsProvider, useFlags };
|
|
214
|
+
export { Databuddy, FlagsProvider, useFeature, useFeatureOn, useFlag, useFlagValue, useFlags, useVariant };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
class Logger {
|
|
2
|
+
debugEnabled = false;
|
|
3
|
+
/**
|
|
4
|
+
* Enable or disable debug logging
|
|
5
|
+
*/
|
|
6
|
+
setDebug(enabled) {
|
|
7
|
+
this.debugEnabled = enabled;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Log debug messages (only when debug is enabled)
|
|
11
|
+
*/
|
|
12
|
+
debug(...args) {
|
|
13
|
+
if (this.debugEnabled) {
|
|
14
|
+
console.log("[Databuddy]", ...args);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Log info messages (always enabled)
|
|
19
|
+
*/
|
|
20
|
+
info(...args) {
|
|
21
|
+
console.info("[Databuddy]", ...args);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Log warning messages (always enabled)
|
|
25
|
+
*/
|
|
26
|
+
warn(...args) {
|
|
27
|
+
console.warn("[Databuddy]", ...args);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Log error messages (always enabled)
|
|
31
|
+
*/
|
|
32
|
+
error(...args) {
|
|
33
|
+
console.error("[Databuddy]", ...args);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Log with table format (only when debug is enabled)
|
|
37
|
+
*/
|
|
38
|
+
table(data) {
|
|
39
|
+
if (this.debugEnabled) {
|
|
40
|
+
console.table(data);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Time a function execution (only when debug is enabled)
|
|
45
|
+
*/
|
|
46
|
+
time(label) {
|
|
47
|
+
if (this.debugEnabled) {
|
|
48
|
+
console.time(`[Databuddy] ${label}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* End timing a function execution (only when debug is enabled)
|
|
53
|
+
*/
|
|
54
|
+
timeEnd(label) {
|
|
55
|
+
if (this.debugEnabled) {
|
|
56
|
+
console.timeEnd(`[Databuddy] ${label}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Log JSON data (only when debug is enabled)
|
|
61
|
+
*/
|
|
62
|
+
json(data) {
|
|
63
|
+
if (this.debugEnabled) {
|
|
64
|
+
console.log("[Databuddy]", JSON.stringify(data, null, 2));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const logger = new Logger();
|
|
69
|
+
|
|
70
|
+
const DEFAULT_RESULT = {
|
|
71
|
+
enabled: false,
|
|
72
|
+
value: false,
|
|
73
|
+
payload: null,
|
|
74
|
+
reason: "DEFAULT"
|
|
75
|
+
};
|
|
76
|
+
function getCacheKey(key, user) {
|
|
77
|
+
if (!(user?.userId || user?.email)) {
|
|
78
|
+
return key;
|
|
79
|
+
}
|
|
80
|
+
return `${key}:${user.userId ?? ""}:${user.email ?? ""}`;
|
|
81
|
+
}
|
|
82
|
+
function buildQueryParams(config, user) {
|
|
83
|
+
const params = new URLSearchParams();
|
|
84
|
+
params.set("clientId", config.clientId);
|
|
85
|
+
const u = user ?? config.user;
|
|
86
|
+
if (u?.userId) {
|
|
87
|
+
params.set("userId", u.userId);
|
|
88
|
+
}
|
|
89
|
+
if (u?.email) {
|
|
90
|
+
params.set("email", u.email);
|
|
91
|
+
}
|
|
92
|
+
if (u?.properties) {
|
|
93
|
+
params.set("properties", JSON.stringify(u.properties));
|
|
94
|
+
}
|
|
95
|
+
if (config.environment) {
|
|
96
|
+
params.set("environment", config.environment);
|
|
97
|
+
}
|
|
98
|
+
return params;
|
|
99
|
+
}
|
|
100
|
+
async function fetchFlags(apiUrl, keys, params) {
|
|
101
|
+
const batchParams = new URLSearchParams(params);
|
|
102
|
+
batchParams.set("keys", keys.join(","));
|
|
103
|
+
const url = `${apiUrl}/public/v1/flags/bulk?${batchParams}`;
|
|
104
|
+
const response = await fetch(url);
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const result = {};
|
|
107
|
+
for (const key of keys) {
|
|
108
|
+
result[key] = { ...DEFAULT_RESULT, reason: "ERROR" };
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
const data = await response.json();
|
|
113
|
+
return data.flags ?? {};
|
|
114
|
+
}
|
|
115
|
+
async function fetchAllFlags(apiUrl, params) {
|
|
116
|
+
const url = `${apiUrl}/public/v1/flags/bulk?${params}`;
|
|
117
|
+
const response = await fetch(url);
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
return data.flags ?? {};
|
|
123
|
+
}
|
|
124
|
+
function isCacheValid(entry) {
|
|
125
|
+
if (!entry) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return Date.now() <= entry.expiresAt;
|
|
129
|
+
}
|
|
130
|
+
function isCacheStale(entry) {
|
|
131
|
+
return Date.now() > entry.staleAt;
|
|
132
|
+
}
|
|
133
|
+
function createCacheEntry(result, ttl, staleTime) {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
return {
|
|
136
|
+
result,
|
|
137
|
+
staleAt: now + (staleTime ?? ttl / 2),
|
|
138
|
+
expiresAt: now + ttl
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
class RequestBatcher {
|
|
142
|
+
pending = /* @__PURE__ */ new Map();
|
|
143
|
+
timer = null;
|
|
144
|
+
batchDelayMs;
|
|
145
|
+
apiUrl;
|
|
146
|
+
params;
|
|
147
|
+
constructor(apiUrl, params, batchDelayMs = 10) {
|
|
148
|
+
this.apiUrl = apiUrl;
|
|
149
|
+
this.params = params;
|
|
150
|
+
this.batchDelayMs = batchDelayMs;
|
|
151
|
+
}
|
|
152
|
+
request(key) {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const existing = this.pending.get(key);
|
|
155
|
+
if (existing) {
|
|
156
|
+
existing.push({ resolve, reject });
|
|
157
|
+
} else {
|
|
158
|
+
this.pending.set(key, [{ resolve, reject }]);
|
|
159
|
+
}
|
|
160
|
+
if (!this.timer) {
|
|
161
|
+
this.timer = setTimeout(() => this.flush(), this.batchDelayMs);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async flush() {
|
|
166
|
+
this.timer = null;
|
|
167
|
+
const keys = [...this.pending.keys()];
|
|
168
|
+
const callbacks = new Map(this.pending);
|
|
169
|
+
this.pending.clear();
|
|
170
|
+
if (keys.length === 0) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const results = await fetchFlags(this.apiUrl, keys, this.params);
|
|
175
|
+
for (const [key, cbs] of callbacks) {
|
|
176
|
+
const result = results[key] ?? { ...DEFAULT_RESULT, reason: "NOT_FOUND" };
|
|
177
|
+
for (const cb of cbs) {
|
|
178
|
+
cb.resolve(result);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const error = err instanceof Error ? err : new Error("Fetch failed");
|
|
183
|
+
for (const cbs of callbacks.values()) {
|
|
184
|
+
for (const cb of cbs) {
|
|
185
|
+
cb.reject(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
destroy() {
|
|
191
|
+
if (this.timer) {
|
|
192
|
+
clearTimeout(this.timer);
|
|
193
|
+
this.timer = null;
|
|
194
|
+
}
|
|
195
|
+
this.pending.clear();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { DEFAULT_RESULT as D, RequestBatcher as R, isCacheStale as a, buildQueryParams as b, createCacheEntry as c, fetchAllFlags as f, getCacheKey as g, isCacheValid as i, logger as l };
|