diff --git a/README.md b/README.md index 2a77222..4f69a32 100644 --- a/README.md +++ b/README.md @@ -65,19 +65,21 @@ if (prefab.isEnabled('cool-feature') { setTimeout(ping, prefab.get('ping-delay')); ``` -Here's an explanation of each property - -| property | example | purpose | -| --------------- | ------------------------------------ | -------------------------------------------------------------------------------------------- | -| `isEnabled` | `prefab.isEnabled("new-logo")` | returns a boolean (default `false`) if a feature is enabled based on the current context | -| `get` | `prefab.get('retry-count')` | returns the value of a flag or config evaluated in the current context | -| `getDuration` | `prefab.getDuration('http.timeout')` | returns a duration object `{seconds: number, ms: number}` | -| `loaded` | `if (prefab.loaded) { ... }` | a boolean indicating whether prefab content has loaded | -| `shouldLog` | `if (prefab.shouldLog(...)) {` | returns a boolean indicating whether the proposed log level is valid for the current context | -| `poll` | `prefab.poll({frequencyInMs})` | starts polling every `frequencyInMs` ms. | -| `stopPolling` | `prefab.stopPolling()` | stops the polling process | -| `context` | `prefab.context` | get the current context (after `init()`). | -| `updateContext` | `prefab.updateContext(newContext)` | update the context and refetch. Pass `false` as a second argument to skip refetching | +## Client API + +| property | example | purpose | +| --------------- | ------------------------------------- | -------------------------------------------------------------------------------------------- | +| `isEnabled` | `prefab.isEnabled("new-logo")` | returns a boolean (default `false`) if a feature is enabled based on the current context | +| `get` | `prefab.get('retry-count')` | returns the value of a flag or config evaluated in the current context | +| `getDuration` | `prefab.getDuration('http.timeout')` | returns a duration object `{seconds: number, ms: number}` | +| `loaded` | `if (prefab.loaded) { ... }` | a boolean indicating whether prefab content has loaded | +| `shouldLog` | `if (prefab.shouldLog(...)) {` | returns a boolean indicating whether the proposed log level is valid for the current context | +| `poll` | `prefab.poll({frequencyInMs})` | starts polling every `frequencyInMs` ms. | +| `stopPolling` | `prefab.stopPolling()` | stops the polling process | +| `context` | `prefab.context` | get the current context (after `init()`). | +| `updateContext` | `prefab.updateContext(newContext)` | update the context and refetch. Pass `false` as a second argument to skip refetching | +| `extract` | `prefab.extract()` | returns the current config as a plain object of key, config value pairs | +| `hydrate` | `prefab.hydrate(configurationObject)` | sets the current config based on a plain object of key, config value pairs | ## `shouldLog()` diff --git a/src/afterEvaluationCallback.test.ts b/src/afterEvaluationCallback.test.ts index bdafa5e..f7b5830 100644 --- a/src/afterEvaluationCallback.test.ts +++ b/src/afterEvaluationCallback.test.ts @@ -25,7 +25,7 @@ describe("afterEvaluationCallback", () => { const prefab = new Prefab(); prefab.afterEvaluationCallback = callback; - prefab.setConfig({ turbo: 2.5 }); + prefab.hydrate({ turbo: 2.5 }); expect(callback).not.toHaveBeenCalled(); @@ -46,7 +46,7 @@ describe("afterEvaluationCallback", () => { prefab.afterEvaluationCallback = callback; - prefab.setConfig({ turbo: 2.5 }); + prefab.hydrate({ turbo: 2.5 }); expect(callback).not.toHaveBeenCalled(); @@ -72,7 +72,7 @@ describe("afterEvaluationCallback", () => { await waitForAsyncCall(); expect(callback).toHaveBeenCalledTimes(0); - prefab.setConfig({ foo: true }); + prefab.hydrate({ foo: true }); expect(prefab.isEnabled("foo")).toBe(true); @@ -94,7 +94,7 @@ describe("afterEvaluationCallback", () => { await waitForAsyncCall(); expect(callback).toHaveBeenCalledTimes(0); - prefab.setConfig({ foo: true }); + prefab.hydrate({ foo: true }); expect(prefab.isEnabled("foo")).toBe(true); diff --git a/src/prefab.test.ts b/src/prefab.test.ts index d5f7a84..6c49157 100644 --- a/src/prefab.test.ts +++ b/src/prefab.test.ts @@ -211,11 +211,11 @@ describe("poll", () => { }); }); -describe("setConfig", () => { +describe("hydrate", () => { it("works when types are not provided", () => { expect(prefab.configs).toEqual({}); - prefab.setConfig({ + prefab.hydrate({ turbo: 2.5, foo: true, jsonExample: { foo: "bar", baz: 123 }, @@ -285,7 +285,7 @@ describe("bootstrapping", () => { }); test("get", () => { - prefab.setConfig({ + prefab.hydrate({ evaluations: { turbo: { value: { double: 2.5 } }, durationExample: { value: { duration: { millis: 1884000, definition: "PT1884S" } } }, @@ -308,7 +308,7 @@ test("get", () => { }); test("getDuration", () => { - prefab.setConfig({ + prefab.hydrate({ evaluations: { turbo: { value: { double: 2.5 } }, durationExample: { @@ -331,11 +331,33 @@ test("isEnabled", () => { // it is false when no config is loaded expect(prefab.isEnabled("foo")).toBe(false); - prefab.setConfig({ foo: true }); + prefab.hydrate({ foo: true }); expect(prefab.isEnabled("foo")).toBe(true); }); +describe("extract", () => { + it("correctly extracts configuration values", () => { + prefab.hydrate({ + turbo: 2.5, + foo: true, + jsonExample: { foo: "bar", baz: 123 }, + }); + + const extracted = prefab.extract(); + expect(extracted).toEqual({ + turbo: 2.5, + foo: true, + jsonExample: { foo: "bar", baz: 123 }, + }); + }); + + it("returns an empty object when no configs are set", () => { + const extracted = prefab.extract(); + expect(extracted).toEqual({}); + }); +}); + describe("shouldLog", () => { test("compares against the default level where there is no value", () => { expect( @@ -356,7 +378,7 @@ describe("shouldLog", () => { }); test("compares against the value when present", () => { - prefab.setConfig({ + prefab.hydrate({ "log-level.example": "INFO", }); @@ -380,7 +402,7 @@ describe("shouldLog", () => { test("traverses the hierarchy to get the closest level for the loggerName", () => { const loggerName = "some.test.name.with.more.levels"; - prefab.setConfig({ + prefab.hydrate({ "log-level.some.test.name": "TRACE", "log-level.some.test": "DEBUG", "log-level.irrelevant": "ERROR", @@ -420,7 +442,7 @@ describe("shouldLog", () => { }); it("can use the root log level setting if nothing is found in the hierarchy", () => { - prefab.setConfig({ + prefab.hydrate({ "log-level": "INFO", }); diff --git a/src/prefab.ts b/src/prefab.ts index f1368fe..8737098 100644 --- a/src/prefab.ts +++ b/src/prefab.ts @@ -134,7 +134,24 @@ export class Prefab { return this.load(); } - get configs(): { [key: string]: Config } { + extract(): Record { + return Object.entries(this._configs).reduce( + (agg, [key, value]) => ({ + ...agg, + [key]: value.value, + }), + {} as Record + ); + } + + hydrate(rawValues: RawConfigWithoutTypes | EvaluationPayload): void { + this.setConfigPrivate(rawValues); + } + + get configs(): Record { + // Log message in yellow without adding chalk dependency + console.warn("\x1b[33m%s\x1b[0m", 'Deprecated: Use "prefab.extract" instead'); + return this._configs; } @@ -174,7 +191,7 @@ export class Prefab { const bootstrapContext = new Context(prefabBootstrap.context); if (this.context.equals(bootstrapContext)) { - this.setConfig({ evaluations: prefabBootstrap.evaluations }); + this.setConfigPrivate({ evaluations: prefabBootstrap.evaluations }); return Promise.resolve(); } } @@ -185,7 +202,7 @@ export class Prefab { return this.loader .load() .then((rawValues: any) => { - this.setConfig(rawValues as EvaluationPayload); + this.setConfigPrivate(rawValues as EvaluationPayload); }) .finally(() => { if (this.pollStatus.status === "running") { @@ -254,6 +271,13 @@ export class Prefab { } setConfig(rawValues: RawConfigWithoutTypes | EvaluationPayload) { + // Log message in yellow without adding chalk dependency + console.warn("\x1b[33m%s\x1b[0m", 'Deprecated: Use "prefab.hydrate" instead'); + + this.setConfigPrivate(rawValues); + } + + private setConfigPrivate(rawValues: RawConfigWithoutTypes | EvaluationPayload) { this._configs = Config.digest(rawValues); this.loaded = true; } @@ -274,7 +298,7 @@ export class Prefab { return undefined; } - const config = this.configs[key]; + const config = this._configs[key]; const value = config?.value;