core-api: refactor global singleton access to get/set/getOrCreate

Co-authored-by: Juan Lulkin <jmaiz@spotify.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-03-08 15:55:39 +01:00
parent 61f071cfcb
commit 386e7aa007
4 changed files with 91 additions and 12 deletions
@@ -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<ComponentType<any>, DataContainer>(),
);
+53 -5
View File
@@ -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',
);
});
});
+34 -3
View File
@@ -26,10 +26,41 @@ function getGlobalObject() {
return Function('return this')();
}
export const globalObject = getGlobalObject();
const globalObject = getGlobalObject();
export function getGlobalSingleton<T>(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<T>(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<T>(
id: string,
supplier: () => T,
): T {
const key = makeKey(id);
let value = globalObject[key];
if (value) {
+2 -2
View File
@@ -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<Params extends AnyParams> = keyof Params extends never
@@ -34,7 +34,7 @@ export type RouteFunc<Params extends AnyParams> = (
...[params]: Params extends undefined ? readonly [] : readonly [Params]
) => string;
export const routeRefType: unique symbol = getGlobalSingleton<any>(
export const routeRefType: unique symbol = getOrCreateGlobalSingleton<any>(
'route-ref-type',
() => Symbol('route-ref-type'),
);