Skip to content

Commit 1be9383

Browse files
authored
Merge pull request #7 from ReforgeHQ/mfaga-match-prefab
feat: add formal support for extract/hydrate of reforge config
2 parents 09a64aa + d241f14 commit 1be9383

File tree

4 files changed

+77
-29
lines changed

4 files changed

+77
-29
lines changed

README.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,21 @@ if (reforge.isEnabled('cool-feature') {
6565
setTimeout(ping, reforge.get('ping-delay'));
6666
```
6767
68-
Here's an explanation of each property
69-
70-
| property | example | purpose |
71-
| --------------- | ------------------------------------- | -------------------------------------------------------------------------------------------- |
72-
| `isEnabled` | `reforge.isEnabled("new-logo")` | returns a boolean (default `false`) if a feature is enabled based on the current context |
73-
| `get` | `reforge.get('retry-count')` | returns the value of a flag or config evaluated in the current context |
74-
| `getDuration` | `reforge.getDuration('http.timeout')` | returns a duration object `{seconds: number, ms: number}` |
75-
| `loaded` | `if (reforge.loaded) { ... }` | a boolean indicating whether reforge content has loaded |
76-
| `shouldLog` | `if (reforge.shouldLog(...)) {` | returns a boolean indicating whether the proposed log level is valid for the current context |
77-
| `poll` | `reforge.poll({frequencyInMs})` | starts polling every `frequencyInMs` ms. |
78-
| `stopPolling` | `reforge.stopPolling()` | stops the polling process |
79-
| `context` | `reforge.context` | get the current context (after `init()`). |
80-
| `updateContext` | `reforge.updateContext(newContext)` | update the context and refetch. Pass `false` as a second argument to skip refetching |
68+
## Client API
69+
70+
| property | example | purpose |
71+
| --------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- |
72+
| `isEnabled` | `reforge.isEnabled("new-logo")` | returns a boolean (default `false`) if a feature is enabled based on the current context |
73+
| `get` | `reforge.get('retry-count')` | returns the value of a flag or config evaluated in the current context |
74+
| `getDuration` | `reforge.getDuration('http.timeout')` | returns a duration object `{seconds: number, ms: number}` |
75+
| `loaded` | `if (reforge.loaded) { ... }` | a boolean indicating whether reforge content has loaded |
76+
| `shouldLog` | `if (reforge.shouldLog(...)) {` | returns a boolean indicating whether the proposed log level is valid for the current context |
77+
| `poll` | `reforge.poll({frequencyInMs})` | starts polling every `frequencyInMs` ms. |
78+
| `stopPolling` | `reforge.stopPolling()` | stops the polling process |
79+
| `context` | `reforge.context` | get the current context (after `init()`). |
80+
| `updateContext` | `reforge.updateContext(newContext)` | update the context and refetch. Pass `false` as a second argument to skip refetching |
81+
| `extract` | `reforge.extract()` | returns the current config as a plain object of key, config value pairs |
82+
| `hydrate` | `reforge.hydrate(configurationObject)` | sets the current config based on a plain object of key, config value pairs |
8183
8284
## `shouldLog()`
8385

src/afterEvaluationCallback.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe("afterEvaluationCallback", () => {
2525
const reforge = new Reforge();
2626
reforge.afterEvaluationCallback = callback;
2727

28-
reforge.setConfig({ turbo: 2.5 });
28+
reforge.hydrate({ turbo: 2.5 });
2929

3030
expect(callback).not.toHaveBeenCalled();
3131

@@ -46,7 +46,7 @@ describe("afterEvaluationCallback", () => {
4646

4747
reforge.afterEvaluationCallback = callback;
4848

49-
reforge.setConfig({ turbo: 2.5 });
49+
reforge.hydrate({ turbo: 2.5 });
5050

5151
expect(callback).not.toHaveBeenCalled();
5252

@@ -72,7 +72,7 @@ describe("afterEvaluationCallback", () => {
7272
await waitForAsyncCall();
7373
expect(callback).toHaveBeenCalledTimes(0);
7474

75-
reforge.setConfig({ foo: true });
75+
reforge.hydrate({ foo: true });
7676

7777
expect(reforge.isEnabled("foo")).toBe(true);
7878

@@ -94,7 +94,7 @@ describe("afterEvaluationCallback", () => {
9494
await waitForAsyncCall();
9595
expect(callback).toHaveBeenCalledTimes(0);
9696

97-
reforge.setConfig({ foo: true });
97+
reforge.hydrate({ foo: true });
9898

9999
expect(reforge.isEnabled("foo")).toBe(true);
100100

src/reforge.test.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,11 @@ describe("poll", () => {
213213
});
214214
});
215215

216-
describe("setConfig", () => {
216+
describe("hydrate", () => {
217217
it("works when types are not provided", () => {
218218
expect(reforge.configs).toEqual({});
219219

220-
reforge.setConfig({
220+
reforge.hydrate({
221221
turbo: 2.5,
222222
foo: true,
223223
jsonExample: { foo: "bar", baz: 123 },
@@ -287,7 +287,7 @@ describe("bootstrapping", () => {
287287
});
288288

289289
test("get", () => {
290-
reforge.setConfig({
290+
reforge.hydrate({
291291
evaluations: {
292292
turbo: { value: { double: 2.5 } },
293293
durationExample: { value: { duration: { millis: 1884000, definition: "PT1884S" } } },
@@ -310,7 +310,7 @@ test("get", () => {
310310
});
311311

312312
test("getDuration", () => {
313-
reforge.setConfig({
313+
reforge.hydrate({
314314
evaluations: {
315315
turbo: { value: { double: 2.5 } },
316316
durationExample: {
@@ -333,11 +333,33 @@ test("isEnabled", () => {
333333
// it is false when no config is loaded
334334
expect(reforge.isEnabled("foo")).toBe(false);
335335

336-
reforge.setConfig({ foo: true });
336+
reforge.hydrate({ foo: true });
337337

338338
expect(reforge.isEnabled("foo")).toBe(true);
339339
});
340340

341+
describe("extract", () => {
342+
it("correctly extracts configuration values", () => {
343+
reforge.hydrate({
344+
turbo: 2.5,
345+
foo: true,
346+
jsonExample: { foo: "bar", baz: 123 },
347+
});
348+
349+
const extracted = reforge.extract();
350+
expect(extracted).toEqual({
351+
turbo: 2.5,
352+
foo: true,
353+
jsonExample: { foo: "bar", baz: 123 },
354+
});
355+
});
356+
357+
it("returns an empty object when no configs are set", () => {
358+
const extracted = reforge.extract();
359+
expect(extracted).toEqual({});
360+
});
361+
});
362+
341363
describe("shouldLog", () => {
342364
test("compares against the default level where there is no value", () => {
343365
expect(
@@ -358,7 +380,7 @@ describe("shouldLog", () => {
358380
});
359381

360382
test("compares against the value when present", () => {
361-
reforge.setConfig({
383+
reforge.hydrate({
362384
"log-level.example": "INFO",
363385
});
364386

@@ -382,7 +404,7 @@ describe("shouldLog", () => {
382404
test("traverses the hierarchy to get the closest level for the loggerName", () => {
383405
const loggerName = "some.test.name.with.more.levels";
384406

385-
reforge.setConfig({
407+
reforge.hydrate({
386408
"log-level.some.test.name": "TRACE",
387409
"log-level.some.test": "DEBUG",
388410
"log-level.irrelevant": "ERROR",
@@ -422,7 +444,7 @@ describe("shouldLog", () => {
422444
});
423445

424446
it("can use the root log level setting if nothing is found in the hierarchy", () => {
425-
reforge.setConfig({
447+
reforge.hydrate({
426448
"log-level": "INFO",
427449
});
428450

src/reforge.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,24 @@ export class Reforge {
135135
return this.load();
136136
}
137137

138-
get configs(): { [key: string]: Config } {
138+
extract(): Record<string, Config["value"]> {
139+
return Object.entries(this._configs).reduce(
140+
(agg, [key, value]) => ({
141+
...agg,
142+
[key]: value.value,
143+
}),
144+
{} as Record<string, Config["value"]>
145+
);
146+
}
147+
148+
hydrate(rawValues: RawConfigWithoutTypes | EvaluationPayload): void {
149+
this.setConfigPrivate(rawValues);
150+
}
151+
152+
get configs(): Record<string, Config> {
153+
// eslint-disable-next-line no-console
154+
console.warn("\x1b[33m%s\x1b[0m", 'Deprecated: Use "prefab.extract" instead');
155+
139156
return this._configs;
140157
}
141158

@@ -175,7 +192,7 @@ export class Reforge {
175192
const bootstrapContext = new Context(reforgeBootstrap.context);
176193

177194
if (this.context.equals(bootstrapContext)) {
178-
this.setConfig({ evaluations: reforgeBootstrap.evaluations });
195+
this.setConfigPrivate({ evaluations: reforgeBootstrap.evaluations });
179196
return Promise.resolve();
180197
}
181198
}
@@ -186,7 +203,7 @@ export class Reforge {
186203
return this.loader
187204
.load()
188205
.then((rawValues: any) => {
189-
this.setConfig(rawValues as EvaluationPayload);
206+
this.setConfigPrivate(rawValues as EvaluationPayload);
190207
})
191208
.finally(() => {
192209
if (this.pollStatus.status === "running") {
@@ -255,6 +272,13 @@ export class Reforge {
255272
}
256273

257274
setConfig(rawValues: RawConfigWithoutTypes | EvaluationPayload) {
275+
// eslint-disable-next-line no-console
276+
console.warn("\x1b[33m%s\x1b[0m", 'Deprecated: Use "prefab.hydrate" instead');
277+
278+
this.setConfigPrivate(rawValues);
279+
}
280+
281+
private setConfigPrivate(rawValues: RawConfigWithoutTypes | EvaluationPayload) {
258282
this._configs = Config.digest(rawValues);
259283
this.loaded = true;
260284
}
@@ -275,7 +299,7 @@ export class Reforge {
275299
return undefined;
276300
}
277301

278-
const config = this.configs[key];
302+
const config = this._configs[key];
279303

280304
const value = config?.value;
281305

0 commit comments

Comments
 (0)