From 386e7aa007f4360224428071a44ad5f5f83f4b74 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 8 Mar 2021 15:55:39 +0100 Subject: [PATCH] core-api: refactor global singleton access to get/set/getOrCreate Co-authored-by: Juan Lulkin Signed-off-by: Patrik Oldsberg --- .../core-api/src/extensions/componentData.tsx | 4 +- .../core-api/src/lib/globalObject.test.ts | 58 +++++++++++++++++-- packages/core-api/src/lib/globalObject.ts | 37 +++++++++++- packages/core-api/src/routing/types.ts | 4 +- 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/packages/core-api/src/extensions/componentData.tsx b/packages/core-api/src/extensions/componentData.tsx index 8b46d34705..fc8594039c 100644 --- a/packages/core-api/src/extensions/componentData.tsx +++ b/packages/core-api/src/extensions/componentData.tsx @@ -15,7 +15,7 @@ */ import { ComponentType, ReactNode } from 'react'; -import { getGlobalSingleton } from '../lib/globalObject'; +import { getOrCreateGlobalSingleton } from '../lib/globalObject'; // TODO(Rugvip): Access via symbol is deprecated, remove once on 0.3.x const DATA_KEY = Symbol('backstage-component-data'); @@ -33,7 +33,7 @@ type MaybeComponentNode = ReactNode & { }; // The store is bridged across versions using the global object -const store = getGlobalSingleton( +const store = getOrCreateGlobalSingleton( 'component-data-store', () => new WeakMap, DataContainer>(), ); diff --git a/packages/core-api/src/lib/globalObject.test.ts b/packages/core-api/src/lib/globalObject.test.ts index f539483a83..e72f027b46 100644 --- a/packages/core-api/src/lib/globalObject.test.ts +++ b/packages/core-api/src/lib/globalObject.test.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { getGlobalSingleton } from './globalObject'; +import { + getGlobalSingleton, + getOrCreateGlobalSingleton, + setGlobalSingleton, +} from './globalObject'; const anyGlobal = global as any; @@ -23,20 +27,64 @@ describe('getGlobalSingleton', () => { delete anyGlobal['__@backstage/my-thing__']; }); + it('should return an existing value', () => { + const myThing = {}; + const myOtherThing = {}; + + anyGlobal['__@backstage/my-thing__'] = myThing; + expect(getGlobalSingleton('my-thing')).toBe(myThing); + expect(getGlobalSingleton('my-thing')).toBe(myThing); + anyGlobal['__@backstage/my-thing__'] = myOtherThing; + expect(getGlobalSingleton('my-thing')).toBe(myOtherThing); + }); + + it('should throw if the value is not set', () => { + expect(() => getGlobalSingleton('my-thing')).toThrow( + 'Global my-thing is not set', + ); + }); +}); + +describe('getOrCreateGlobalSingleton', () => { + beforeEach(() => { + delete anyGlobal['__@backstage/my-thing__']; + }); + it('should return an existing value', () => { const myThing = {}; anyGlobal['__@backstage/my-thing__'] = myThing; - expect(getGlobalSingleton('my-thing', () => ({}))).toBe(myThing); - expect(getGlobalSingleton('my-thing', () => ({}))).toBe(myThing); + expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myThing); + expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myThing); }); it('should should create a new value', () => { const myNewThing = {}; expect(anyGlobal['__@backstage/my-thing__']).toBe(undefined); - expect(getGlobalSingleton('my-thing', () => myNewThing)).toBe(myNewThing); + expect(getOrCreateGlobalSingleton('my-thing', () => myNewThing)).toBe( + myNewThing, + ); expect(anyGlobal['__@backstage/my-thing__']).toBe(myNewThing); - expect(getGlobalSingleton('my-thing', () => ({}))).toBe(myNewThing); + expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myNewThing); + }); +}); + +describe('setGlobalSingleton', () => { + beforeEach(() => { + delete anyGlobal['__@backstage/my-thing__']; + }); + + it('should set a global value', () => { + setGlobalSingleton('my-thing', 'global value'); + + expect(anyGlobal['__@backstage/my-thing__']).toBe('global value'); + }); + + it('should throw if global value is set', () => { + anyGlobal['__@backstage/my-thing__'] = 'already defined'; + expect(() => setGlobalSingleton('my-thing', () => 'global value')).toThrow( + 'Global my-thing is already se', + ); }); }); diff --git a/packages/core-api/src/lib/globalObject.ts b/packages/core-api/src/lib/globalObject.ts index c7f9f98882..87be58499d 100644 --- a/packages/core-api/src/lib/globalObject.ts +++ b/packages/core-api/src/lib/globalObject.ts @@ -26,10 +26,41 @@ function getGlobalObject() { return Function('return this')(); } -export const globalObject = getGlobalObject(); +const globalObject = getGlobalObject(); -export function getGlobalSingleton(id: string, supplier: () => T): T { - const key = `__@backstage/${id}__`; +const makeKey = (id: string) => `__@backstage/${id}__`; + +/** + * Used to provide a global singleton value, failing if it is already set. + */ +export function setGlobalSingleton(id: string, value: unknown): void { + const key = makeKey(id); + if (key in globalObject) { + throw new Error(`Global ${id} is already set`); // TODO some sort of special build err + } + globalObject[key] = value; +} + +/** + * Used to access a global singleton value, failing if it is not already set. + */ +export function getGlobalSingleton(id: string): T { + const key = makeKey(id); + if (!(key in globalObject)) { + throw new Error(`Global ${id} is not set`); // TODO some sort of special build err + } + + return globalObject[key]; +} + +/** + * Serializes access to a global singleton value, with the first caller creating the value. + */ +export function getOrCreateGlobalSingleton( + id: string, + supplier: () => T, +): T { + const key = makeKey(id); let value = globalObject[key]; if (value) { diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts index 56b991f3af..1fabd39cc4 100644 --- a/packages/core-api/src/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -15,7 +15,7 @@ */ import { IconComponent } from '../icons'; -import { getGlobalSingleton } from '../lib/globalObject'; +import { getOrCreateGlobalSingleton } from '../lib/globalObject'; export type AnyParams = { [param in string]: string } | undefined; export type ParamKeys = keyof Params extends never @@ -34,7 +34,7 @@ export type RouteFunc = ( ...[params]: Params extends undefined ? readonly [] : readonly [Params] ) => string; -export const routeRefType: unique symbol = getGlobalSingleton( +export const routeRefType: unique symbol = getOrCreateGlobalSingleton( 'route-ref-type', () => Symbol('route-ref-type'), );