From 8df2e44c2a43def942ca9020575ca54dbcfc8b63 Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Fri, 2 Jun 2023 12:52:28 +0200 Subject: [PATCH 01/14] add timeout to feature flags --- packages/core/src/index.ts | 52 ++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f125310d..ee3f2e7d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -35,7 +35,7 @@ type LocalData< selectedVariant?: string; } >; - flags: Record; + flags: Record; }; interface PersistentStorage { @@ -62,7 +62,7 @@ export class Abby< Tests extends Record > { private log = (...args: any[]) => - this.config.debug ? console.log(`core.Abby`, ...args) : () => {}; + this.config.debug ? console.log(`core.Abby`, ...args) : () => { }; private testDevtoolOverrides: Map< keyof Tests, @@ -151,9 +151,10 @@ export class Abby< return acc; }, (this.config.tests ?? {}) as any), flags: data.flags.reduce((acc, { name, isEnabled }) => { - acc[name] = isEnabled; + const validUntil = new Date(new Date().getTime() + 1000 * 60); + acc[name] = { isEnabled, validUntil }; return acc; - }, {} as Record), + }, {} as Record), }; } @@ -177,7 +178,7 @@ export class Abby< this.#data.tests ), flags: Object.keys(this.#data.flags).reduce((acc, flagName) => { - acc[flagName as FlagName] = this.getFeatureFlag(flagName as FlagName); + acc[flagName as FlagName] = {isEnabled: this.getFeatureFlag(flagName as FlagName), validUntil: this.getFeatureFlagTimeout(flagName as FlagName)} return acc; }, this.#data.flags), }; @@ -203,6 +204,37 @@ export class Abby< return this.getProjectData(); } + /** + * Helper function to retrieve the time a flag is valid + * @param key + * @returns + */ + getFeatureFlagTimeout(key: F) { + return this.#data.flags[key].validUntil; + } + + /** + * + * @param key name of the featureflag + * @returns value of flag + */ + checkFeatureFlagTimeout(key: F) { + const flag = this.#data.flags[key]; + if (!flag?.validUntil) return; + const now = new Date(); + if (flag.validUntil.getTime() <= now.getTime()) { + console.log(flag) + console.log("until", flag.validUntil.toLocaleTimeString(), " now:", now.toLocaleTimeString()) + console.log("fetch new flag") + this.loadProjectData() + // refetch + // set new data + // const newFlag = await + // this.#data.flags[key] = newFlag; + } + return flag.isEnabled; + } + /** * Function to get the value of a feature flag. This includes * the overrides from the dev tools and the local overrides if in development mode @@ -212,11 +244,9 @@ export class Abby< */ getFeatureFlag(key: T) { this.log(`getFeatureFlag()`, key); - - const storedValue = this.#data.flags[key]; - + const localOverride = this.flagOverrides?.get(key); - + if (localOverride != null) { return localOverride; } @@ -227,7 +257,7 @@ export class Abby< * 1. DevTools * 2. DevOverrides from config * 3. DevDefault from config - */ + */ if (process.env.NODE_ENV === "development") { const devOverride = (this.config.settings?.flags?.devOverrides as any)?.[ key @@ -240,6 +270,8 @@ export class Abby< } } + const storedValue = this.checkFeatureFlagTimeout(key); + if (storedValue != null) { this.log(`getFeatureFlag() => storedValue:`, storedValue); return storedValue; From d73a0ada683a0c27e6069f8845d558e7f7a039de Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:21:01 +0200 Subject: [PATCH 02/14] add flag timeout map --- packages/core/src/index.ts | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ee3f2e7d..0d75480f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -35,7 +35,7 @@ type LocalData< selectedVariant?: string; } >; - flags: Record; + flags: Record; }; interface PersistentStorage { @@ -71,6 +71,8 @@ export class Abby< private flagDevtoolOverrides: Map = new Map(); + #flagTimeoutMap = new Map + #data: LocalData = { tests: {} as any, flags: {} as any, @@ -151,10 +153,11 @@ export class Abby< return acc; }, (this.config.tests ?? {}) as any), flags: data.flags.reduce((acc, { name, isEnabled }) => { - const validUntil = new Date(new Date().getTime() + 1000 * 60); - acc[name] = { isEnabled, validUntil }; + const validUntil = new Date(new Date().getTime() + 1000 * 60); // flagdefault timeout is 1 minute + this.#flagTimeoutMap.set(name, validUntil) + acc[name] = isEnabled; return acc; - }, {} as Record), + }, {} as Record), }; } @@ -178,7 +181,7 @@ export class Abby< this.#data.tests ), flags: Object.keys(this.#data.flags).reduce((acc, flagName) => { - acc[flagName as FlagName] = {isEnabled: this.getFeatureFlag(flagName as FlagName), validUntil: this.getFeatureFlagTimeout(flagName as FlagName)} + acc[flagName as FlagName] = this.getFeatureFlag(flagName as FlagName); return acc; }, this.#data.flags), }; @@ -210,29 +213,22 @@ export class Abby< * @returns */ getFeatureFlagTimeout(key: F) { - return this.#data.flags[key].validUntil; + return this.#flagTimeoutMap.get(key) } /** - * + * Helper function to check if a featureflag should be refetched * @param key name of the featureflag * @returns value of flag */ - checkFeatureFlagTimeout(key: F) { - const flag = this.#data.flags[key]; - if (!flag?.validUntil) return; + getValidFlag(key: F) { + const flagTime = this.#flagTimeoutMap.get(key) + if (!flagTime) return this.#data.flags[key]; const now = new Date(); - if (flag.validUntil.getTime() <= now.getTime()) { - console.log(flag) - console.log("until", flag.validUntil.toLocaleTimeString(), " now:", now.toLocaleTimeString()) - console.log("fetch new flag") + if (flagTime.getTime() <= now.getTime()) { this.loadProjectData() - // refetch - // set new data - // const newFlag = await - // this.#data.flags[key] = newFlag; } - return flag.isEnabled; + return this.#data.flags[key]; } /** @@ -244,9 +240,9 @@ export class Abby< */ getFeatureFlag(key: T) { this.log(`getFeatureFlag()`, key); - + const localOverride = this.flagOverrides?.get(key); - + if (localOverride != null) { return localOverride; } @@ -270,7 +266,7 @@ export class Abby< } } - const storedValue = this.checkFeatureFlagTimeout(key); + const storedValue = this.getValidFlag(key); if (storedValue != null) { this.log(`getFeatureFlag() => storedValue:`, storedValue); From 2f7f0e32e7c1e2df52360950bafe63cad6d12bb6 Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:43:16 +0200 Subject: [PATCH 03/14] fix type flagTimeoutMap --- packages/core/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0d75480f..a0f16107 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -71,7 +71,7 @@ export class Abby< private flagDevtoolOverrides: Map = new Map(); - #flagTimeoutMap = new Map + #flagTimeoutMap:Map = new Map; #data: LocalData = { tests: {} as any, From 55627e6acba22c777aff35f37bc448c73efc314f Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:48:00 +0200 Subject: [PATCH 04/14] add test for feature flag caching --- packages/core/tests/base.test.ts | 33 +++++++++++++++++++++++++++ packages/core/tests/mocks/handlers.ts | 30 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index bfed6630..fd4c4fc8 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -110,6 +110,39 @@ describe("Abby", () => { expect(abby.getFeatureFlag("flag1")).toBe(false); }); + + it("refetches an expired flag", async () =>{ + const date = new Date() //current date + vi.setSystemTime(date) + const abby = new Abby({ + projectId: "expired", + flags: ["flag1", "flag2"], + }); + await abby.loadProjectData(); + const expiredDate = new Date(new Date().getTime() + 1000 * 60 * 10) //date in 10minutes + vi.setSystemTime(expiredDate) + const spy = vi.spyOn(abby, "loadProjectData") + + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).toBeCalled() + }) + + it("non expired flag does not get refetched", async () => { + const date = new Date() //current date + vi.setSystemTime(date) + const abby = new Abby({ + projectId: "expired", + flags: ["flag1", "flag2"], + }); + await abby.loadProjectData(); + + const spy = vi.spyOn(abby, "loadProjectData") + + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).not.toBeCalled() + }) }); describe("Math helpers", () => { diff --git a/packages/core/tests/mocks/handlers.ts b/packages/core/tests/mocks/handlers.ts index cb19d326..b1f34be1 100644 --- a/packages/core/tests/mocks/handlers.ts +++ b/packages/core/tests/mocks/handlers.ts @@ -31,4 +31,34 @@ export const handlers = [ ); } ), + rest.get( + "https://www.tryabby.com/api/dashboard/expired/data", + // `${ABBY_BASE_URL}/api/dashboard/expired/data`, + (req, res, ctx) => { + return res( + ctx.json({ + tests: [ + { + name: "test", + weights: [1, 1, 1, 1], + }, + { + name: "test2", + weights: [1, 0], + }, + ], + flags: [ + { + name: "flag1", + isEnabled: true, + }, + { + name: "flag2", + isEnabled: false, + }, + ], + } as AbbyDataResponse) + ); + } + ), ]; From 443cc08fb5c39a97832d80e63f45d77c7e8d8c1e Mon Sep 17 00:00:00 2001 From: Tim <82676248+Tim-53@users.noreply.github.com> Date: Sat, 3 Jun 2023 03:41:27 +0200 Subject: [PATCH 05/14] add optional config, fix tests --- packages/core/src/index.ts | 19 ++++++++++++++++--- packages/core/tests/base.test.ts | 19 ++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a0f16107..27c3ef0c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -43,6 +43,11 @@ interface PersistentStorage { set: (key: string, value: string) => void; } +type flagCacheConfig = { + refetchFlags: boolean, + expirationTimeInMinutes: number +} + export type AbbyConfig< FlagName extends string = string, Tests extends Record = Record @@ -54,6 +59,7 @@ export type AbbyConfig< flags?: Array; settings?: Settings>; debug?: boolean; + flagCacheConfig?: flagCacheConfig, }; export class Abby< @@ -153,7 +159,7 @@ export class Abby< return acc; }, (this.config.tests ?? {}) as any), flags: data.flags.reduce((acc, { name, isEnabled }) => { - const validUntil = new Date(new Date().getTime() + 1000 * 60); // flagdefault timeout is 1 minute + const validUntil = new Date(new Date().getTime() + 1000 * 60 *( this.config.flagCacheConfig?.expirationTimeInMinutes ?? 1)); // flagdefault timeout is 1 minute this.#flagTimeoutMap.set(name, validUntil) acc[name] = isEnabled; return acc; @@ -226,11 +232,18 @@ export class Abby< if (!flagTime) return this.#data.flags[key]; const now = new Date(); if (flagTime.getTime() <= now.getTime()) { - this.loadProjectData() + this.refetchFlags() } return this.#data.flags[key]; } + /** + * helper function to make testing easier + */ + refetchFlags() { + this.loadProjectData(); + } + /** * Function to get the value of a feature flag. This includes * the overrides from the dev tools and the local overrides if in development mode @@ -266,7 +279,7 @@ export class Abby< } } - const storedValue = this.getValidFlag(key); + const storedValue = this.config.flagCacheConfig?.refetchFlags ? this.getValidFlag(key) : this.#data.flags[key]; if (storedValue != null) { this.log(`getFeatureFlag() => storedValue:`, storedValue); diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index fd4c4fc8..71412ae8 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -117,13 +117,17 @@ describe("Abby", () => { const abby = new Abby({ projectId: "expired", flags: ["flag1", "flag2"], + flagCacheConfig: { + refetchFlags: true, + expirationTimeInMinutes: 2 + } }); - await abby.loadProjectData(); - const expiredDate = new Date(new Date().getTime() + 1000 * 60 * 10) //date in 10minutes + await abby.loadProjectData() + const expiredDate = new Date(new Date().getTime() + 1000 * 60 * 10) //date in 100 minutes vi.setSystemTime(expiredDate) - const spy = vi.spyOn(abby, "loadProjectData") + const spy = vi.spyOn(abby, "refetchFlags") - expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); expect(abby.getFeatureFlag("flag2")).toBeFalsy(); expect(spy).toBeCalled() }) @@ -134,10 +138,15 @@ describe("Abby", () => { const abby = new Abby({ projectId: "expired", flags: ["flag1", "flag2"], + flagCacheConfig: { + refetchFlags: true, + expirationTimeInMinutes: 2 + } }); + await abby.loadProjectData(); - const spy = vi.spyOn(abby, "loadProjectData") + const spy = vi.spyOn(abby, "refetchFlags") expect(abby.getFeatureFlag("flag1")).toBeTruthy(); expect(abby.getFeatureFlag("flag2")).toBeFalsy(); From c3e075b1ab50ed7979ccbc6117bbf771e066b0ee Mon Sep 17 00:00:00 2001 From: Tim <82676248+Tim-53@users.noreply.github.com> Date: Sat, 3 Jun 2023 04:04:17 +0200 Subject: [PATCH 06/14] add more tests for config --- packages/core/tests/base.test.ts | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index 71412ae8..ac620899 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -152,6 +152,81 @@ describe("Abby", () => { expect(abby.getFeatureFlag("flag2")).toBeFalsy(); expect(spy).not.toBeCalled() }) + + it("respects the featureFlagCacheConfig refetchFlags value set to false", async () => { + const date = new Date() //current date + vi.setSystemTime(date) + const abby = new Abby({ + projectId: "expired", + flags: ["flag1", "flag2"], + flagCacheConfig: { + refetchFlags: false, + expirationTimeInMinutes: 2 + } + }); + + await abby.loadProjectData(); + + const spy = vi.spyOn(abby, "refetchFlags") + + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).not.toBeCalled() + }) + + it("", async () => { + const date = new Date() //current date + vi.setSystemTime(date) + const abby = new Abby({ + projectId: "expired", + flags: ["flag1", "flag2"], + flagCacheConfig: { + refetchFlags: true, + expirationTimeInMinutes: 2 + } + }); + + await abby.loadProjectData(); + + const spy = vi.spyOn(abby, "refetchFlags") + + //set date to 5 Minutes in the future + const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); + vi.setSystemTime(dateIn3Minutes) + + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).toBeCalled() + }) + + it("respects the featureFlagCacheCOnfig expiration time", async () => { + const date = new Date() //current date + vi.setSystemTime(date) + const abby = new Abby({ + projectId: "expired", + flags: ["flag1", "flag2"], + flagCacheConfig: { + refetchFlags: true, + expirationTimeInMinutes: 2 + } + }); + + await abby.loadProjectData(); + + const spy = vi.spyOn(abby, "refetchFlags") + + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).not.toBeCalled() + + //set date to 5 Minutes in the future + const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); + vi.setSystemTime(dateIn3Minutes) + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).toBeCalled() + }) + }); describe("Math helpers", () => { From 1e0d542b66a3cb45fda2e20420fb74b9c67c062d Mon Sep 17 00:00:00 2001 From: Tim <82676248+Tim-53@users.noreply.github.com> Date: Sat, 3 Jun 2023 04:27:51 +0200 Subject: [PATCH 07/14] add even more tests --- packages/core/tests/base.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index ac620899..002b016d 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -229,6 +229,23 @@ describe("Abby", () => { }); +it("respects the default behaviour", async () => { + const date = new Date() //current date + vi.setSystemTime(date) + const abby = new Abby({ + projectId: "expired", + flags: ["flag1", "flag2"], + }); + + await abby.loadProjectData(); + + const spy = vi.spyOn(abby, "refetchFlags") + + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBeFalsy(); + expect(spy).not.toBeCalled() +}) + describe("Math helpers", () => { it("validates weight", () => { const variants = ["variant1", "variant2"]; From 0033e0d39eea0d6cac85e41a1404bff068cbf922 Mon Sep 17 00:00:00 2001 From: Tim <82676248+Tim-53@users.noreply.github.com> Date: Sat, 3 Jun 2023 14:35:03 +0200 Subject: [PATCH 08/14] rename to timetolive --- packages/core/src/index.ts | 4 ++-- packages/core/tests/base.test.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 27c3ef0c..d21e3833 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -45,7 +45,7 @@ interface PersistentStorage { type flagCacheConfig = { refetchFlags: boolean, - expirationTimeInMinutes: number + timeToLive: number } export type AbbyConfig< @@ -159,7 +159,7 @@ export class Abby< return acc; }, (this.config.tests ?? {}) as any), flags: data.flags.reduce((acc, { name, isEnabled }) => { - const validUntil = new Date(new Date().getTime() + 1000 * 60 *( this.config.flagCacheConfig?.expirationTimeInMinutes ?? 1)); // flagdefault timeout is 1 minute + const validUntil = new Date(new Date().getTime() + 1000 * 60 *( this.config.flagCacheConfig?.timeToLive ?? 1)); // flagdefault timeout is 1 minute this.#flagTimeoutMap.set(name, validUntil) acc[name] = isEnabled; return acc; diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index 002b016d..17ddd2e1 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -119,7 +119,7 @@ describe("Abby", () => { flags: ["flag1", "flag2"], flagCacheConfig: { refetchFlags: true, - expirationTimeInMinutes: 2 + timeToLive: 2 } }); await abby.loadProjectData() @@ -140,7 +140,7 @@ describe("Abby", () => { flags: ["flag1", "flag2"], flagCacheConfig: { refetchFlags: true, - expirationTimeInMinutes: 2 + timeToLive: 2 } }); @@ -161,7 +161,7 @@ describe("Abby", () => { flags: ["flag1", "flag2"], flagCacheConfig: { refetchFlags: false, - expirationTimeInMinutes: 2 + timeToLive: 2 } }); @@ -182,7 +182,7 @@ describe("Abby", () => { flags: ["flag1", "flag2"], flagCacheConfig: { refetchFlags: true, - expirationTimeInMinutes: 2 + timeToLive: 2 } }); @@ -207,7 +207,7 @@ describe("Abby", () => { flags: ["flag1", "flag2"], flagCacheConfig: { refetchFlags: true, - expirationTimeInMinutes: 2 + timeToLive: 2 } }); From 5de80abc4da9df361abe8f6520766ded2917c446 Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Tue, 6 Jun 2023 11:11:17 +0200 Subject: [PATCH 09/14] update tests --- packages/core/tests/base.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index 17ddd2e1..eca7377b 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -241,6 +241,10 @@ it("respects the default behaviour", async () => { const spy = vi.spyOn(abby, "refetchFlags") + //set date to 5 Minutes in the future + const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); + vi.setSystemTime(dateIn3Minutes) + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); expect(abby.getFeatureFlag("flag2")).toBeFalsy(); expect(spy).not.toBeCalled() From 008d37dd3d7cf475cdcd09becf0e772e9b1409b4 Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Fri, 9 Jun 2023 16:33:30 +0200 Subject: [PATCH 10/14] repair lockfile --- pnpm-lock.yaml | 110 ++++++++++++------------------------------------- 1 file changed, 27 insertions(+), 83 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8060499..fdcea76c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ importers: version: 2.8.8 turbo: specifier: latest - version: 1.9.9 + version: 1.10.2 apps/angular-example: dependencies: @@ -672,7 +672,7 @@ importers: version: 8.5.0(eslint@7.32.0) eslint-config-turbo: specifier: latest - version: 1.9.9(eslint@7.32.0) + version: 1.10.2(eslint@7.32.0) eslint-plugin-react: specifier: 7.31.8 version: 7.31.8(eslint@7.32.0) @@ -1322,7 +1322,6 @@ packages: /@angular/compiler-cli@15.2.0(@angular/compiler@15.2.0)(typescript@4.9.5): resolution: {integrity: sha512-ETnRBdY/LGcmDRQ9GQc9KyCd1kuRnj+Y9luq2dCTMysP+NgylmYoGDsJOsDKm6SzPo+B4PSAyHX2J4CVQFHpPg==} engines: {node: ^14.20.0 || ^16.13.0 || >=18.10.0} - hasBin: true peerDependencies: '@angular/compiler': 15.2.0 typescript: '>=4.8.2 <5.0' @@ -1894,7 +1893,6 @@ packages: /@babel/parser@7.21.4: resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==} engines: {node: '>=6.0.0'} - hasBin: true dependencies: '@babel/types': 7.21.4 @@ -4762,7 +4760,6 @@ packages: /@microsoft/api-extractor@7.34.4: resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==} - hasBin: true dependencies: '@microsoft/api-extractor-model': 7.26.4 '@microsoft/tsdoc': 0.14.2 @@ -7067,7 +7064,6 @@ packages: /@sveltejs/kit@1.15.8(svelte@3.58.0)(vite@4.3.2): resolution: {integrity: sha512-xPIF3UbFEA5BBZWFTGGUtSZ0O3DAtmzIp/yZZVdLIfzZ9+geKG3iGSVFnOUdYstjU7JcvJg12UC5MD5xoED59A==} engines: {node: ^16.14 || >=18} - hasBin: true requiresBuild: true peerDependencies: svelte: ^3.54.0 @@ -7095,7 +7091,6 @@ packages: /@sveltejs/package@2.0.2(svelte@3.58.0)(typescript@4.9.5): resolution: {integrity: sha512-cCOCcO8yMHnhHyaR51nQtvKZ3o/vSU9UYI1EXLT1j2CKNPMuH1/g6JNwKcNNrtQGwwquudc69ZeYy8D/TDNwEw==} engines: {node: ^16.14 || >=18} - hasBin: true peerDependencies: svelte: ^3.44.0 dependencies: @@ -8068,7 +8063,6 @@ packages: /@types/sass@1.45.0: resolution: {integrity: sha512-jn7qwGFmJHwUSphV8zZneO3GmtlgLsmhs/LQyVvQbIIa+fzGMUiHI4HXJZL3FT8MJmgXWbLGiVVY7ElvHq6vDA==} - deprecated: This is a stub types definition. sass provides its own type definitions, so you do not need this installed. dependencies: sass: 1.62.0 dev: true @@ -8622,7 +8616,6 @@ packages: /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} - hasBin: true /acorn@8.8.1: resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} @@ -8631,7 +8624,6 @@ packages: /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} - hasBin: true /address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} @@ -8975,7 +8967,6 @@ packages: /autoprefixer@10.4.13(postcss@8.4.21): resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} - hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: @@ -8990,7 +8981,6 @@ packages: /autoprefixer@10.4.14(postcss@8.4.21): resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} engines: {node: ^10 || ^12 || >=14} - hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: @@ -11028,7 +11018,6 @@ packages: /esbuild@0.17.17: resolution: {integrity: sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA==} engines: {node: '>=12'} - hasBin: true requiresBuild: true optionalDependencies: '@esbuild/android-arm': 0.17.17 @@ -11196,20 +11185,19 @@ packages: /eslint-config-prettier@8.5.0(eslint@7.32.0): resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} - hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: eslint: 7.32.0 dev: false - /eslint-config-turbo@1.9.9(eslint@7.32.0): - resolution: {integrity: sha512-OQLvRK9Ej/8HIEAW6e9hPu3nk1nCYWJ76voB4eOIaI2fYeIKC++0/r0zJPMOD8puo5V1DH+Gkd0XioKpL14ncg==} + /eslint-config-turbo@1.10.2(eslint@7.32.0): + resolution: {integrity: sha512-BaCnpn2GM0rTFLuTVplqY8n+3ttWcu/vEmfjJ2BNBVmwX6ALZoJQfL26ZW6VucRk0psTUJALeo+aPrf3VKEJXA==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 7.32.0 - eslint-plugin-turbo: 1.9.9(eslint@7.32.0) + eslint-plugin-turbo: 1.10.2(eslint@7.32.0) dev: false /eslint-import-resolver-node@0.3.6: @@ -11496,8 +11484,8 @@ packages: string.prototype.matchall: 4.0.8 dev: true - /eslint-plugin-turbo@1.9.9(eslint@7.32.0): - resolution: {integrity: sha512-BgtBMcgNd2YKiHbn1clRiEAmnlpSl19kt9yfIhFEsNIVPg2Gx0O1H++vWXGzMtT19mjHG4Unx0uIMRENKnDYLg==} + /eslint-plugin-turbo@1.10.2(eslint@7.32.0): + resolution: {integrity: sha512-Kxsy4zlKLrGkEqZgcAQtu16YqU/g0mV1vYa9/VweF+MSnWWQsEzsJ1qlzTfXV6N9VqGmkuLiyWOA84sRUklOOg==} peerDependencies: eslint: '>6.6.0' dependencies: @@ -11706,7 +11694,6 @@ packages: /eslint@8.4.1: resolution: {integrity: sha512-TxU/p7LB1KxQ6+7aztTnO7K0i+h0tDi81YRY9VzB6Id71kNz+fFYnf5HD5UOQmxkzcoa0TlVZf9dpMtUv0GpWg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true dependencies: '@eslint/eslintrc': 1.3.3 '@humanwhocodes/config-array': 0.9.5 @@ -12519,7 +12506,6 @@ packages: /glob@10.2.3: resolution: {integrity: sha512-Kb4rfmBVE3eQTAimgmeqc2LwSnN0wIOkkUL6HmxEFxNJ4fHghYHVbFba/HcGcRjE6s9KoMNK3rSOwkL4PioZjg==} engines: {node: '>=16 || 14 >=14.17'} - hasBin: true dependencies: foreground-child: 3.1.1 jackspeak: 2.2.0 @@ -13838,20 +13824,17 @@ packages: /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true dependencies: argparse: 1.0.10 esprima: 4.0.1 /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true dependencies: argparse: 2.0.1 /jscodeshift@0.14.0(@babel/preset-env@7.21.4): resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} - hasBin: true peerDependencies: '@babel/preset-env': ^7.1.6 dependencies: @@ -15298,18 +15281,15 @@ packages: /mime@1.4.1: resolution: {integrity: sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==} - hasBin: true dev: false /mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} - hasBin: true /mime@2.5.2: resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==} engines: {node: '>=4.0.0'} - hasBin: true dev: true /mime@2.6.0: @@ -15319,7 +15299,6 @@ packages: /mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} - hasBin: true dev: true /mimic-fn@2.1.0: @@ -15486,12 +15465,10 @@ packages: /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} - hasBin: true /mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} engines: {node: '>=10'} - hasBin: true dev: true /mousetrap@1.6.5: @@ -15523,7 +15500,6 @@ packages: /msw@0.49.1(typescript@4.9.3): resolution: {integrity: sha512-JpIIq4P65ofj0HVmDMkJuRwgP9s5kcdutpZ15evMTb2k91/USB7IKWRLV9J1Mzc3OqTdwNj4RwtOWJ5y/FulQQ==} engines: {node: '>=14'} - hasBin: true requiresBuild: true peerDependencies: typescript: '>= 4.4.x <= 4.9.x' @@ -15559,7 +15535,6 @@ packages: /msw@0.49.3(typescript@4.9.5): resolution: {integrity: sha512-kRCbDNbNnRq5LC1H/NUceZlrPAvSrMH6Or0mirIuH69NY84xwDruPn/hkXTovIK1KwDwbk+ZdoSyJlpiekLxEA==} engines: {node: '>=14'} - hasBin: true requiresBuild: true peerDependencies: typescript: '>= 4.4.x <= 4.9.x' @@ -15611,7 +15586,6 @@ packages: /nanoid@3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -15697,7 +15671,6 @@ packages: /next-sitemap@3.1.55(@next/env@13.3.4)(next@13.3.4): resolution: {integrity: sha512-ZjkRfkqoSLbU+e8W9TWWe0zfOGNA47lpvm35kNcUCmj73gpLX2PIn51gwHT/B6bgGVAFYY0OXixJDrxIIwcEHw==} engines: {node: '>=14.18'} - hasBin: true peerDependencies: '@next/env': '*' next: '*' @@ -15723,7 +15696,6 @@ packages: /next@13.3.4(@babel/core@7.20.5)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-sod7HeokBSvH5QV0KB+pXeLfcXUlLrGnVUXxHpmhilQ+nQYT3Im2O8DswD5e4uqbR8Pvdu9pcWgb1CbXZQZlmQ==} engines: {node: '>=16.8.0'} - hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 fibers: '>= 3.1.0' @@ -15835,7 +15807,6 @@ packages: /ng-packagr@15.2.2(@angular/compiler-cli@15.2.0)(tslib@2.5.0)(typescript@4.9.5): resolution: {integrity: sha512-+042GBD35ztxbHywGJloAiDM/s3Ja3TZtQh361TWqd/xza3K5DMUu6VRGLTgMwG7CW1YsqYHWgMZslP1c+ng7A==} engines: {node: ^14.20.0 || ^16.13.0 || >=18.10.0} - hasBin: true peerDependencies: '@angular/compiler-cli': ^15.0.0 || ^15.2.0-next.0 tailwindcss: ^2.0.0 || ^3.0.0 @@ -15896,7 +15867,6 @@ packages: /node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - requiresBuild: true optional: true /node-dir@0.1.17: @@ -15950,7 +15920,6 @@ packages: /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} - requiresBuild: true optional: true /node-gyp@9.3.1: @@ -17793,7 +17762,6 @@ packages: /resolve@2.0.0-next.4: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} - hasBin: true dependencies: is-core-module: 2.11.0 path-parse: 1.0.7 @@ -17834,14 +17802,12 @@ packages: /rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - hasBin: true dependencies: glob: 7.2.3 dev: true /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true dependencies: glob: 7.2.3 dev: true @@ -17861,7 +17827,6 @@ packages: /rollup@3.21.6: resolution: {integrity: sha512-SXIICxvxQxR3D4dp/3LDHZIJPC8a4anKMHd4E3Jiz2/JnY+2bEjqrOokAauc5ShGVNFHlEFjBXAXlaxkJqIqSg==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true optionalDependencies: fsevents: 2.3.2 dev: true @@ -17971,7 +17936,6 @@ packages: /sass@1.62.0: resolution: {integrity: sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==} engines: {node: '>=14.0.0'} - hasBin: true dependencies: chokidar: 3.5.3 immutable: 4.3.0 @@ -18051,17 +18015,14 @@ packages: /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true /semver@7.0.0: resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} - hasBin: true dev: true /semver@7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} engines: {node: '>=10'} - hasBin: true dependencies: lru-cache: 6.0.0 @@ -18407,7 +18368,6 @@ packages: /sorcery@0.10.0: resolution: {integrity: sha512-R5ocFmKZQFfSTstfOtHjJuAwbpGyf9qjQa1egyhvXSbM7emjrtLXtGdZsDJDABC85YBfVvrOiGWKSYXPKdvP1g==} - hasBin: true dependencies: buffer-crc32: 0.2.13 minimist: 1.2.7 @@ -18460,7 +18420,6 @@ packages: /sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead dev: true /space-separated-tokens@1.1.5: @@ -18823,7 +18782,6 @@ packages: /svelte-check@2.10.3(@babel/core@7.21.4)(svelte@3.55.1): resolution: {integrity: sha512-Nt1aWHTOKFReBpmJ1vPug0aGysqPwJh2seM1OvICfM2oeyaA62mOiy5EvkXhltGfhCcIQcq2LoE0l1CwcWPjlw==} - hasBin: true peerDependencies: svelte: ^3.24.0 dependencies: @@ -18997,7 +18955,6 @@ packages: /tailwindcss@3.3.1(postcss@8.4.21)(ts-node@10.9.1): resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==} engines: {node: '>=12.13.0'} - hasBin: true peerDependencies: postcss: ^8.0.9 dependencies: @@ -19328,7 +19285,6 @@ packages: /ts-node@10.9.1(@types/node@18.15.11)(typescript@4.9.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -19389,7 +19345,6 @@ packages: /tsup@6.5.0(typescript@4.9.3): resolution: {integrity: sha512-36u82r7rYqRHFkD15R20Cd4ercPkbYmuvRkz3Q1LCm5BsiFNUgpo36zbjVhCOgvjyxNBWNKHsaD5Rl8SykfzNA==} engines: {node: '>=14'} - hasBin: true peerDependencies: '@swc/core': ^1 postcss: ^8.4.12 @@ -19465,65 +19420,65 @@ packages: - supports-color dev: true - /turbo-darwin-64@1.9.9: - resolution: {integrity: sha512-UDGM9E21eCDzF5t1F4rzrjwWutcup33e7ZjNJcW/mJDPorazZzqXGKEPIy9kXwKhamUUXfC7668r6ZuA1WXF2Q==} + /turbo-darwin-64@1.10.2: + resolution: {integrity: sha512-sVLpVVANByfMgqf7OYPcZM4KiDnjGu7ITvAzBSa9Iwe14yoWLn8utrNsWCRaQEB6kEqBGLPmvL7AKwkl8M2Gqg==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.9.9: - resolution: {integrity: sha512-VyfkXzTJpYLTAQ9krq2myyEq7RPObilpS04lgJ4OO1piq76RNmSpX9F/t9JCaY9Pj/4TL7i0d8PM7NGhwEA5Ag==} + /turbo-darwin-arm64@1.10.2: + resolution: {integrity: sha512-TKG91DSoYQjsCft4XBx4lYycVT5n3UQB/nOKgv/WJCSfwshLWulya3yhP8JT5erv9rPF8gwgnx87lrCmT4EAVA==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.9.9: - resolution: {integrity: sha512-Fu1MY29Odg8dHOqXcpIIGC3T63XLOGgnGfbobXMKdrC7JQDvtJv8TUCYciRsyknZYjyyKK1z6zKuYIiDjf3KeQ==} + /turbo-linux-64@1.10.2: + resolution: {integrity: sha512-ZIzAkfrzjJFkSM/uEfxU6JjseCsT5PHRu0s0lmYce37ApQbv/HC7tI0cFhuosI30+O8109/mkyZykKE7AQfgqA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.9.9: - resolution: {integrity: sha512-50LI8NafPuJxdnMCBeDdzgyt1cgjQG7FwkyY336v4e95WJPUVjrHdrKH6jYXhOUyrv9+jCJxwX1Yrg02t5yJ1g==} + /turbo-linux-arm64@1.10.2: + resolution: {integrity: sha512-G4uZA+RBQ5S1X/oUxO5KoLL2NDMkrrBZF52+00jQv6UEb9lWDgwzqSwoAGjdXxeDCrqMW5rBVwb/IBIF2/yhwA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.9.9: - resolution: {integrity: sha512-9IsTReoLmQl1IRsy3WExe2j2RKWXQyXujfJ4fXF+jp08KxjVF4/tYP2CIRJx/A7UP/7keBta27bZqzAjsmbSTA==} + /turbo-windows-64@1.10.2: + resolution: {integrity: sha512-ObfQO37kGu1jBzFs/L+hybrCXBwdnimotJwzg7pCoSyGijKITlugrpJoPDKlg0eMr3/1Y6KUeHy26vZaDXrbuQ==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.9.9: - resolution: {integrity: sha512-CUu4hpeQo68JjDr0V0ygTQRLbS+/sNfdqEVV+Xz9136vpKn2WMQLAuUBVZV0Sp0S/7i+zGnplskT0fED+W46wQ==} + /turbo-windows-arm64@1.10.2: + resolution: {integrity: sha512-7S6dx4738R/FIT2cxbsunqgHN5LelXzuzkcaZgdkU33oswRf/6KOfOABzQLdTX7Uos59cBSdwayf6KQJxuOXUg==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.9.9: - resolution: {integrity: sha512-+ZS66LOT7ahKHxh6XrIdcmf2Yk9mNpAbPEj4iF2cs0cAeaDU3xLVPZFF0HbSho89Uxwhx7b5HBgPbdcjQTwQkg==} + /turbo@1.10.2: + resolution: {integrity: sha512-m9sR5XHhuzxUQACf0vI2qCG5OqDYAZiPTaAsTwECnwUF4/cXwEmcYddbLJnO+K9orNvcnjjent5oBNBVQ/o0ow==} hasBin: true requiresBuild: true optionalDependencies: - turbo-darwin-64: 1.9.9 - turbo-darwin-arm64: 1.9.9 - turbo-linux-64: 1.9.9 - turbo-linux-arm64: 1.9.9 - turbo-windows-64: 1.9.9 - turbo-windows-arm64: 1.9.9 + turbo-darwin-64: 1.10.2 + turbo-darwin-arm64: 1.10.2 + turbo-linux-64: 1.10.2 + turbo-linux-arm64: 1.10.2 + turbo-windows-64: 1.10.2 + turbo-windows-arm64: 1.10.2 dev: true /type-check@0.3.2: @@ -19597,7 +19552,6 @@ packages: /typescript@4.8.4: resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} engines: {node: '>=4.2.0'} - hasBin: true dev: true /typescript@4.9.3: @@ -19856,7 +19810,6 @@ packages: /update-browserslist-db@1.0.10(browserslist@4.21.5): resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: @@ -20090,7 +20043,6 @@ packages: /vite@3.2.5: resolution: {integrity: sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==} engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true peerDependencies: '@types/node': '>= 14' less: '*' @@ -20123,7 +20075,6 @@ packages: /vite@4.2.0: resolution: {integrity: sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==} engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true peerDependencies: '@types/node': '>= 14' less: '*' @@ -20156,7 +20107,6 @@ packages: /vite@4.3.2(@types/node@18.15.11): resolution: {integrity: sha512-9R53Mf+TBoXCYejcL+qFbZde+eZveQLDYd9XgULILLC1a5ZwPaqgmdVpL8/uvw2BM/1TzetWjglwm+3RO+xTyw==} engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true peerDependencies: '@types/node': '>= 14' less: '*' @@ -20211,7 +20161,6 @@ packages: /vitest@0.25.8(jsdom@20.0.3): resolution: {integrity: sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==} engines: {node: '>=v14.16.0'} - hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@vitest/browser': '*' @@ -20405,7 +20354,6 @@ packages: /webpack-dev-server@4.11.1(webpack@5.75.0): resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} engines: {node: '>= 12.13.0'} - hasBin: true peerDependencies: webpack: ^4.37.0 || ^5.0.0 webpack-cli: '*' @@ -20480,7 +20428,6 @@ packages: /webpack@5.75.0(esbuild@0.17.8): resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==} engines: {node: '>=10.13.0'} - hasBin: true peerDependencies: webpack-cli: '*' peerDependenciesMeta: @@ -20672,14 +20619,12 @@ packages: /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} - hasBin: true dependencies: isexe: 2.0.0 /which@3.0.1: resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true dependencies: isexe: 2.0.0 dev: true @@ -20936,7 +20881,6 @@ packages: /z-schema@5.0.5: resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} engines: {node: '>=8.0.0'} - hasBin: true dependencies: lodash.get: 4.4.2 lodash.isequal: 4.5.0 From 7d72fe5ae006735ac9a64d3053e1f3107e7002eb Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Thu, 13 Jul 2023 20:57:00 +0200 Subject: [PATCH 11/14] fix tests for core after main merge --- packages/core/src/index.ts | 9 +- packages/core/tests/base.test.ts | 156 +++++++++++++++++-------------- 2 files changed, 92 insertions(+), 73 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3bfbf605..83c6e316 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -111,6 +111,10 @@ export class Abby< flagName as FlagName, config.flags as any ); + const validUntil = new Date( + new Date().getTime() + 1000 * 60 * (this.config.flagCacheConfig?.timeToLive ?? 1) + ); // flagdefault timeout is 1 minute + this.#flagTimeoutMap.set(flagName, validUntil); return acc; }, {} as Record @@ -285,8 +289,9 @@ export class Abby< return devOverride; } } - - const storedValue = this.#data.flags[key as unknown as FlagName]; + const storedValue = this.config.flagCacheConfig?.refetchFlags + ? this.getValidFlag(key as unknown as FlagName) + : this.#data.flags[key as unknown as FlagName]; const flagType = this._cfg.flags?.[key]; const defaultValue = this._cfg.settings?.flags?.defaultValues?.[flagType!]; diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index 77215254..12c7de31 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -122,144 +122,158 @@ describe("Abby", () => { expect(abby.getFeatureFlag("flag1")).toBe(false); }); - it("refetches an expired flag", async () =>{ - const date = new Date() //current date - vi.setSystemTime(date) + it("refetches an expired flag", async () => { + const date = new Date(); //current date + vi.setSystemTime(date); const abby = new Abby({ projectId: "expired", - flags: ["flag1", "flag2"], + flags: { + flag1: "Boolean", + flag2: "String", + }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2 - } + timeToLive: 2, + }, }); - await abby.loadProjectData() - const expiredDate = new Date(new Date().getTime() + 1000 * 60 * 10) //date in 100 minutes - vi.setSystemTime(expiredDate) - const spy = vi.spyOn(abby, "refetchFlags") + await abby.loadProjectData(); + const expiredDate = new Date(new Date().getTime() + 1000 * 60 * 10); //date in 100 minutes + vi.setSystemTime(expiredDate); + const spy = vi.spyOn(abby, "refetchFlags"); - expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).toBeCalled() - }) + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).toBeCalled(); + }); it("non expired flag does not get refetched", async () => { - const date = new Date() //current date - vi.setSystemTime(date) + const date = new Date(); //current date + vi.setSystemTime(date); const abby = new Abby({ projectId: "expired", - flags: ["flag1", "flag2"], + flags: { + flag1: "Boolean", + flag2: "Boolean", + }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2 - } + timeToLive: 2, + }, }); await abby.loadProjectData(); - const spy = vi.spyOn(abby, "refetchFlags") + const spy = vi.spyOn(abby, "refetchFlags"); expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).not.toBeCalled() - }) + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).not.toBeCalled(); + }); it("respects the featureFlagCacheConfig refetchFlags value set to false", async () => { - const date = new Date() //current date - vi.setSystemTime(date) + const date = new Date(); //current date + vi.setSystemTime(date); const abby = new Abby({ projectId: "expired", - flags: ["flag1", "flag2"], + flags: { + flag1: "Boolean", + flag2: "Boolean", + }, flagCacheConfig: { refetchFlags: false, - timeToLive: 2 - } + timeToLive: 2, + }, }); await abby.loadProjectData(); - const spy = vi.spyOn(abby, "refetchFlags") + const spy = vi.spyOn(abby, "refetchFlags"); expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).not.toBeCalled() - }) + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).not.toBeCalled(); + }); - it("", async () => { - const date = new Date() //current date - vi.setSystemTime(date) + it("it refetches expired flags", async () => { + const date = new Date(); //current date + vi.setSystemTime(date); const abby = new Abby({ projectId: "expired", - flags: ["flag1", "flag2"], + flags: { + flag1: "Boolean", + flag2: "Boolean", + }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2 - } + timeToLive: 2, + }, }); await abby.loadProjectData(); - const spy = vi.spyOn(abby, "refetchFlags") + const spy = vi.spyOn(abby, "refetchFlags"); //set date to 5 Minutes in the future - const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); - vi.setSystemTime(dateIn3Minutes) + const dateIn5Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); + vi.setSystemTime(dateIn5Minutes); expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).toBeCalled() - }) + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).toBeCalled(); + }); it("respects the featureFlagCacheCOnfig expiration time", async () => { - const date = new Date() //current date - vi.setSystemTime(date) + const date = new Date(); //current date + vi.setSystemTime(date); const abby = new Abby({ projectId: "expired", - flags: ["flag1", "flag2"], + flags: { + flag1: "Boolean", + flag2: "Boolean", + }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2 - } + timeToLive: 2, + }, }); await abby.loadProjectData(); - - const spy = vi.spyOn(abby, "refetchFlags") - + + const spy = vi.spyOn(abby, "refetchFlags"); + expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).not.toBeCalled() + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).not.toBeCalled(); //set date to 5 Minutes in the future const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); - vi.setSystemTime(dateIn3Minutes) + vi.setSystemTime(dateIn3Minutes); expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).toBeCalled() - }) - + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).toBeCalled(); + }); }); it("respects the default behaviour", async () => { - const date = new Date() //current date - vi.setSystemTime(date) + const date = new Date(); //current date + vi.setSystemTime(date); const abby = new Abby({ projectId: "expired", - flags: ["flag1", "flag2"], + flags: { flag1: "Boolean", flag2: "Boolean" }, }); await abby.loadProjectData(); - - const spy = vi.spyOn(abby, "refetchFlags") - - //set date to 5 Minutes in the future - const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); - vi.setSystemTime(dateIn3Minutes) + + const spy = vi.spyOn(abby, "refetchFlags"); + + //set date to 5 Minutes in the future + const dateIn3Minutes = new Date(new Date().getTime() + 1000 * 60 * 5); + vi.setSystemTime(dateIn3Minutes); expect(abby.getFeatureFlag("flag1")).toBeTruthy(); - expect(abby.getFeatureFlag("flag2")).toBeFalsy(); - expect(spy).not.toBeCalled() -}) + expect(abby.getFeatureFlag("flag2")).toBe("test"); + expect(spy).not.toBeCalled(); +}); describe("Math helpers", () => { it("validates weight", () => { From 013f01b04a2ba02767f429ee85af0db4de02b0a4 Mon Sep 17 00:00:00 2001 From: Tim-53 <82676248+Tim-53@users.noreply.github.com> Date: Thu, 13 Jul 2023 20:57:39 +0200 Subject: [PATCH 12/14] Update packages/core/src/index.ts Co-authored-by: slaesh <55546823+slaesh@users.noreply.github.com> --- packages/core/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 83c6e316..51c99f70 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -48,7 +48,7 @@ interface PersistentStorage { set: (key: string, value: string) => void; } -type flagCacheConfig = { +type FlagCacheConfig = { refetchFlags: boolean; timeToLive: number; }; From e086c298146fc760c31c638199bf9b44b6e966ec Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Thu, 13 Jul 2023 21:03:21 +0200 Subject: [PATCH 13/14] clearer variable names --- packages/core/src/index.ts | 8 ++++---- packages/core/tests/base.test.ts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 83c6e316..325da0ca 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -48,9 +48,9 @@ interface PersistentStorage { set: (key: string, value: string) => void; } -type flagCacheConfig = { +type FlagCacheConfig = { refetchFlags: boolean; - timeToLive: number; + timeToLiveInMinutes: number; }; export type AbbyConfig< @@ -65,7 +65,7 @@ export type AbbyConfig< flags?: Flags; settings?: Settings, Flags>; debug?: boolean; - flagCacheConfig?: flagCacheConfig; + flagCacheConfig?: FlagCacheConfig; }; export class Abby< @@ -112,7 +112,7 @@ export class Abby< config.flags as any ); const validUntil = new Date( - new Date().getTime() + 1000 * 60 * (this.config.flagCacheConfig?.timeToLive ?? 1) + new Date().getTime() + 1000 * 60 * (this.config.flagCacheConfig?.timeToLiveInMinutes ?? 1) ); // flagdefault timeout is 1 minute this.#flagTimeoutMap.set(flagName, validUntil); return acc; diff --git a/packages/core/tests/base.test.ts b/packages/core/tests/base.test.ts index 12c7de31..a963d037 100644 --- a/packages/core/tests/base.test.ts +++ b/packages/core/tests/base.test.ts @@ -133,7 +133,7 @@ describe("Abby", () => { }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2, + timeToLiveInMinutes: 2, }, }); await abby.loadProjectData(); @@ -157,7 +157,7 @@ describe("Abby", () => { }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2, + timeToLiveInMinutes: 2, }, }); @@ -181,7 +181,7 @@ describe("Abby", () => { }, flagCacheConfig: { refetchFlags: false, - timeToLive: 2, + timeToLiveInMinutes: 2, }, }); @@ -205,7 +205,7 @@ describe("Abby", () => { }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2, + timeToLiveInMinutes: 2, }, }); @@ -233,7 +233,7 @@ describe("Abby", () => { }, flagCacheConfig: { refetchFlags: true, - timeToLive: 2, + timeToLiveInMinutes: 2, }, }); From c37e887310e2fb7322429715f33361bd6d2b7f7e Mon Sep 17 00:00:00 2001 From: Tim Trost <82676248+Tim-53@users.noreply.github.com> Date: Thu, 13 Jul 2023 21:24:14 +0200 Subject: [PATCH 14/14] integrate feedback --- packages/core/src/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 325da0ca..c28f247e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -241,9 +241,11 @@ export class Abby< * @param key name of the featureflag * @returns value of flag */ - getValidFlag(key: F) { + getValidFlag(key: F, refetch: boolean | undefined) { + if (!refetch) return this.#data.flags[key]; const flagTime = this.#flagTimeoutMap.get(key); if (!flagTime) return this.#data.flags[key]; + const now = new Date(); if (flagTime.getTime() <= now.getTime()) { this.refetchFlags(); @@ -289,9 +291,10 @@ export class Abby< return devOverride; } } - const storedValue = this.config.flagCacheConfig?.refetchFlags - ? this.getValidFlag(key as unknown as FlagName) - : this.#data.flags[key as unknown as FlagName]; + const storedValue = this.getValidFlag( + key as unknown as FlagName, + this.config.flagCacheConfig?.refetchFlags + ); const flagType = this._cfg.flags?.[key]; const defaultValue = this._cfg.settings?.flags?.defaultValues?.[flagType!];