config-loader: automatically flatten merged config sources

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-03-30 17:39:44 +02:00
parent 10bedeed41
commit 4043a201e5
2 changed files with 62 additions and 2 deletions
@@ -17,8 +17,9 @@
import { MergedConfigSource } from './MergedConfigSource';
import { MutableConfigSource } from './MutableConfigSource';
import { isResolved, readAll, simpleSource } from './__testUtils__/testUtils';
import { ConfigSource } from './types';
describe('MergeConfigSource', () => {
describe('MergedConfigSource', () => {
it('should forward from a single source', async () => {
const source = simpleSource([{ a: 1 }, { a: 2 }, { a: 3 }]);
const merged = MergedConfigSource.from([source]);
@@ -104,4 +105,47 @@ describe('MergeConfigSource', () => {
done: true,
});
});
it('should be flattened', async () => {
const sym = Symbol.for(
'@backstage/config-loader#MergedConfigSource.sources',
);
const sourceA: ConfigSource = {
async *readConfigData() {
yield { data: [] };
},
};
const sourceD: ConfigSource = {
async *readConfigData() {
yield { data: [] };
},
};
const sourceB: ConfigSource = {
async *readConfigData() {
yield { data: [] };
},
};
const sourceC: ConfigSource = {
async *readConfigData() {
yield { data: [] };
},
};
const sourceAB = MergedConfigSource.from([sourceA, sourceB]);
const sourceABC = MergedConfigSource.from([sourceAB, sourceC]);
const sourceABCD = MergedConfigSource.from([sourceABC, sourceD]);
expect((sourceAB as any)[sym]).toEqual([sourceA, sourceB]);
expect((sourceABC as any)[sym]).toEqual([sourceA, sourceB, sourceC]);
expect((sourceABCD as any)[sym]).toEqual([
sourceA,
sourceB,
sourceC,
sourceD,
]);
await expect(readAll(sourceAB)).resolves.toEqual([[]]);
await expect(readAll(sourceABC)).resolves.toEqual([[]]);
await expect(readAll(sourceABCD)).resolves.toEqual([[]]);
});
});
@@ -21,13 +21,29 @@ import {
ReadConfigDataOptions,
} from './types';
const sourcesSymbol = Symbol.for(
'@backstage/config-loader#MergedConfigSource.sources',
);
export class MergedConfigSource implements ConfigSource {
// An optimization to flatten nested merged sources to avid unnecessary microtasks
static #flattenSources(sources: ConfigSource[]): ConfigSource[] {
return sources.flatMap(source => {
if (sourcesSymbol in source) {
return this.#flattenSources(source[sourcesSymbol] as ConfigSource[]);
}
return source;
});
}
static from(sources: ConfigSource[]): ConfigSource {
return new MergedConfigSource(sources);
return new MergedConfigSource(this.#flattenSources(sources));
}
private constructor(private readonly sources: ConfigSource[]) {}
[sourcesSymbol] = this.sources;
async *readConfigData(
options?: ReadConfigDataOptions,
): AsyncConfigSourceIterator {