@reflag/node-sdk 1.0.0-alpha.1

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +835 -0
  3. package/dist/package.json +49 -0
  4. package/dist/src/batch-buffer.js +85 -0
  5. package/dist/src/batch-buffer.js.map +1 -0
  6. package/dist/src/cache.js +71 -0
  7. package/dist/src/cache.js.map +1 -0
  8. package/dist/src/client.js +1141 -0
  9. package/dist/src/client.js.map +1 -0
  10. package/dist/src/config.js +98 -0
  11. package/dist/src/config.js.map +1 -0
  12. package/dist/src/edgeClient.js +32 -0
  13. package/dist/src/edgeClient.js.map +1 -0
  14. package/dist/src/fetch-http-client.js +85 -0
  15. package/dist/src/fetch-http-client.js.map +1 -0
  16. package/dist/src/flusher.js +56 -0
  17. package/dist/src/flusher.js.map +1 -0
  18. package/dist/src/inRequestCache.js +72 -0
  19. package/dist/src/inRequestCache.js.map +1 -0
  20. package/dist/src/index.js +9 -0
  21. package/dist/src/index.js.map +1 -0
  22. package/dist/src/periodicallyUpdatingCache.js +78 -0
  23. package/dist/src/periodicallyUpdatingCache.js.map +1 -0
  24. package/dist/src/rate-limiter.js +52 -0
  25. package/dist/src/rate-limiter.js.map +1 -0
  26. package/dist/src/types.js +6 -0
  27. package/dist/src/types.js.map +1 -0
  28. package/dist/src/utils.js +207 -0
  29. package/dist/src/utils.js.map +1 -0
  30. package/dist/types/src/batch-buffer.d.ts +27 -0
  31. package/dist/types/src/batch-buffer.d.ts.map +1 -0
  32. package/dist/types/src/cache.d.ts +16 -0
  33. package/dist/types/src/cache.d.ts.map +1 -0
  34. package/dist/types/src/client.d.ts +410 -0
  35. package/dist/types/src/client.d.ts.map +1 -0
  36. package/dist/types/src/config.d.ts +20 -0
  37. package/dist/types/src/config.d.ts.map +1 -0
  38. package/dist/types/src/edgeClient.d.ts +25 -0
  39. package/dist/types/src/edgeClient.d.ts.map +1 -0
  40. package/dist/types/src/fetch-http-client.d.ts +20 -0
  41. package/dist/types/src/fetch-http-client.d.ts.map +1 -0
  42. package/dist/types/src/flusher.d.ts +4 -0
  43. package/dist/types/src/flusher.d.ts.map +1 -0
  44. package/dist/types/src/inRequestCache.d.ts +7 -0
  45. package/dist/types/src/inRequestCache.d.ts.map +1 -0
  46. package/dist/types/src/index.d.ts +4 -0
  47. package/dist/types/src/index.d.ts.map +1 -0
  48. package/dist/types/src/periodicallyUpdatingCache.d.ts +16 -0
  49. package/dist/types/src/periodicallyUpdatingCache.d.ts.map +1 -0
  50. package/dist/types/src/rate-limiter.d.ts +14 -0
  51. package/dist/types/src/rate-limiter.d.ts.map +1 -0
  52. package/dist/types/src/types.d.ts +639 -0
  53. package/dist/types/src/types.d.ts.map +1 -0
  54. package/dist/types/src/utils.d.ts +65 -0
  55. package/dist/types/src/utils.d.ts.map +1 -0
  56. package/package.json +50 -0
