-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathSvelteLDClient.ts
More file actions
199 lines (173 loc) · 6.4 KB
/
SvelteLDClient.ts
File metadata and controls
199 lines (173 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import { derived, type Readable, readonly, writable, type Writable } from 'svelte/store';
import {
createClient,
type LDClient as LDClientBase,
type LDContext,
type LDFlagSet,
type LDFlagValue,
type LDIdentifyResult,
type LDOptions,
} from '@launchdarkly/js-client-sdk';
export type { LDContext, LDFlagValue };
/** Client ID for LaunchDarkly */
export type LDClientID = string;
/** Flags for LaunchDarkly */
export type LDFlags = LDFlagSet;
/**
* The LaunchDarkly client interface for Svelte which is a restrictive proxy of the {@link LDClientBase}.
*/
export interface LDClient {
/**
* Initializes the LaunchDarkly client.
* @param {LDClientID} clientId - The LD client-side ID.
* @param {LDContext} context - The user context.
* @param {LDOptions} options - The options.
* @returns {Promise} A promise that resolves when the client is initialized.
*/
initialize(clientId: LDClientID, context: LDContext, options?: LDOptions): Promise<void>;
/**
* Identifies the user context.
* @param {LDContext} context - The user context.
* @returns {Promise} A promise that resolves when the user is identified.
*/
identify(context: LDContext): Promise<LDIdentifyResult>;
/**
* The flags store.
*/
flags: Readable<LDFlags>;
/**
* The initialization state store.
*/
initalizationState: Readable<string>;
/**
* Watches a flag for changes.
* @param {string} flagKey - The key of the flag to watch.
* @returns {Readable<LDFlagValue>} A readable store of the flag value.
*/
watch(flagKey: string): Readable<LDFlagValue>;
/**
* Gets the current value of a flag.
* @param {string} flagKey - The key of the flag to get.
* @param {TFlag} defaultValue - The default value of the flag.
* @returns {TFlag} The current value of the flag.
*/
useFlag<TFlag extends LDFlagValue>(flagKey: string, defaultValue: TFlag): TFlag;
}
/**
* Checks if the LaunchDarkly client is initialized.
* @param {LDClient | undefined} client - The LaunchDarkly client.
* @throws {Error} If the client is not initialized.
*/
function isClientInitialized(client: LDClientBase | undefined): asserts client is LDClientBase {
if (!client) {
throw new Error('LaunchDarkly client not initialized');
}
}
/**
* Creates a proxy for the given flags object that intercepts access to flag values.
* When a flag value is accessed, it checks if the flag key exists in the target object.
* If the flag key exists, it returns the variation of the flag from the client.
* Otherwise, it returns the current value of the flag.
*
* @param client - The LaunchDarkly client instance used to get flag variations.
* @param flags - The initial flags object to be proxied.
* @returns A proxy object that intercepts access to flag values and returns the appropriate variation.
*/
function toFlagsProxy(client: LDClientBase, flags: LDFlags): LDFlags {
return new Proxy(flags, {
get(target, prop, receiver) {
const currentValue = Reflect.get(target, prop, receiver);
// only process flag keys and ignore symbols and native Object functions
if (typeof prop === 'symbol') {
return currentValue;
}
// check if flag key exists
const validFlagKey = Object.hasOwn(target, prop);
if (!validFlagKey) {
return currentValue;
}
return client.variation(prop, currentValue);
},
});
}
/**
* Creates a LaunchDarkly instance.
* @returns {Object} The LaunchDarkly instance object.
*/
function init(): LDClient {
let coreLdClient: LDClientBase | undefined;
const flagsWritable = writable<LDFlags>({});
const initializeResult = writable<string>('pending');
// NOTE: we will returns an empty promise for now as the promise states and handling is being wrappered
// we can evaluate this decision in the future before this SDK is marked as stable.
/**
* Initializes the LaunchDarkly client.
* @param {LDClientID} clientId - The client ID.
* @param {LDContext} context - The user context.
* @returns {Object} An object with the initialization status store.
*/
function initialize(
clientId: LDClientID,
context: LDContext,
options?: LDOptions,
): Promise<void> {
coreLdClient = createClient(clientId, context, options);
coreLdClient.on('change', () => {
const rawFlags = coreLdClient!.allFlags();
const allFlags = toFlagsProxy(coreLdClient!, rawFlags);
flagsWritable.set(allFlags);
});
// TODO: currently all options are defaulted which means that the client initailization will timeout in 5 seconds.
// we will need to address this before this SDK is marked as stable.
coreLdClient.start();
return coreLdClient
.waitForInitialization()
.then((result) => {
const rawFlags = coreLdClient!.allFlags();
const allFlags = toFlagsProxy(coreLdClient!, rawFlags);
flagsWritable.set(allFlags);
initializeResult.set(result.status);
})
.catch(() => {
// NOTE: this should never happen as we don't throw errors from initialization.
options?.logger?.error('Failed to initialize LaunchDarkly client');
initializeResult.set('failed');
});
}
/**
* Identifies the user context.
* @param {LDContext} context - The user context.
* @returns {Promise} A promise that resolves when the user is identified.
*/
async function identify(context: LDContext): Promise<LDIdentifyResult> {
isClientInitialized(coreLdClient);
return coreLdClient.identify(context);
}
/**
* Watches a flag for changes.
* @param {string} flagKey - The key of the flag to watch.
* @returns {Readable<LDFlagsValue>} A readable store of the flag value.
*/
const watch = (flagKey: string): Readable<LDFlagValue> =>
derived<Writable<LDFlags>, LDFlagValue>(flagsWritable, ($flags) => $flags[flagKey]);
/**
* Gets the current value of a flag.
* @param {string} flagKey - The key of the flag to get.
* @param {TFlag} defaultValue - The default value of the flag.
* @returns {TFlag} The current value of the flag.
*/
function useFlag<TFlag extends LDFlagValue>(flagKey: string, defaultValue: TFlag): TFlag {
isClientInitialized(coreLdClient);
return coreLdClient.variation(flagKey, defaultValue);
}
return {
identify,
flags: readonly(flagsWritable),
initialize,
initalizationState: readonly(initializeResult),
watch,
useFlag,
};
}
/** The LaunchDarkly instance */
export const LD = init();