Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/node-sdk/examples/express/reflagConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"overrides": {
"show-todos": true,
"create-todos": true
}
}
2 changes: 1 addition & 1 deletion packages/node-sdk/src/batch-buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default class BatchBuffer<T> {
count: flushingBuffer.length,
});
} catch (error) {
this.logger?.error("flush of buffered items failed; discarding items", {
this.logger?.warn("flush of buffered items failed; discarding items", {
error,
count: flushingBuffer.length,
});
Expand Down
70 changes: 53 additions & 17 deletions packages/node-sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,38 @@ function createFlagsFallbackSnapshot(
};
}

function formatFlagsFallbackAge(savedAt: string): string | undefined {
const savedAtMs = Date.parse(savedAt);
if (!Number.isFinite(savedAtMs)) {
return undefined;
}

const ageMs = Math.max(0, Date.now() - savedAtMs);
const minuteMs = 60_000;
const hourMs = 60 * minuteMs;
const dayMs = 24 * hourMs;

if (ageMs < minuteMs) {
return "<1m";
}

if (ageMs < hourMs) {
return `${Math.floor(ageMs / minuteMs)}m`;
}

if (ageMs < dayMs) {
return `${Math.floor(ageMs / hourMs)}h`;
}

return `${Math.floor(ageMs / dayMs)}d`;
}

function createErrorWithCause(message: string, cause: unknown): Error {
const error = new Error(message) as Error & { cause?: unknown };
error.cause = cause;
return error;
}

/**
* The SDK client.
*
Expand Down Expand Up @@ -427,7 +459,6 @@ export class ReflagClient {
this._config.flagsFetchRetries,
);
if (!isObject(res) || !Array.isArray(res?.features)) {
this.logger.warn("flags cache: invalid response", res);
return await this.loadFlagsFallbackDefinitions();
}

Expand Down Expand Up @@ -468,6 +499,9 @@ export class ReflagClient {
);

if (!snapshot) {
this.logger.warn(
"remote flags unavailable, no fallback flags found in flagsFallbackProvider",
);
return undefined;
}

Expand All @@ -476,9 +510,12 @@ export class ReflagClient {
return undefined;
}

this.logger.warn("using flag definitions from flagsFallbackProvider", {
savedAt: snapshot.savedAt,
});
const fallbackAge = formatFlagsFallbackAge(snapshot.savedAt);
this.logger.warn(
fallbackAge
? `remote flags unavailable, using fallback flags fetched ${fallbackAge} ago (${snapshot.savedAt})`
: `remote flags unavailable, using fallback flags (${snapshot.savedAt})`,
);

return compileFlagDefinitions(snapshot.flags);
} catch (error) {
Expand Down Expand Up @@ -977,8 +1014,6 @@ export class ReflagClient {
* @param path - The path to send the request to.
* @param body - The body of the request.
*
* @returns A boolean indicating if the request was successful.
*
* @throws An error if the path or body is invalid.
**/
private async post<TBody>(path: string, body: TBody) {
Expand All @@ -996,16 +1031,16 @@ export class ReflagClient {
this.logger.debug(`post request to "${url}"`, response);

if (!response.ok || !isObject(response.body) || !response.body.success) {
this.logger.warn(
throw createErrorWithCause(
`invalid response received from server for "${url}"`,
JSON.stringify(response),
);
return false;
}
return true;
} catch (error) {
this.logger.error(`post request to "${url}" failed with error`, error);
return false;
throw createErrorWithCause(
`post request to "${url}" failed with error`,
error,
);
}
}

Expand Down Expand Up @@ -1043,15 +1078,15 @@ export class ReflagClient {
const { success: _, ...result } = response.body;
return result as TResponse;
},
() => {
this.logger.warn("failed to fetch flags, will retry");
(error) => {
this.logger.warn("failed to fetch flags, will retry", error);
},
retries,
1000,
10000,
);
} catch (error) {
this.logger.error(
this.logger.debug(
`get request to "${path}" failed with error after ${retries} retries`,
error,
);
Expand All @@ -1072,9 +1107,10 @@ export class ReflagClient {
"events must be a non-empty array",
);

const sent = await this.post("bulk", events);
if (!sent) {
throw new Error("Failed to send bulk events");
try {
await this.post("bulk", events);
} catch (error) {
throw createErrorWithCause("failed to send bulk events", error);
}
}

Expand Down
Loading
Loading