Skip to content

Commit bdeab03

Browse files
committed
refactor config
1 parent 7b4417b commit bdeab03

File tree

2 files changed

+76
-88
lines changed

2 files changed

+76
-88
lines changed

packages/node-sdk/src/client.ts

Lines changed: 60 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -104,27 +104,32 @@ type BulkEvent =
104104
**/
105105
export class BucketClient {
106106
private _config: {
107-
logger?: Logger;
108107
apiBaseUrl: string;
109-
httpClient: HttpClient;
110108
refetchInterval: number;
111109
staleWarningInterval: number;
112110
headers: Record<string, string>;
113111
fallbackFeatures?: Record<keyof TypedFeatures, RawFeature>;
114-
featuresCache: Cache<FeaturesAPIResponse>;
115-
batchBuffer: BatchBuffer<BulkEvent>;
116112
featureOverrides: FeatureOverridesFn;
117-
rateLimiter: ReturnType<typeof newRateLimiter>;
118113
offline: boolean;
119114
fetchFeatures: boolean;
120115
configFile?: string;
121116
};
117+
httpClient: HttpClient;
118+
119+
private featuresCache: Cache<FeaturesAPIResponse>;
120+
private batchBuffer: BatchBuffer<BulkEvent>;
121+
private rateLimiter: ReturnType<typeof newRateLimiter>;
122+
123+
/**
124+
* Gets the logger associated with the client.
125+
*/
126+
public readonly logger: Logger;
122127

123128
private _initialize = once(async () => {
124129
if (!this._config.offline && this._config.fetchFeatures) {
125-
await this._config.featuresCache.refresh();
130+
await this.featuresCache.refresh();
126131
}
127-
this._config.logger?.info("Bucket initialized");
132+
this.logger.info("Bucket initialized");
128133
});
129134

130135
/**
@@ -202,7 +207,7 @@ export class BucketClient {
202207
}
203208

204209
// use the supplied logger or apply the log level to the console logger
205-
const logger = options.logger
210+
this.logger = options.logger
206211
? options.logger
207212
: applyLogLevel(
208213
decorateLogger(BUCKET_LOG_PREFIX, console),
@@ -245,26 +250,28 @@ export class BucketClient {
245250
)
246251
: undefined;
247252

253+
this.rateLimiter = newRateLimiter(
254+
FEATURE_EVENT_RATE_LIMITER_WINDOW_SIZE_MS,
255+
);
256+
this.httpClient = options.httpClient || fetchClient;
257+
this.batchBuffer = new BatchBuffer<BulkEvent>({
258+
...options?.batchOptions,
259+
flushHandler: (items) => this.sendBulkEvents(items),
260+
logger: this.logger,
261+
});
262+
248263
this._config = {
249-
logger,
250264
offline,
251265
apiBaseUrl: (config.apiBaseUrl ?? config.host) || API_BASE_URL,
252266
headers: {
253267
"Content-Type": "application/json",
254268
[SDK_VERSION_HEADER_NAME]: SDK_VERSION,
255269
["Authorization"]: `Bearer ${config.secretKey}`,
256270
},
257-
rateLimiter: newRateLimiter(FEATURE_EVENT_RATE_LIMITER_WINDOW_SIZE_MS),
258-
httpClient: options.httpClient || fetchClient,
259271
refetchInterval: FEATURES_REFETCH_MS,
260272
fetchFeatures: options.fetchFeatures ?? true,
261273
staleWarningInterval: FEATURES_REFETCH_MS * 5,
262274
fallbackFeatures: fallbackFeatures,
263-
batchBuffer: new BatchBuffer<BulkEvent>({
264-
...options?.batchOptions,
265-
flushHandler: (items) => this.sendBulkEvents(items),
266-
logger,
267-
}),
268275
featureOverrides:
269276
typeof config.featureOverrides === "function"
270277
? config.featureOverrides
@@ -279,10 +286,10 @@ export class BucketClient {
279286
this._config.apiBaseUrl += "/";
280287
}
281288

282-
this._config.featuresCache = cache<FeaturesAPIResponse>(
289+
this.featuresCache = cache<FeaturesAPIResponse>(
283290
this._config.refetchInterval,
284291
this._config.staleWarningInterval,
285-
this._config.logger,
292+
this.logger,
286293
async () => {
287294
const res = await this.get<FeaturesAPIResponse>("features");
288295

@@ -295,15 +302,6 @@ export class BucketClient {
295302
);
296303
}
297304

298-
/**
299-
* Gets the logger associated with the client.
300-
*
301-
* @returns The logger or `undefined` if it is not set.
302-
**/
303-
public get logger() {
304-
return this._config.logger;
305-
}
306-
307305
/**
308306
* Sets the feature overrides.
309307
*
@@ -369,10 +367,8 @@ export class BucketClient {
369367
return;
370368
}
371369

372-
if (
373-
this._config.rateLimiter.isAllowed(hashObject({ ...options, userId }))
374-
) {
375-
await this._config.batchBuffer.add({
370+
if (this.rateLimiter.isAllowed(hashObject({ ...options, userId }))) {
371+
await this.batchBuffer.add({
376372
type: "user",
377373
userId,
378374
attributes: options?.attributes,
@@ -415,10 +411,8 @@ export class BucketClient {
415411
return;
416412
}
417413

418-
if (
419-
this._config.rateLimiter.isAllowed(hashObject({ ...options, companyId }))
420-
) {
421-
await this._config.batchBuffer.add({
414+
if (this.rateLimiter.isAllowed(hashObject({ ...options, companyId }))) {
415+
await this.batchBuffer.add({
422416
type: "company",
423417
companyId,
424418
userId: options?.userId,
@@ -461,7 +455,7 @@ export class BucketClient {
461455
return;
462456
}
463457

464-
await this._config.batchBuffer.add({
458+
await this.batchBuffer.add({
465459
type: "event",
466460
event,
467461
companyId: options?.companyId,
@@ -480,7 +474,8 @@ export class BucketClient {
480474
* Useful when loading feature definitions from a file or other source.
481475
**/
482476
public bootstrapFeatureDefinitions(features: FeaturesAPIResponse) {
483-
this._config.featuresCache.set(features);
477+
this.featuresCache.set(features);
478+
this.logger.info("Bootstrapped feature definitions");
484479
}
485480

486481
/**
@@ -509,7 +504,7 @@ export class BucketClient {
509504
return;
510505
}
511506

512-
await this._config.batchBuffer.flush();
507+
await this.batchBuffer.flush();
513508
}
514509

515510
/**
@@ -519,7 +514,7 @@ export class BucketClient {
519514
* @returns The features definitions.
520515
*/
521516
public async getFeatureDefinitions(): Promise<FeatureDefinition[]> {
522-
const features = this.getFeaturesCache().get()?.features || [];
517+
const features = this.featuresCache.get()?.features || [];
523518
return features.map((f) => ({
524519
key: f.key,
525520
description: f.description,
@@ -661,26 +656,24 @@ export class BucketClient {
661656

662657
const url = this.buildUrl(path);
663658
try {
664-
const response = await this._config.httpClient.post<
665-
TBody,
666-
{ success: boolean }
667-
>(url, this._config.headers, body);
659+
const response = await this.httpClient.post<TBody, { success: boolean }>(
660+
url,
661+
this._config.headers,
662+
body,
663+
);
668664

669-
this._config.logger?.debug(`post request to "${url}"`, response);
665+
this.logger.debug(`post request to "${url}"`, response);
670666

671667
if (!response.ok || !isObject(response.body) || !response.body.success) {
672-
this._config.logger?.warn(
668+
this.logger.warn(
673669
`invalid response received from server for "${url}"`,
674670
response,
675671
);
676672
return false;
677673
}
678674
return true;
679675
} catch (error) {
680-
this._config.logger?.error(
681-
`post request to "${url}" failed with error`,
682-
error,
683-
);
676+
this.logger.error(`post request to "${url}" failed with error`, error);
684677
return false;
685678
}
686679
}
@@ -698,14 +691,14 @@ export class BucketClient {
698691

699692
try {
700693
const url = this.buildUrl(path);
701-
const response = await this._config.httpClient.get<
694+
const response = await this.httpClient.get<
702695
TResponse & { success: boolean }
703696
>(url, this._config.headers);
704697

705-
this._config.logger?.debug(`get request to "${url}"`, response);
698+
this.logger.debug(`get request to "${url}"`, response);
706699

707700
if (!response.ok || !isObject(response.body) || !response.body.success) {
708-
this._config.logger?.warn(
701+
this.logger.warn(
709702
`invalid response received from server for "${url}"`,
710703
response,
711704
);
@@ -716,10 +709,7 @@ export class BucketClient {
716709
const { success: _, ...result } = response.body;
717710
return result as TResponse;
718711
} catch (error) {
719-
this._config.logger?.error(
720-
`get request to "${path}" failed with error`,
721-
error,
722-
);
712+
this.logger.error(`get request to "${path}" failed with error`, error);
723713
return undefined;
724714
}
725715
}
@@ -812,7 +802,7 @@ export class BucketClient {
812802
}
813803

814804
if (
815-
!this._config.rateLimiter.isAllowed(
805+
!this.rateLimiter.isAllowed(
816806
hashObject({
817807
action: event.action,
818808
key: event.key,
@@ -825,7 +815,7 @@ export class BucketClient {
825815
return;
826816
}
827817

828-
await this._config.batchBuffer.add({
818+
await this.batchBuffer.add({
829819
type: "feature-flag-event",
830820
action: event.action,
831821
key: event.key,
@@ -850,9 +840,7 @@ export class BucketClient {
850840
*/
851841
private async syncContext(options: ContextWithTracking) {
852842
if (!options.enableTracking) {
853-
this._config.logger?.debug(
854-
"tracking disabled, not updating user/company",
855-
);
843+
this.logger.debug("tracking disabled, not updating user/company");
856844

857845
return;
858846
}
@@ -904,7 +892,7 @@ export class BucketClient {
904892
(acc, { config, ...feature }) => {
905893
if (
906894
feature.missingContextFields?.length &&
907-
this._config.rateLimiter.isAllowed(
895+
this.rateLimiter.isAllowed(
908896
hashObject({
909897
featureKey: feature.key,
910898
missingContextFields: feature.missingContextFields,
@@ -917,7 +905,7 @@ export class BucketClient {
917905

918906
if (
919907
config?.missingContextFields?.length &&
920-
this._config.rateLimiter.isAllowed(
908+
this.rateLimiter.isAllowed(
921909
hashObject({
922910
featureKey: feature.key,
923911
configKey: config.key,
@@ -935,7 +923,7 @@ export class BucketClient {
935923
);
936924

937925
if (Object.keys(report).length > 0) {
938-
this._config.logger?.warn(
926+
this.logger.warn(
939927
`feature/remote config targeting rules might not be correctly evaluated due to missing context fields.`,
940928
report,
941929
);
@@ -953,9 +941,9 @@ export class BucketClient {
953941
if (this._config.offline) {
954942
featureDefinitions = [];
955943
} else {
956-
const fetchedFeatures = this.getFeaturesCache().get();
944+
const fetchedFeatures = this.featuresCache.get();
957945
if (!fetchedFeatures) {
958-
this._config.logger?.warn(
946+
this.logger.warn(
959947
"failed to use feature definitions, there are none cached yet. Using fallback features.",
960948
);
961949
return this._config.fallbackFeatures || {};
@@ -1059,7 +1047,7 @@ export class BucketClient {
10591047
)
10601048
.filter(Boolean);
10611049
if (failed.length > 0) {
1062-
this._config.logger?.error(`failed to queue some evaluate events.`, {
1050+
this.logger.error(`failed to queue some evaluate events.`, {
10631051
errors: failed,
10641052
});
10651053
}
@@ -1129,7 +1117,7 @@ export class BucketClient {
11291117
evalMissingFields: feature.missingContextFields,
11301118
})
11311119
.catch((err) => {
1132-
client._config.logger?.error(
1120+
client.logger?.error(
11331121
`failed to send check event for "${feature.key}": ${err}`,
11341122
err,
11351123
);
@@ -1150,7 +1138,7 @@ export class BucketClient {
11501138
evalMissingFields: config?.missingContextFields,
11511139
})
11521140
.catch((err) => {
1153-
client._config.logger?.error(
1141+
client.logger?.error(
11541142
`failed to send check event for "${feature.key}": ${err}`,
11551143
err,
11561144
);
@@ -1161,7 +1149,7 @@ export class BucketClient {
11611149
key: feature.key,
11621150
track: async () => {
11631151
if (typeof context.user?.id === "undefined") {
1164-
this._config.logger?.warn("no user set, cannot track event");
1152+
this.logger.warn("no user set, cannot track event");
11651153
return;
11661154
}
11671155

@@ -1170,7 +1158,7 @@ export class BucketClient {
11701158
companyId: context.company?.id,
11711159
});
11721160
} else {
1173-
this._config.logger?.debug("tracking disabled, not tracking event");
1161+
this.logger.debug("tracking disabled, not tracking event");
11741162
}
11751163
},
11761164
};
@@ -1224,7 +1212,7 @@ export class BucketClient {
12241212
}) || [],
12251213
);
12261214
} else {
1227-
this._config.logger?.error("failed to fetch evaluated features");
1215+
this.logger.error("failed to fetch evaluated features");
12281216
return {};
12291217
}
12301218
}

0 commit comments

Comments
 (0)