@@ -0,0 +1,1141 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __rest = (this && this.__rest) || function (s, e) {
45
+ var t = {};
46
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
47
+ t[p] = s[p];
48
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
49
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
50
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
51
+ t[p[i]] = s[p[i]];
52
+ }
53
+ return t;
54
+ };
55
+ var __importDefault = (this && this.__importDefault) || function (mod) {
56
+ return (mod && mod.__esModule) ? mod : { "default": mod };
57
+ };
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.BoundReflagClient = exports.ReflagClient = void 0;
60
+ const fs_1 = __importDefault(require("fs"));
61
+ const flag_evaluation_1 = require("@reflag/flag-evaluation");
62
+ const batch_buffer_1 = __importDefault(require("./batch-buffer"));
63
+ const config_1 = require("./config");
64
+ const fetch_http_client_1 = __importStar(require("./fetch-http-client"));
65
+ const flusher_1 = require("./flusher");
66
+ const inRequestCache_1 = __importDefault(require("./inRequestCache"));
67
+ const periodicallyUpdatingCache_1 = __importDefault(require("./periodicallyUpdatingCache"));
68
+ const rate_limiter_1 = require("./rate-limiter");
69
+ const utils_1 = require("./utils");
70
+ const reflagConfigDefaultFile = "reflag.config.json";
71
+ /**
72
+ * The SDK client.
73
+ *
74
+ * @remarks
75
+ * This is the main class for interacting with Reflag.
76
+ * It is used to evaluate flags, update user and company contexts, and track events.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * // set the REFLAG_SECRET_KEY environment variable or pass the secret key to the constructor
81
+ * const client = new ReflagClient();
82
+ *
83
+ * // evaluate a flag
84
+ * const isFlagEnabled = client.getFlag("flag-key", {
85
+ * user: { id: "user-id" },
86
+ * company: { id: "company-id" },
87
+ * });
88
+ * ```
89
+ **/
90
+ class ReflagClient {
91
+ /**
92
+ * Creates a new SDK client.
93
+ * See README for configuration options.
94
+ *
95
+ * @param options - The options for the client or an existing client to clone.
96
+ * @param options.secretKey - The secret key to use for the client.
97
+ * @param options.apiBaseUrl - The base URL to send requests to (optional).
98
+ * @param options.logger - The logger to use for logging (optional).
99
+ * @param options.httpClient - The HTTP client to use for sending requests (optional).
100
+ * @param options.logLevel - The log level to use for logging (optional).
101
+ * @param options.offline - Whether to run in offline mode (optional).
102
+ * @param options.fallbackFlags - The fallback flags to use if the flag is not found (optional).
103
+ * @param options.batchOptions - The options for the batch buffer (optional).
104
+ * @param options.flagOverrides - The flag overrides to use for the client (optional).
105
+ * @param options.configFile - The path to the config file (optional).
106
+ * @param options.flagsFetchRetries - Number of retries for fetching flags (optional, defaults to 3).
107
+ * @param options.fetchTimeoutMs - Timeout for fetching flags (optional, defaults to 10000ms).
108
+ * @param options.cacheStrategy - The cache strategy to use for the client (optional, defaults to "periodically-update").
109
+ *
110
+ * @throws An error if the options are invalid.
111
+ **/
112
+ constructor(options = {}) {
113
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
114
+ this.initializationFinished = false;
115
+ this._initialize = (0, utils_1.once)(() => __awaiter(this, void 0, void 0, function* () {
116
+ const start = Date.now();
117
+ if (!this._config.offline) {
118
+ yield this.flagsCache.refresh();
119
+ }
120
+ this.logger.info("Reflag initialized in " +
121
+ Math.round(Date.now() - start) +
122
+ "ms" +
123
+ (this._config.offline ? " (offline mode)" : ""));
124
+ this.initializationFinished = true;
125
+ }));
126
+ (0, utils_1.ok)((0, utils_1.isObject)(options), "options must be an object");
127
+ (0, utils_1.ok)(options.host === undefined ||
128
+ (typeof options.host === "string" && options.host.length > 0), "host must be a string");
129
+ (0, utils_1.ok)(options.apiBaseUrl === undefined ||
130
+ (typeof options.apiBaseUrl === "string" &&
131
+ options.apiBaseUrl.length > 0), "apiBaseUrl must be a string");
132
+ (0, utils_1.ok)(options.logger === undefined || (0, utils_1.isObject)(options.logger), "logger must be an object");
133
+ (0, utils_1.ok)(options.httpClient === undefined || (0, utils_1.isObject)(options.httpClient), "httpClient must be an object");
134
+ (0, utils_1.ok)(options.fallbackFlags === undefined ||
135
+ Array.isArray(options.fallbackFlags) ||
136
+ (0, utils_1.isObject)(options.fallbackFlags), "fallbackFlags must be an array or object");
137
+ (0, utils_1.ok)(options.batchOptions === undefined || (0, utils_1.isObject)(options.batchOptions), "batchOptions must be an object");
138
+ (0, utils_1.ok)(options.configFile === undefined ||
139
+ typeof options.configFile === "string", "configFile must be a string");
140
+ (0, utils_1.ok)(options.flagsFetchRetries === undefined ||
141
+ (Number.isInteger(options.flagsFetchRetries) &&
142
+ options.flagsFetchRetries >= 0), "flagsFetchRetries must be a non-negative integer");
143
+ (0, utils_1.ok)(options.fetchTimeoutMs === undefined ||
144
+ (Number.isInteger(options.fetchTimeoutMs) &&
145
+ options.fetchTimeoutMs >= 0), "fetchTimeoutMs must be a non-negative integer");
146
+ if (!options.configFile) {
147
+ options.configFile =
148
+ ((_a = process.env.REFLAG_CONFIG_FILE) !== null && _a !== void 0 ? _a : fs_1.default.existsSync(reflagConfigDefaultFile))
149
+ ? reflagConfigDefaultFile
150
+ : undefined;
151
+ }
152
+ const externalConfig = (0, config_1.loadConfig)(options.configFile);
153
+ const config = (0, utils_1.mergeSkipUndefined)(externalConfig, options);
154
+ const offline = (_b = config.offline) !== null && _b !== void 0 ? _b : process.env.NODE_ENV === "test";
155
+ if (!offline) {
156
+ (0, utils_1.ok)(typeof config.secretKey === "string", "secretKey must be a string, or set offline=true");
157
+ (0, utils_1.ok)(config.secretKey.length > 22, "invalid secretKey specified");
158
+ }
159
+ // use the supplied logger or apply the log level to the console logger
160
+ const logLevel = (_d = (_c = options.logLevel) !== null && _c !== void 0 ? _c : config === null || config === void 0 ? void 0 : config.logLevel) !== null && _d !== void 0 ? _d : "INFO";
161
+ this.logger = options.logger
162
+ ? options.logger
163
+ : (0, utils_1.applyLogLevel)((0, utils_1.decorateLogger)(config_1.REFLAG_LOG_PREFIX, console), logLevel);
164
+ const fallbackFlags = Array.isArray(options.fallbackFlags)
165
+ ? options.fallbackFlags.reduce((acc, key) => {
166
+ acc[key] = {
167
+ isEnabled: true,
168
+ key,
169
+ };
170
+ return acc;
171
+ }, {})
172
+ : (0, utils_1.isObject)(options.fallbackFlags)
173
+ ? Object.entries(options.fallbackFlags).reduce((acc, [key, fallback]) => {
174
+ acc[key] = {
175
+ isEnabled: typeof fallback === "object"
176
+ ? fallback.isEnabled
177
+ : !!fallback,
178
+ key,
179
+ config: typeof fallback === "object" && fallback.config
180
+ ? {
181
+ key: fallback.config.key,
182
+ payload: fallback.config.payload,
183
+ }
184
+ : undefined,
185
+ };
186
+ return acc;
187
+ }, {})
188
+ : undefined;
189
+ this.rateLimiter = (0, rate_limiter_1.newRateLimiter)(config_1.FLAG_EVENT_RATE_LIMITER_WINDOW_SIZE_MS);
190
+ this.httpClient = options.httpClient || fetch_http_client_1.default;
191
+ this.batchBuffer = new batch_buffer_1.default(Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.batchOptions), { flushHandler: (items) => this.sendBulkEvents(items), logger: this.logger }));
192
+ this._config = {
193
+ offline,
194
+ emitEvaluationEvents: (_e = config.emitEvaluationEvents) !== null && _e !== void 0 ? _e : true,
195
+ apiBaseUrl: ((_f = config.apiBaseUrl) !== null && _f !== void 0 ? _f : config.host) || config_1.API_BASE_URL,
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ [config_1.SDK_VERSION_HEADER_NAME]: config_1.SDK_VERSION,
199
+ ["Authorization"]: `Bearer ${config.secretKey}`,
200
+ },
201
+ refetchInterval: config_1.FLAGS_REFETCH_MS,
202
+ staleWarningInterval: config_1.FLAGS_REFETCH_MS * 5,
203
+ fallbackFlags: fallbackFlags,
204
+ flagOverrides: typeof config.flagOverrides === "function"
205
+ ? config.flagOverrides
206
+ : () => config.flagOverrides,
207
+ flagsFetchRetries: (_g = options.flagsFetchRetries) !== null && _g !== void 0 ? _g : 3,
208
+ fetchTimeoutMs: (_h = options.fetchTimeoutMs) !== null && _h !== void 0 ? _h : config_1.API_TIMEOUT_MS,
209
+ cacheStrategy: (_j = options.cacheStrategy) !== null && _j !== void 0 ? _j : "periodically-update",
210
+ };
211
+ if (((_l = (_k = config.batchOptions) === null || _k === void 0 ? void 0 : _k.flushOnExit) !== null && _l !== void 0 ? _l : true) && !this._config.offline) {
212
+ (0, flusher_1.subscribe)(() => this.flush());
213
+ }
214
+ if (!new URL(this._config.apiBaseUrl).pathname.endsWith("/")) {
215
+ this._config.apiBaseUrl += "/";
216
+ }
217
+ const fetchFlags = () => __awaiter(this, void 0, void 0, function* () {
218
+ const res = yield this.get("features", this._config.flagsFetchRetries);
219
+ if (!(0, utils_1.isObject)(res) || !Array.isArray(res === null || res === void 0 ? void 0 : res.features)) {
220
+ this.logger.warn("flags cache: invalid response", res);
221
+ return undefined;
222
+ }
223
+ return res.features.map((flagDef) => {
224
+ var _a;
225
+ return Object.assign(Object.assign({}, flagDef), { enabledEvaluator: (0, flag_evaluation_1.newEvaluator)(flagDef.targeting.rules.map((rule) => ({
226
+ filter: rule.filter,
227
+ value: true,
228
+ }))), configEvaluator: flagDef.config
229
+ ? (0, flag_evaluation_1.newEvaluator)((_a = flagDef.config) === null || _a === void 0 ? void 0 : _a.variants.map((variant) => ({
230
+ filter: variant.filter,
231
+ value: {
232
+ key: variant.key,
233
+ payload: variant.payload,
234
+ },
235
+ })))
236
+ : undefined });
237
+ });
238
+ });
239
+ if (this._config.cacheStrategy === "periodically-update") {
240
+ this.flagsCache = (0, periodicallyUpdatingCache_1.default)(this._config.refetchInterval, this._config.staleWarningInterval, this.logger, fetchFlags);
241
+ }
242
+ else {
243
+ this.flagsCache = (0, inRequestCache_1.default)(this._config.refetchInterval, this.logger, fetchFlags);
244
+ }
245
+ }
246
+ /**
247
+ * Sets the flag overrides.
248
+ *
249
+ * @param overrides - The flag overrides.
250
+ *
251
+ * @remarks
252
+ * The flag overrides are used to override the flag definitions.
253
+ * This is useful for testing or development.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * client.flagOverrides = {
258
+ * "flag-1": true,
259
+ * "flag-2": false,
260
+ * };
261
+ * ```
262
+ **/
263
+ set flagOverrides(overrides) {
264
+ if (typeof overrides === "object") {
265
+ this._config.flagOverrides = () => overrides;
266
+ }
267
+ else {
268
+ this._config.flagOverrides = overrides;
269
+ }
270
+ }
271
+ /**
272
+ * Clears the flag overrides.
273
+ *
274
+ * @remarks
275
+ * This is useful for testing or development.
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * afterAll(() => {
280
+ * client.clearFlagOverrides();
281
+ * });
282
+ * ```
283
+ **/
284
+ clearFlagOverrides() {
285
+ this._config.flagOverrides = () => ({});
286
+ }
287
+ /**
288
+ * Returns a new BoundReflagClient with the user/company/otherContext
289
+ * set to be used in subsequent calls.
290
+ * For example, for evaluating flag targeting or tracking events.
291
+ *
292
+ * @param context - The context to bind the client to.
293
+ * @param context.enableTracking - Whether to enable tracking for the context.
294
+ * @param context.user - The user context.
295
+ * @param context.company - The company context.
296
+ * @param context.other - The other context.
297
+ *
298
+ * @returns A new client bound with the arguments given.
299
+ *
300
+ * @throws An error if the user/company is given but their ID is not a string.
301
+ *
302
+ * @remarks
303
+ * The `updateUser` / `updateCompany` methods will automatically be called when
304
+ * the user/company is set respectively.
305
+ **/
306
+ bindClient(_a) {
307
+ var { enableTracking = true } = _a, context = __rest(_a, ["enableTracking"]);
308
+ return new BoundReflagClient(this, Object.assign({ enableTracking }, context));
309
+ }
310
+ /**
311
+ * Updates the associated user in Reflag.
312
+ *
313
+ * @param userId - The userId of the user to update.
314
+ * @param options - The options for the user.
315
+ * @param options.attributes - The additional attributes of the user (optional).
316
+ * @param options.meta - The meta context associated with tracking (optional).
317
+ *
318
+ * @throws An error if the company is not set or the options are invalid.
319
+ * @remarks
320
+ * The company must be set using `withCompany` before calling this method.
321
+ * If the user is set, the company will be associated with the user.
322
+ **/
323
+ updateUser(userId, options) {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ (0, utils_1.idOk)(userId, "userId");
326
+ (0, utils_1.ok)(options === undefined || (0, utils_1.isObject)(options), "options must be an object");
327
+ (0, utils_1.ok)((options === null || options === void 0 ? void 0 : options.attributes) === undefined || (0, utils_1.isObject)(options.attributes), "attributes must be an object");
328
+ checkMeta(options === null || options === void 0 ? void 0 : options.meta);
329
+ if (this._config.offline) {
330
+ return;
331
+ }
332
+ if (this.rateLimiter.isAllowed((0, utils_1.hashObject)(Object.assign(Object.assign({}, options), { userId })))) {
333
+ yield this.batchBuffer.add({
334
+ type: "user",
335
+ userId,
336
+ attributes: options === null || options === void 0 ? void 0 : options.attributes,
337
+ context: options === null || options === void 0 ? void 0 : options.meta,
338
+ });
339
+ }
340
+ });
341
+ }
342
+ /**
343
+ * Updates the associated company in Reflag.
344
+ *
345
+ * @param companyId - The companyId of the company to update.
346
+ * @param options - The options for the company.
347
+ * @param options.attributes - The additional attributes of the company (optional).
348
+ * @param options.meta - The meta context associated with tracking (optional).
349
+ * @param options.userId - The userId of the user to associate with the company (optional).
350
+ *
351
+ * @throws An error if the company is not set or the options are invalid.
352
+ * @remarks
353
+ * The company must be set using `withCompany` before calling this method.
354
+ * If the user is set, the company will be associated with the user.
355
+ **/
356
+ updateCompany(companyId, options) {
357
+ return __awaiter(this, void 0, void 0, function* () {
358
+ (0, utils_1.idOk)(companyId, "companyId");
359
+ (0, utils_1.ok)(options === undefined || (0, utils_1.isObject)(options), "options must be an object");
360
+ (0, utils_1.ok)((options === null || options === void 0 ? void 0 : options.attributes) === undefined || (0, utils_1.isObject)(options.attributes), "attributes must be an object");
361
+ checkMeta(options === null || options === void 0 ? void 0 : options.meta);
362
+ if (typeof (options === null || options === void 0 ? void 0 : options.userId) !== "undefined") {
363
+ (0, utils_1.idOk)(options === null || options === void 0 ? void 0 : options.userId, "userId");
364
+ }
365
+ if (this._config.offline) {
366
+ return;
367
+ }
368
+ if (this.rateLimiter.isAllowed((0, utils_1.hashObject)(Object.assign(Object.assign({}, options), { companyId })))) {
369
+ yield this.batchBuffer.add({
370
+ type: "company",
371
+ companyId,
372
+ userId: options === null || options === void 0 ? void 0 : options.userId,
373
+ attributes: options === null || options === void 0 ? void 0 : options.attributes,
374
+ context: options === null || options === void 0 ? void 0 : options.meta,
375
+ });
376
+ }
377
+ });
378
+ }
379
+ /**
380
+ * Tracks an event in Reflag.
381
+
382
+ * @param options.companyId - Optional company ID for the event (optional).
383
+ *
384
+ * @throws An error if the user is not set or the event is invalid or the options are invalid.
385
+ * @remarks
386
+ * If the company is set, the event will be associated with the company.
387
+ **/
388
+ track(userId, event, options) {
389
+ return __awaiter(this, void 0, void 0, function* () {
390
+ (0, utils_1.idOk)(userId, "userId");
391
+ (0, utils_1.ok)(typeof event === "string" && event.length > 0, "event must be a string");
392
+ (0, utils_1.ok)(options === undefined || (0, utils_1.isObject)(options), "options must be an object");
393
+ (0, utils_1.ok)((options === null || options === void 0 ? void 0 : options.attributes) === undefined || (0, utils_1.isObject)(options.attributes), "attributes must be an object");
394
+ (0, utils_1.ok)((options === null || options === void 0 ? void 0 : options.meta) === undefined || (0, utils_1.isObject)(options.meta), "meta must be an object");
395
+ if ((options === null || options === void 0 ? void 0 : options.companyId) !== undefined) {
396
+ (0, utils_1.idOk)(options === null || options === void 0 ? void 0 : options.companyId, "companyId");
397
+ }
398
+ if (this._config.offline) {
399
+ return;
400
+ }
401
+ yield this.batchBuffer.add({
402
+ type: "event",
403
+ event,
404
+ companyId: options === null || options === void 0 ? void 0 : options.companyId,
405
+ userId,
406
+ attributes: options === null || options === void 0 ? void 0 : options.attributes,
407
+ context: options === null || options === void 0 ? void 0 : options.meta,
408
+ });
409
+ });
410
+ }
411
+ /**
412
+ * Initializes the client by caching the flags definitions.
413
+ *
414
+ * @remarks
415
+ * Call this method before calling `getFlags` to ensure the flag definitions are cached.
416
+ * The client will ignore subsequent calls to this method.
417
+ **/
418
+ initialize() {
419
+ return __awaiter(this, void 0, void 0, function* () {
420
+ yield this._initialize();
421
+ return;
422
+ });
423
+ }
424
+ /**
425
+ * Flushes and completes any in-flight fetches in the flag cache.
426
+ *
427
+ * @remarks
428
+ * It is recommended to call this method when the application is shutting down to ensure all events are sent
429
+ * before the process exits.
430
+ *
431
+ * This method is automatically called when the process exits if `batchOptions.flushOnExit` is `true` in the options (default).
432
+ */
433
+ flush() {
434
+ return __awaiter(this, void 0, void 0, function* () {
435
+ if (this._config.offline) {
436
+ return;
437
+ }
438
+ yield this.batchBuffer.flush();
439
+ yield this.flagsCache.waitRefresh();
440
+ });
441
+ }
442
+ /**
443
+ * Gets the flag definitions, including all config values.
444
+ * To evaluate which flags are enabled for a given user/company, use `getFlags`.
445
+ *
446
+ * @returns The flags definitions.
447
+ */
448
+ getFlagDefinitions() {
449
+ const flags = this.flagsCache.get() || [];
450
+ return flags.map((f) => ({
451
+ key: f.key,
452
+ description: f.description,
453
+ flag: f.targeting,
454
+ config: f.config,
455
+ }));
456
+ }
457
+ /**
458
+ * Gets the evaluated flags for the current context which includes the user, company, and custom context.
459
+ *
460
+ * @param options - The options for the context.
461
+ * @param options.enableTracking - Whether to enable tracking for the context.
462
+ * @param options.meta - The meta context associated with the context.
463
+ * @param options.user - The user context.
464
+ * @param options.company - The company context.
465
+ * @param options.other - The other context.
466
+ *
467
+ * @returns The evaluated flags.
468
+ *
469
+ * @remarks
470
+ * Call `initialize` before calling this method to ensure the flag definitions are cached, no flags will be returned otherwise.
471
+ **/
472
+ getFlags(_a) {
473
+ var { enableTracking = true } = _a, context = __rest(_a, ["enableTracking"]);
474
+ return this._getFlags(Object.assign({ enableTracking }, context));
475
+ }
476
+ /**
477
+ * Gets the evaluated flag for the current context which includes the user, company, and custom context.
478
+ * Using the `isEnabled` property sends a `check` event to Reflag.
479
+ *
480
+ * @param key - The key of the flag to get.
481
+ * @returns The evaluated flag.
482
+ *
483
+ * @remarks
484
+ * Call `initialize` before calling this method to ensure the flag definitions are cached, no flags will be returned otherwise.
485
+ **/
486
+ getFlag(_a, key) {
487
+ var { enableTracking = true } = _a, context = __rest(_a, ["enableTracking"]);
488
+ return this._getFlags(Object.assign({ enableTracking }, context), key);
489
+ }
490
+ /**
491
+ * Gets evaluated flags with the usage of remote context.
492
+ * This method triggers a network request every time it's called.
493
+ *
494
+ * @param userId - The userId of the user to get the flags for.
495
+ * @param companyId - The companyId of the company to get the flags for.
496
+ * @param additionalContext - The additional context to get the flags for.
497
+ *
498
+ * @returns evaluated flags
499
+ */
500
+ getFlagsRemote(userId, companyId, additionalContext) {
501
+ return __awaiter(this, void 0, void 0, function* () {
502
+ return this._getFlagsRemote(undefined, userId, companyId, additionalContext);
503
+ });
504
+ }
505
+ /**
506
+ * Gets evaluated flag with the usage of remote context.
507
+ * This method triggers a network request every time it's called.
508
+ *
509
+ * @param key - The key of the flag to get.
510
+ * @param userId - The userId of the user to get the flag for.
511
+ * @param companyId - The companyId of the company to get the flag for.
512
+ * @param additionalContext - The additional context to get the flag for.
513
+ *
514
+ * @returns evaluated flag
515
+ */
516
+ getFlagRemote(key, userId, companyId, additionalContext) {
517
+ return __awaiter(this, void 0, void 0, function* () {
518
+ return this._getFlagsRemote(key, userId, companyId, additionalContext);
519
+ });
520
+ }
521
+ buildUrl(path) {
522
+ if (path.startsWith("/")) {
523
+ path = path.slice(1);
524
+ }
525
+ const url = new URL(path, this._config.apiBaseUrl);
526
+ return url.toString();
527
+ }
528
+ /**
529
+ * Sends a POST request to the specified path.
530
+ *
531
+ * @param path - The path to send the request to.
532
+ * @param body - The body of the request.
533
+ *
534
+ * @returns A boolean indicating if the request was successful.
535
+ *
536
+ * @throws An error if the path or body is invalid.
537
+ **/
538
+ post(path, body) {
539
+ return __awaiter(this, void 0, void 0, function* () {
540
+ (0, utils_1.ok)(typeof path === "string" && path.length > 0, "path must be a string");
541
+ (0, utils_1.ok)(typeof body === "object", "body must be an object");
542
+ const url = this.buildUrl(path);
543
+ try {
544
+ const response = yield this.httpClient.post(url, this._config.headers, body);
545
+ this.logger.debug(`post request to "${url}"`, response);
546
+ if (!response.ok || !(0, utils_1.isObject)(response.body) || !response.body.success) {
547
+ this.logger.warn(`invalid response received from server for "${url}"`, JSON.stringify(response));
548
+ return false;
549
+ }
550
+ return true;
551
+ }
552
+ catch (error) {
553
+ this.logger.error(`post request to "${url}" failed with error`, error);
554
+ return false;
555
+ }
556
+ });
557
+ }
558
+ /**
559
+ * Sends a GET request to the specified path.
560
+ *
561
+ * @param path - The path to send the request to.
562
+ * @param retries - Optional number of retries for the request.
563
+ *
564
+ * @returns The response from the server.
565
+ * @throws An error if the path is invalid.
566
+ **/
567
+ get(path_1) {
568
+ return __awaiter(this, arguments, void 0, function* (path, retries = 3) {
569
+ (0, utils_1.ok)(typeof path === "string" && path.length > 0, "path must be a string");
570
+ try {
571
+ const url = this.buildUrl(path);
572
+ return yield (0, fetch_http_client_1.withRetry)(() => __awaiter(this, void 0, void 0, function* () {
573
+ const response = yield this.httpClient.get(url, this._config.headers, this._config.fetchTimeoutMs);
574
+ this.logger.debug(`get request to "${url}"`, response);
575
+ if (!response.ok ||
576
+ !(0, utils_1.isObject)(response.body) ||
577
+ !response.body.success) {
578
+ throw new Error(`invalid response received from server for "${url}": ${JSON.stringify(response.body)}`);
579
+ }
580
+ const _a = response.body, { success: _ } = _a, result = __rest(_a, ["success"]);
581
+ return result;
582
+ }), () => {
583
+ this.logger.warn("failed to fetch flags, will retry");
584
+ }, retries, 1000, 10000);
585
+ }
586
+ catch (error) {
587
+ this.logger.error(`get request to "${path}" failed with error after ${retries} retries`, error);
588
+ return undefined;
589
+ }
590
+ });
591
+ }
592
+ /**
593
+ * Sends a batch of events to the Reflag API.
594
+ *
595
+ * @param events - The events to send.
596
+ *
597
+ * @throws An error if the send fails.
598
+ **/
599
+ sendBulkEvents(events) {
600
+ return __awaiter(this, void 0, void 0, function* () {
601
+ (0, utils_1.ok)(Array.isArray(events) && events.length > 0, "events must be a non-empty array");
602
+ const sent = yield this.post("bulk", events);
603
+ if (!sent) {
604
+ throw new Error("Failed to send bulk events");
605
+ }
606
+ });
607
+ }
608
+ /**
609
+ * Sends a flag event to the Reflag API.
610
+ *
611
+ * Flag events are used to track the evaluation of flag targeting rules.
612
+ * "check" events are sent when a flag's `isEnabled` property is checked.
613
+ * "evaluate" events are sent when a flag's targeting rules are matched against
614
+ * the current context.
615
+ *
616
+ * @param event - The event to send.
617
+ * @param event.action - The action to send.
618
+ * @param event.key - The key of the flag to send.
619
+ * @param event.targetingVersion - The targeting version of the flag to send.
620
+ * @param event.evalResult - The evaluation result of the flag to send.
621
+ * @param event.evalContext - The evaluation context of the flag to send.
622
+ * @param event.evalRuleResults - The evaluation rule results of the flag to send.
623
+ * @param event.evalMissingFields - The evaluation missing fields of the flag to send.
624
+ *
625
+ * @throws An error if the event is invalid.
626
+ *
627
+ * @remarks
628
+ * This method is rate-limited to prevent too many events from being sent.
629
+ **/
630
+ sendFlagEvent(event) {
631
+ return __awaiter(this, void 0, void 0, function* () {
632
+ (0, utils_1.ok)(typeof event === "object", "event must be an object");
633
+ (0, utils_1.ok)(typeof event.action === "string" &&
634
+ (event.action === "evaluate" ||
635
+ event.action === "evaluate-config" ||
636
+ event.action === "check" ||
637
+ event.action === "check-config"), "event must have an action");
638
+ (0, utils_1.ok)(typeof event.key === "string" && event.key.length > 0, "event must have a flag key");
639
+ (0, utils_1.ok)(typeof event.targetingVersion === "number" ||
640
+ event.targetingVersion === undefined, "event must have a targeting version");
641
+ (0, utils_1.ok)(typeof event.evalResult === "boolean" || (0, utils_1.isObject)(event.evalResult), "event must have an evaluation result");
642
+ (0, utils_1.ok)(event.evalContext === undefined || typeof event.evalContext === "object", "event context must be an object");
643
+ (0, utils_1.ok)(event.evalRuleResults === undefined ||
644
+ Array.isArray(event.evalRuleResults), "event rule results must be an array");
645
+ (0, utils_1.ok)(event.evalMissingFields === undefined ||
646
+ Array.isArray(event.evalMissingFields), "event missing fields must be an array");
647
+ const contextKey = new URLSearchParams((0, flag_evaluation_1.flattenJSON)(event.evalContext || {})).toString();
648
+ if (this._config.offline) {
649
+ return;
650
+ }
651
+ if (!this._config.emitEvaluationEvents &&
652
+ (event.action === "evaluate" || event.action === "evaluate-config")) {
653
+ return;
654
+ }
655
+ if (!this.rateLimiter.isAllowed((0, utils_1.hashObject)({
656
+ action: event.action,
657
+ key: event.key,
658
+ targetingVersion: event.targetingVersion,
659
+ evalResult: event.evalResult,
660
+ contextKey,
661
+ }))) {
662
+ return;
663
+ }
664
+ yield this.batchBuffer.add({
665
+ type: "feature-flag-event",
666
+ action: event.action,
667
+ key: event.key,
668
+ targetingVersion: event.targetingVersion,
669
+ evalContext: event.evalContext,
670
+ evalResult: event.evalResult,
671
+ evalRuleResults: event.evalRuleResults,
672
+ evalMissingFields: event.evalMissingFields,
673
+ });
674
+ });
675
+ }
676
+ /**
677
+ * Updates the context in Reflag (if needed).
678
+ * This method should be used before requesting flags or binding a client.
679
+ *
680
+ * @param options - The options for the context.
681
+ * @param options.enableTracking - Whether to enable tracking for the context.
682
+ * @param options.meta - The meta context associated with the context.
683
+ * @param options.user - The user context.
684
+ * @param options.company - The company context.
685
+ * @param options.other - The other context.
686
+ */
687
+ syncContext(options) {
688
+ return __awaiter(this, void 0, void 0, function* () {
689
+ var _a, _b;
690
+ if (!options.enableTracking) {
691
+ this.logger.debug("tracking disabled, not updating user/company");
692
+ return;
693
+ }
694
+ const promises = [];
695
+ if (typeof ((_a = options.company) === null || _a === void 0 ? void 0 : _a.id) !== "undefined") {
696
+ const _c = options.company, { id: _ } = _c, attributes = __rest(_c, ["id"]);
697
+ promises.push(this.updateCompany(options.company.id, {
698
+ attributes,
699
+ meta: options.meta,
700
+ }));
701
+ }
702
+ if (typeof ((_b = options.user) === null || _b === void 0 ? void 0 : _b.id) !== "undefined") {
703
+ const _d = options.user, { id: _ } = _d, attributes = __rest(_d, ["id"]);
704
+ promises.push(this.updateUser(options.user.id, {
705
+ attributes,
706
+ meta: options.meta,
707
+ }));
708
+ }
709
+ if (promises.length > 0) {
710
+ yield Promise.all(promises);
711
+ }
712
+ });
713
+ }
714
+ /**
715
+ * Warns if a flag has targeting rules that require context fields that are missing.
716
+ *
717
+ * @param context - The context.
718
+ * @param flag - The flag to check.
719
+ */
720
+ _warnMissingFlagContextFields(context, flag) {
721
+ var _a, _b;
722
+ const report = {};
723
+ const { config } = flag, flagData = __rest(flag, ["config"]);
724
+ if (((_a = flagData.missingContextFields) === null || _a === void 0 ? void 0 : _a.length) &&
725
+ this.rateLimiter.isAllowed((0, utils_1.hashObject)({
726
+ flagKey: flagData.key,
727
+ missingContextFields: flagData.missingContextFields,
728
+ context,
729
+ }))) {
730
+ report[flagData.key] = flagData.missingContextFields;
731
+ }
732
+ if (((_b = config === null || config === void 0 ? void 0 : config.missingContextFields) === null || _b === void 0 ? void 0 : _b.length) &&
733
+ this.rateLimiter.isAllowed((0, utils_1.hashObject)({
734
+ flagKey: flagData.key,
735
+ configKey: config.key,
736
+ missingContextFields: config.missingContextFields,
737
+ context,
738
+ }))) {
739
+ report[`${flagData.key}.config`] = config.missingContextFields;
740
+ }
741
+ if (Object.keys(report).length > 0) {
742
+ this.logger.warn(`flag targeting rules might not be correctly evaluated due to missing context fields.`, report);
743
+ }
744
+ }
745
+ _getFlags(options, key) {
746
+ checkContextWithTracking(options);
747
+ if (!this.initializationFinished) {
748
+ this.logger.error("getFlag(s): ReflagClient is not initialized yet.");
749
+ }
750
+ void this.syncContext(options);
751
+ let flagDefinitions = [];
752
+ if (!this._config.offline) {
753
+ const flagDefs = this.flagsCache.get();
754
+ if (!flagDefs) {
755
+ this.logger.warn("no flag definitions available, using fallback flags.");
756
+ const fallbackFlags = this._config.fallbackFlags || {};
757
+ if (key) {
758
+ return this._wrapRawFlag(Object.assign(Object.assign({}, options), { enableChecks: true }), Object.assign({ key }, fallbackFlags[key]));
759
+ }
760
+ return Object.fromEntries(Object.entries(fallbackFlags).map(([k, v]) => [
761
+ k,
762
+ this._wrapRawFlag(options, v),
763
+ ]));
764
+ }
765
+ flagDefinitions = flagDefs;
766
+ }
767
+ const { enableTracking = true, meta: _ } = options, context = __rest(options, ["enableTracking", "meta"]);
768
+ const evaluated = flagDefinitions
769
+ .filter(({ key: flagKey }) => (key ? key === flagKey : true))
770
+ .map((flag) => {
771
+ var _a, _b, _c;
772
+ return ({
773
+ flagKey: flag.key,
774
+ targetingVersion: flag.targeting.version,
775
+ configVersion: (_a = flag.config) === null || _a === void 0 ? void 0 : _a.version,
776
+ enabledResult: flag.enabledEvaluator(context, flag.key),
777
+ configResult: (_c = (_b = flag.configEvaluator) === null || _b === void 0 ? void 0 : _b.call(flag, context, flag.key)) !== null && _c !== void 0 ? _c : {
778
+ flagKey: flag.key,
779
+ context,
780
+ value: undefined,
781
+ ruleEvaluationResults: [],
782
+ missingContextFields: [],
783
+ },
784
+ });
785
+ });
786
+ if (enableTracking) {
787
+ const promises = evaluated
788
+ .map((res) => {
789
+ var _a;
790
+ const outPromises = [];
791
+ outPromises.push(this.sendFlagEvent({
792
+ action: "evaluate",
793
+ key: res.flagKey,
794
+ targetingVersion: res.targetingVersion,
795
+ evalResult: (_a = res.enabledResult.value) !== null && _a !== void 0 ? _a : false,
796
+ evalContext: res.enabledResult.context,
797
+ evalRuleResults: res.enabledResult.ruleEvaluationResults,
798
+ evalMissingFields: res.enabledResult.missingContextFields,
799
+ }));
800
+ const config = res.configResult;
801
+ if (config.value) {
802
+ outPromises.push(this.sendFlagEvent({
803
+ action: "evaluate-config",
804
+ key: res.flagKey,
805
+ targetingVersion: res.configVersion,
806
+ evalResult: config.value,
807
+ evalContext: config.context,
808
+ evalRuleResults: config.ruleEvaluationResults,
809
+ evalMissingFields: config.missingContextFields,
810
+ }));
811
+ }
812
+ return outPromises;
813
+ })
814
+ .flat();
815
+ void Promise.allSettled(promises).then((results) => {
816
+ const failed = results
817
+ .map((result) => result.status === "rejected" ? result.reason : undefined)
818
+ .filter(Boolean);
819
+ if (failed.length > 0) {
820
+ this.logger.error(`failed to queue some evaluate events.`, {
821
+ errors: failed,
822
+ });
823
+ }
824
+ });
825
+ }
826
+ let evaluatedFlags = evaluated.reduce((acc, res) => {
827
+ var _a, _b, _c, _d, _e, _f, _g;
828
+ acc[res.flagKey] = {
829
+ key: res.flagKey,
830
+ isEnabled: (_a = res.enabledResult.value) !== null && _a !== void 0 ? _a : false,
831
+ ruleEvaluationResults: res.enabledResult.ruleEvaluationResults,
832
+ missingContextFields: res.enabledResult.missingContextFields,
833
+ targetingVersion: res.targetingVersion,
834
+ config: {
835
+ key: (_c = (_b = res.configResult) === null || _b === void 0 ? void 0 : _b.value) === null || _c === void 0 ? void 0 : _c.key,
836
+ payload: (_e = (_d = res.configResult) === null || _d === void 0 ? void 0 : _d.value) === null || _e === void 0 ? void 0 : _e.payload,
837
+ targetingVersion: res.configVersion,
838
+ ruleEvaluationResults: (_f = res.configResult) === null || _f === void 0 ? void 0 : _f.ruleEvaluationResults,
839
+ missingContextFields: (_g = res.configResult) === null || _g === void 0 ? void 0 : _g.missingContextFields,
840
+ },
841
+ };
842
+ return acc;
843
+ }, {});
844
+ // apply flag overrides
845
+ const overrides = Object.entries(this._config.flagOverrides(context))
846
+ .filter(([flagKey]) => (key ? key === flagKey : true))
847
+ .map(([flagKey, override]) => [
848
+ flagKey,
849
+ (0, utils_1.isObject)(override)
850
+ ? {
851
+ key: flagKey,
852
+ isEnabled: override.isEnabled,
853
+ config: override.config,
854
+ }
855
+ : {
856
+ key: flagKey,
857
+ isEnabled: !!override,
858
+ config: undefined,
859
+ },
860
+ ]);
861
+ if (overrides.length > 0) {
862
+ // merge overrides into evaluated flags
863
+ evaluatedFlags = Object.assign(Object.assign({}, evaluatedFlags), Object.fromEntries(overrides));
864
+ }
865
+ if (key) {
866
+ return this._wrapRawFlag(Object.assign(Object.assign({}, options), { enableChecks: true }), Object.assign({ key }, evaluatedFlags[key]));
867
+ }
868
+ return Object.fromEntries(Object.entries(evaluatedFlags).map(([k, v]) => [
869
+ k,
870
+ this._wrapRawFlag(options, v),
871
+ ]));
872
+ }
873
+ _wrapRawFlag(_a, _b) {
874
+ var { enableTracking, enableChecks = false } = _a, context = __rest(_a, ["enableTracking", "enableChecks"]);
875
+ var config = _b.config, flag = __rest(_b, ["config"]);
876
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
877
+ const client = this;
878
+ const simplifiedConfig = config
879
+ ? { key: config.key, payload: config.payload }
880
+ : { key: undefined, payload: undefined };
881
+ return {
882
+ get isEnabled() {
883
+ var _a, _b;
884
+ if (enableTracking && enableChecks) {
885
+ client._warnMissingFlagContextFields(context, flag);
886
+ void client
887
+ .sendFlagEvent({
888
+ action: "check",
889
+ key: flag.key,
890
+ targetingVersion: flag.targetingVersion,
891
+ evalResult: (_a = flag.isEnabled) !== null && _a !== void 0 ? _a : false,
892
+ evalContext: context,
893
+ evalRuleResults: flag.ruleEvaluationResults,
894
+ evalMissingFields: flag.missingContextFields,
895
+ })
896
+ .catch((err) => {
897
+ var _a;
898
+ (_a = client.logger) === null || _a === void 0 ? void 0 : _a.error(`failed to send check event for "${flag.key}": ${err}`, err);
899
+ });
900
+ }
901
+ return (_b = flag.isEnabled) !== null && _b !== void 0 ? _b : false;
902
+ },
903
+ get config() {
904
+ if (enableTracking && enableChecks) {
905
+ client._warnMissingFlagContextFields(context, flag);
906
+ void client
907
+ .sendFlagEvent({
908
+ action: "check-config",
909
+ key: flag.key,
910
+ targetingVersion: config === null || config === void 0 ? void 0 : config.targetingVersion,
911
+ evalResult: simplifiedConfig,
912
+ evalContext: context,
913
+ evalRuleResults: config === null || config === void 0 ? void 0 : config.ruleEvaluationResults,
914
+ evalMissingFields: config === null || config === void 0 ? void 0 : config.missingContextFields,
915
+ })
916
+ .catch((err) => {
917
+ var _a;
918
+ (_a = client.logger) === null || _a === void 0 ? void 0 : _a.error(`failed to send check event for "${flag.key}": ${err}`, err);
919
+ });
920
+ }
921
+ return simplifiedConfig;
922
+ },
923
+ key: flag.key,
924
+ track: () => __awaiter(this, void 0, void 0, function* () {
925
+ var _a, _b;
926
+ if (typeof ((_a = context.user) === null || _a === void 0 ? void 0 : _a.id) === "undefined") {
927
+ this.logger.warn("no user set, cannot track event");
928
+ return;
929
+ }
930
+ if (enableTracking) {
931
+ yield this.track(context.user.id, flag.key, {
932
+ companyId: (_b = context.company) === null || _b === void 0 ? void 0 : _b.id,
933
+ });
934
+ }
935
+ else {
936
+ this.logger.debug("tracking disabled, not tracking event");
937
+ }
938
+ }),
939
+ };
940
+ }
941
+ _getFlagsRemote(key, userId, companyId, additionalContext) {
942
+ return __awaiter(this, void 0, void 0, function* () {
943
+ const context = additionalContext || {};
944
+ if (userId) {
945
+ context.user = { id: userId };
946
+ }
947
+ if (companyId) {
948
+ context.company = { id: companyId };
949
+ }
950
+ const contextWithTracking = Object.assign(Object.assign({}, context), { enableTracking: true });
951
+ checkContextWithTracking(contextWithTracking);
952
+ const params = new URLSearchParams(Object.keys(context).length ? (0, flag_evaluation_1.flattenJSON)({ context }) : undefined);
953
+ if (key) {
954
+ params.append("key", key);
955
+ }
956
+ const res = yield this.get(`features/evaluated?${params}`);
957
+ if (key) {
958
+ const flag = res === null || res === void 0 ? void 0 : res.features[key];
959
+ if (!flag) {
960
+ this.logger.error(`flag ${key} not found`);
961
+ }
962
+ return this._wrapRawFlag(contextWithTracking, Object.assign({ key }, flag));
963
+ }
964
+ else {
965
+ return (res === null || res === void 0 ? void 0 : res.features)
966
+ ? Object.fromEntries(Object.entries(res === null || res === void 0 ? void 0 : res.features).map(([flagKey, flag]) => [
967
+ flagKey,
968
+ this._wrapRawFlag(contextWithTracking, flag),
969
+ ]))
970
+ : {};
971
+ }
972
+ });
973
+ }
974
+ }
975
+ exports.ReflagClient = ReflagClient;
976
+ /**
977
+ * A client bound with a specific user, company, and other context.
978
+ */
979
+ class BoundReflagClient {
980
+ /**
981
+ * (Internal) Creates a new BoundReflagClient. Use `bindClient` to create a new client bound with a specific context.
982
+ *
983
+ * @param client - The `ReflagClient` to use.
984
+ * @param options - The options for the client.
985
+ * @param options.enableTracking - Whether to enable tracking for the client.
986
+ *
987
+ * @internal
988
+ */
989
+ constructor(client, _a) {
990
+ var { enableTracking = true } = _a, context = __rest(_a, ["enableTracking"]);
991
+ this._client = client;
992
+ this._options = Object.assign({ enableTracking }, context);
993
+ checkContextWithTracking(this._options);
994
+ void this._client["syncContext"](this._options);
995
+ }
996
+ /**
997
+ * Gets the "other" context associated with the client.
998
+ *
999
+ * @returns The "other" context or `undefined` if it is not set.
1000
+ **/
1001
+ get otherContext() {
1002
+ return this._options.other;
1003
+ }
1004
+ /**
1005
+ * Gets the user associated with the client.
1006
+ *
1007
+ * @returns The user or `undefined` if it is not set.
1008
+ **/
1009
+ get user() {
1010
+ return this._options.user;
1011
+ }
1012
+ /**
1013
+ * Gets the company associated with the client.
1014
+ *
1015
+ * @returns The company or `undefined` if it is not set.
1016
+ **/
1017
+ get company() {
1018
+ return this._options.company;
1019
+ }
1020
+ /**
1021
+ * Get flags for the user/company/other context bound to this client.
1022
+ * Meant for use in serialization of flags for transferring to the client-side/browser.
1023
+ *
1024
+ * @returns Flags for the given user/company and whether each one is enabled or not
1025
+ */
1026
+ getFlags() {
1027
+ return this._client.getFlags(this._options);
1028
+ }
1029
+ /**
1030
+ * Get a specific flag for the user/company/other context bound to this client.
1031
+ * Using the `isEnabled` property sends a `check` event to Reflag.
1032
+ *
1033
+ * @param key - The key of the flag to get.
1034
+ *
1035
+ * @returns Flags for the given user/company and whether each one is enabled or not
1036
+ */
1037
+ getFlag(key) {
1038
+ return this._client.getFlag(this._options, key);
1039
+ }
1040
+ /**
1041
+ * Get remotely evaluated flag for the user/company/other context bound to this client.
1042
+ *
1043
+ * @returns Flags for the given user/company and whether each one is enabled or not
1044
+ */
1045
+ getFlagsRemote() {
1046
+ return __awaiter(this, void 0, void 0, function* () {
1047
+ const _a = this._options, { enableTracking: _, meta: __ } = _a, context = __rest(_a, ["enableTracking", "meta"]);
1048
+ return yield this._client.getFlagsRemote(undefined, undefined, context);
1049
+ });
1050
+ }
1051
+ /**
1052
+ * Get remotely evaluated flag for the user/company/other context bound to this client.
1053
+ *
1054
+ * @param key - The key of the flag to get.
1055
+ *
1056
+ * @returns Flag for the given user/company and key and whether it's enabled or not
1057
+ */
1058
+ getFlagRemote(key) {
1059
+ return __awaiter(this, void 0, void 0, function* () {
1060
+ const _a = this._options, { enableTracking: _, meta: __ } = _a, context = __rest(_a, ["enableTracking", "meta"]);
1061
+ return yield this._client.getFlagRemote(key, undefined, undefined, context);
1062
+ });
1063
+ }
1064
+ /**
1065
+ * Track an event in Reflag.
1066
+ *
1067
+ * @param event - The event to track.
1068
+ * @param options - The options for the event.
1069
+ * @param options.attributes - The attributes of the event (optional).
1070
+ * @param options.meta - The meta context associated with tracking (optional).
1071
+ * @param options.companyId - Optional company ID for the event (optional).
1072
+ *
1073
+ * @throws An error if the event is invalid or the options are invalid.
1074
+ */
1075
+ track(event, options) {
1076
+ return __awaiter(this, void 0, void 0, function* () {
1077
+ var _a, _b, _c, _d;
1078
+ (0, utils_1.ok)(options === undefined || (0, utils_1.isObject)(options), "options must be an object");
1079
+ checkMeta(options === null || options === void 0 ? void 0 : options.meta);
1080
+ const userId = (_a = this._options.user) === null || _a === void 0 ? void 0 : _a.id;
1081
+ if (!userId) {
1082
+ (_b = this._client.logger) === null || _b === void 0 ? void 0 : _b.warn("no user set, cannot track event");
1083
+ return;
1084
+ }
1085
+ if (!this._options.enableTracking) {
1086
+ (_c = this._client.logger) === null || _c === void 0 ? void 0 : _c.debug("tracking disabled for this bound client, not tracking event");
1087
+ return;
1088
+ }
1089
+ yield this._client.track(userId, event, (options === null || options === void 0 ? void 0 : options.companyId)
1090
+ ? options
1091
+ : Object.assign(Object.assign({}, options), { companyId: (_d = this._options.company) === null || _d === void 0 ? void 0 : _d.id }));
1092
+ });
1093
+ }
1094
+ /**
1095
+ * Create a new client bound with the additional context.
1096
+ * Note: This performs a shallow merge for user/company/other individually.
1097
+ *
1098
+ * @param context - The context to bind the client to.
1099
+ * @param context.user - The user to bind the client to.
1100
+ * @param context.company - The company to bind the client to.
1101
+ * @param context.other - The other context to bind the client to.
1102
+ * @param context.enableTracking - Whether to enable tracking for the client.
1103
+ * @param context.meta - The meta context to bind the client to.
1104
+ *
1105
+ * @returns new client bound with the additional context
1106
+ */
1107
+ bindClient({ user, company, other, enableTracking, meta, }) {
1108
+ // merge new context into existing
1109
+ const boundConfig = Object.assign(Object.assign({}, this._options), { user: user ? Object.assign(Object.assign({}, this._options.user), user) : undefined, company: company ? Object.assign(Object.assign({}, this._options.company), company) : undefined, other: Object.assign(Object.assign({}, this._options.other), other), enableTracking: enableTracking !== null && enableTracking !== void 0 ? enableTracking : this._options.enableTracking, meta: meta !== null && meta !== void 0 ? meta : this._options.meta });
1110
+ return new BoundReflagClient(this._client, boundConfig);
1111
+ }
1112
+ /**
1113
+ * Flushes the batch buffer.
1114
+ */
1115
+ flush() {
1116
+ return __awaiter(this, void 0, void 0, function* () {
1117
+ yield this._client.flush();
1118
+ });
1119
+ }
1120
+ }
1121
+ exports.BoundReflagClient = BoundReflagClient;
1122
+ function checkMeta(meta) {
1123
+ (0, utils_1.ok)(typeof meta === "undefined" || (0, utils_1.isObject)(meta), "meta must be an object if given");
1124
+ (0, utils_1.ok)((meta === null || meta === void 0 ? void 0 : meta.active) === undefined || typeof (meta === null || meta === void 0 ? void 0 : meta.active) === "boolean", "meta.active must be a boolean if given");
1125
+ }
1126
+ function checkContextWithTracking(context) {
1127
+ var _a, _b;
1128
+ (0, utils_1.ok)((0, utils_1.isObject)(context), "context must be an object");
1129
+ (0, utils_1.ok)(typeof context.user === "undefined" || (0, utils_1.isObject)(context.user), "user must be an object if given");
1130
+ if (typeof ((_a = context.user) === null || _a === void 0 ? void 0 : _a.id) !== "undefined") {
1131
+ (0, utils_1.idOk)(context.user.id, "user.id");
1132
+ }
1133
+ (0, utils_1.ok)(typeof context.company === "undefined" || (0, utils_1.isObject)(context.company), "company must be an object if given");
1134
+ if (typeof ((_b = context.company) === null || _b === void 0 ? void 0 : _b.id) !== "undefined") {
1135
+ (0, utils_1.idOk)(context.company.id, "company.id");
1136
+ }
1137
+ (0, utils_1.ok)(context.other === undefined || (0, utils_1.isObject)(context.other), "other must be an object if given");
1138
+ (0, utils_1.ok)(typeof context.enableTracking === "boolean", "enableTracking must be a boolean");
1139
+ checkMeta(context.meta);
1140
+ }
1141
+ //# sourceMappingURL=client.js.map