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:
@@ -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>(),
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user