@backstage/backend-test-utils 0.4.5-next.0 → 0.4.5-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @backstage/backend-test-utils
2
2
 
3
+ ## 0.4.5-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`.
8
+ - 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation.
9
+ - Updated dependencies
10
+ - @backstage/backend-defaults@0.4.2-next.2
11
+ - @backstage/backend-plugin-api@0.8.0-next.2
12
+ - @backstage/backend-app-api@0.8.1-next.2
13
+ - @backstage/plugin-auth-node@0.5.0-next.2
14
+ - @backstage/plugin-events-node@0.3.9-next.2
15
+ - @backstage/config@1.2.0
16
+ - @backstage/errors@1.2.4
17
+ - @backstage/types@1.1.1
18
+
19
+ ## 0.4.5-next.1
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @backstage/backend-plugin-api@0.7.1-next.1
25
+ - @backstage/backend-app-api@0.8.1-next.1
26
+ - @backstage/backend-defaults@0.4.2-next.1
27
+ - @backstage/config@1.2.0
28
+ - @backstage/errors@1.2.4
29
+ - @backstage/types@1.1.1
30
+ - @backstage/plugin-auth-node@0.4.18-next.1
31
+ - @backstage/plugin-events-node@0.3.9-next.1
32
+
3
33
  ## 0.4.5-next.0
4
34
 
5
35
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -1675,7 +1675,7 @@ function createPluginsForOrphanModules(features) {
1675
1675
  const pluginIds = /* @__PURE__ */ new Set();
1676
1676
  const modulePluginIds = /* @__PURE__ */ new Set();
1677
1677
  for (const feature of features) {
1678
- if (isInternalBackendFeature(feature)) {
1678
+ if (isInternalBackendRegistrations(feature)) {
1679
1679
  const registrations = feature.getRegistrations();
1680
1680
  for (const registration of registrations) {
1681
1681
  if (registration.type === "plugin") {
@@ -1704,17 +1704,7 @@ function createExtensionPointTestModules(features, extensionPointTuples) {
1704
1704
  return [];
1705
1705
  }
1706
1706
  const registrations = features.flatMap((feature) => {
1707
- if (feature.$$type !== "@backstage/BackendFeature") {
1708
- throw new Error(
1709
- `Failed to add feature, invalid type '${feature.$$type}'`
1710
- );
1711
- }
1712
- if (isInternalBackendFeature(feature)) {
1713
- if (feature.version !== "v1") {
1714
- throw new Error(
1715
- `Failed to add feature, invalid version '${feature.version}'`
1716
- );
1717
- }
1707
+ if (isInternalBackendRegistrations(feature)) {
1718
1708
  return feature.getRegistrations();
1719
1709
  }
1720
1710
  return [];
@@ -1883,8 +1873,24 @@ function registerTestHooks() {
1883
1873
  });
1884
1874
  }
1885
1875
  registerTestHooks();
1886
- function isInternalBackendFeature(feature) {
1887
- return typeof feature.getRegistrations === "function";
1876
+ function toInternalBackendFeature(feature) {
1877
+ if (feature.$$type !== "@backstage/BackendFeature") {
1878
+ throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`);
1879
+ }
1880
+ const internal = feature;
1881
+ if (internal.version !== "v1") {
1882
+ throw new Error(
1883
+ `Invalid BackendFeature, bad version '${internal.version}'`
1884
+ );
1885
+ }
1886
+ return internal;
1887
+ }
1888
+ function isInternalBackendRegistrations(feature) {
1889
+ const internal = toInternalBackendFeature(feature);
1890
+ if (internal.featureType === "registrations") {
1891
+ return true;
1892
+ }
1893
+ return "getRegistrations" in internal;
1888
1894
  }
1889
1895
 
1890
1896
  class Node {
@@ -2084,7 +2090,19 @@ function createPluginMetadataServiceFactory(pluginId) {
2084
2090
  }
2085
2091
  class ServiceRegistry {
2086
2092
  static create(factories) {
2087
- const registry = new ServiceRegistry(factories);
2093
+ const factoryMap = /* @__PURE__ */ new Map();
2094
+ for (const factory of factories) {
2095
+ if (factory.service.multiton) {
2096
+ const existing = factoryMap.get(factory.service.id) ?? [];
2097
+ factoryMap.set(
2098
+ factory.service.id,
2099
+ existing.concat(toInternalServiceFactory(factory))
2100
+ );
2101
+ } else {
2102
+ factoryMap.set(factory.service.id, [toInternalServiceFactory(factory)]);
2103
+ }
2104
+ }
2105
+ const registry = new ServiceRegistry(factoryMap);
2088
2106
  registry.checkForCircularDeps();
2089
2107
  return registry;
2090
2108
  }
@@ -2095,17 +2113,15 @@ class ServiceRegistry {
2095
2113
  #addedFactoryIds = /* @__PURE__ */ new Set();
2096
2114
  #instantiatedFactories = /* @__PURE__ */ new Set();
2097
2115
  constructor(factories) {
2098
- this.#providedFactories = new Map(
2099
- factories.map((sf) => [sf.service.id, toInternalServiceFactory(sf)])
2100
- );
2116
+ this.#providedFactories = factories;
2101
2117
  this.#loadedDefaultFactories = /* @__PURE__ */ new Map();
2102
2118
  this.#implementations = /* @__PURE__ */ new Map();
2103
2119
  }
2104
2120
  #resolveFactory(ref, pluginId) {
2105
2121
  if (ref.id === backendPluginApi.coreServices.pluginMetadata.id) {
2106
- return Promise.resolve(
2122
+ return Promise.resolve([
2107
2123
  toInternalServiceFactory(createPluginMetadataServiceFactory(pluginId))
2108
- );
2124
+ ]);
2109
2125
  }
2110
2126
  let resolvedFactory = this.#providedFactories.get(ref.id);
2111
2127
  const { __defaultFactory: defaultFactory } = ref;
@@ -2120,13 +2136,16 @@ class ServiceRegistry {
2120
2136
  );
2121
2137
  this.#loadedDefaultFactories.set(defaultFactory, loadedFactory);
2122
2138
  }
2123
- resolvedFactory = loadedFactory.catch((error) => {
2124
- throw new Error(
2125
- `Failed to instantiate service '${ref.id}' because the default factory loader threw an error, ${errors.stringifyError(
2126
- error
2127
- )}`
2128
- );
2129
- });
2139
+ resolvedFactory = loadedFactory.then(
2140
+ (factory) => [factory],
2141
+ (error) => {
2142
+ throw new Error(
2143
+ `Failed to instantiate service '${ref.id}' because the default factory loader threw an error, ${errors.stringifyError(
2144
+ error
2145
+ )}`
2146
+ );
2147
+ }
2148
+ );
2130
2149
  }
2131
2150
  return Promise.resolve(resolvedFactory);
2132
2151
  }
@@ -2138,6 +2157,9 @@ class ServiceRegistry {
2138
2157
  if (this.#providedFactories.get(ref.id)) {
2139
2158
  return false;
2140
2159
  }
2160
+ if (ref.multiton) {
2161
+ return false;
2162
+ }
2141
2163
  return !ref.__defaultFactory;
2142
2164
  });
2143
2165
  if (missingDeps.length) {
@@ -2149,13 +2171,13 @@ class ServiceRegistry {
2149
2171
  }
2150
2172
  checkForCircularDeps() {
2151
2173
  const graph = DependencyGraph.fromIterable(
2152
- Array.from(this.#providedFactories).map(
2153
- ([serviceId, serviceFactory]) => ({
2154
- value: serviceId,
2155
- provides: [serviceId],
2156
- consumes: Object.values(serviceFactory.deps).map((d) => d.id)
2157
- })
2158
- )
2174
+ Array.from(this.#providedFactories).map(([serviceId, factories]) => ({
2175
+ value: serviceId,
2176
+ provides: [serviceId],
2177
+ consumes: factories.flatMap(
2178
+ (factory) => Object.values(factory.deps).map((d) => d.id)
2179
+ )
2180
+ }))
2159
2181
  );
2160
2182
  const circularDependencies = Array.from(graph.detectCircularDependencies());
2161
2183
  if (circularDependencies.length) {
@@ -2171,21 +2193,28 @@ class ServiceRegistry {
2171
2193
  `The ${backendPluginApi.coreServices.pluginMetadata.id} service cannot be overridden`
2172
2194
  );
2173
2195
  }
2174
- if (this.#addedFactoryIds.has(factoryId)) {
2175
- throw new Error(
2176
- `Duplicate service implementations provided for ${factoryId}`
2177
- );
2178
- }
2179
2196
  if (this.#instantiatedFactories.has(factoryId)) {
2180
2197
  throw new Error(
2181
2198
  `Unable to set service factory with id ${factoryId}, service has already been instantiated`
2182
2199
  );
2183
2200
  }
2184
- this.#addedFactoryIds.add(factoryId);
2185
- this.#providedFactories.set(factoryId, toInternalServiceFactory(factory));
2201
+ if (factory.service.multiton) {
2202
+ const newFactories = (this.#providedFactories.get(factoryId) ?? []).concat(toInternalServiceFactory(factory));
2203
+ this.#providedFactories.set(factoryId, newFactories);
2204
+ } else {
2205
+ if (this.#addedFactoryIds.has(factoryId)) {
2206
+ throw new Error(
2207
+ `Duplicate service implementations provided for ${factoryId}`
2208
+ );
2209
+ }
2210
+ this.#addedFactoryIds.add(factoryId);
2211
+ this.#providedFactories.set(factoryId, [
2212
+ toInternalServiceFactory(factory)
2213
+ ]);
2214
+ }
2186
2215
  }
2187
2216
  async initializeEagerServicesWithScope(scope, pluginId = "root") {
2188
- for (const factory of this.#providedFactories.values()) {
2217
+ for (const [factory] of this.#providedFactories.values()) {
2189
2218
  if (factory.service.scope === scope) {
2190
2219
  if (scope === "root" && factory.initialization !== "lazy") {
2191
2220
  await this.get(factory.service, pluginId);
@@ -2197,72 +2226,80 @@ class ServiceRegistry {
2197
2226
  }
2198
2227
  get(ref, pluginId) {
2199
2228
  this.#instantiatedFactories.add(ref.id);
2200
- return this.#resolveFactory(ref, pluginId)?.then((factory) => {
2201
- if (factory.service.scope === "root") {
2202
- let existing = this.#rootServiceImplementations.get(factory);
2203
- if (!existing) {
2204
- this.#checkForMissingDeps(factory, pluginId);
2205
- const rootDeps = new Array();
2206
- for (const [name, serviceRef] of Object.entries(factory.deps)) {
2207
- if (serviceRef.scope !== "root") {
2208
- throw new Error(
2209
- `Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`
2229
+ const resolvedFactory = this.#resolveFactory(ref, pluginId);
2230
+ if (!resolvedFactory) {
2231
+ return ref.multiton ? Promise.resolve([]) : void 0;
2232
+ }
2233
+ return resolvedFactory.then((factories) => {
2234
+ return Promise.all(
2235
+ factories.map((factory) => {
2236
+ if (factory.service.scope === "root") {
2237
+ let existing = this.#rootServiceImplementations.get(factory);
2238
+ if (!existing) {
2239
+ this.#checkForMissingDeps(factory, pluginId);
2240
+ const rootDeps = new Array();
2241
+ for (const [name, serviceRef] of Object.entries(factory.deps)) {
2242
+ if (serviceRef.scope !== "root") {
2243
+ throw new Error(
2244
+ `Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`
2245
+ );
2246
+ }
2247
+ const target = this.get(serviceRef, pluginId);
2248
+ rootDeps.push(target.then((impl) => [name, impl]));
2249
+ }
2250
+ existing = Promise.all(rootDeps).then(
2251
+ (entries) => factory.factory(Object.fromEntries(entries), void 0)
2210
2252
  );
2253
+ this.#rootServiceImplementations.set(factory, existing);
2211
2254
  }
2212
- const target = this.get(serviceRef, pluginId);
2213
- rootDeps.push(target.then((impl) => [name, impl]));
2255
+ return existing;
2214
2256
  }
2215
- existing = Promise.all(rootDeps).then(
2216
- (entries) => factory.factory(Object.fromEntries(entries), void 0)
2217
- );
2218
- this.#rootServiceImplementations.set(factory, existing);
2219
- }
2220
- return existing;
2221
- }
2222
- let implementation = this.#implementations.get(factory);
2223
- if (!implementation) {
2224
- this.#checkForMissingDeps(factory, pluginId);
2225
- const rootDeps = new Array();
2226
- for (const [name, serviceRef] of Object.entries(factory.deps)) {
2227
- if (serviceRef.scope === "root") {
2228
- const target = this.get(serviceRef, pluginId);
2229
- rootDeps.push(target.then((impl) => [name, impl]));
2257
+ let implementation = this.#implementations.get(factory);
2258
+ if (!implementation) {
2259
+ this.#checkForMissingDeps(factory, pluginId);
2260
+ const rootDeps = new Array();
2261
+ for (const [name, serviceRef] of Object.entries(factory.deps)) {
2262
+ if (serviceRef.scope === "root") {
2263
+ const target = this.get(serviceRef, pluginId);
2264
+ rootDeps.push(target.then((impl) => [name, impl]));
2265
+ }
2266
+ }
2267
+ implementation = {
2268
+ context: Promise.all(rootDeps).then(
2269
+ (entries) => factory.createRootContext?.(Object.fromEntries(entries))
2270
+ ).catch((error) => {
2271
+ const cause = errors.stringifyError(error);
2272
+ throw new Error(
2273
+ `Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`
2274
+ );
2275
+ }),
2276
+ byPlugin: /* @__PURE__ */ new Map()
2277
+ };
2278
+ this.#implementations.set(factory, implementation);
2230
2279
  }
2231
- }
2232
- implementation = {
2233
- context: Promise.all(rootDeps).then(
2234
- (entries) => factory.createRootContext?.(Object.fromEntries(entries))
2235
- ).catch((error) => {
2236
- const cause = errors.stringifyError(error);
2237
- throw new Error(
2238
- `Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`
2239
- );
2240
- }),
2241
- byPlugin: /* @__PURE__ */ new Map()
2242
- };
2243
- this.#implementations.set(factory, implementation);
2244
- }
2245
- let result = implementation.byPlugin.get(pluginId);
2246
- if (!result) {
2247
- const allDeps = new Array();
2248
- for (const [name, serviceRef] of Object.entries(factory.deps)) {
2249
- const target = this.get(serviceRef, pluginId);
2250
- allDeps.push(target.then((impl) => [name, impl]));
2251
- }
2252
- result = implementation.context.then(
2253
- (context) => Promise.all(allDeps).then(
2254
- (entries) => factory.factory(Object.fromEntries(entries), context)
2255
- )
2256
- ).catch((error) => {
2257
- const cause = errors.stringifyError(error);
2258
- throw new Error(
2259
- `Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`
2260
- );
2261
- });
2262
- implementation.byPlugin.set(pluginId, result);
2263
- }
2264
- return result;
2265
- });
2280
+ let result = implementation.byPlugin.get(pluginId);
2281
+ if (!result) {
2282
+ const allDeps = new Array();
2283
+ for (const [name, serviceRef] of Object.entries(factory.deps)) {
2284
+ const target = this.get(serviceRef, pluginId);
2285
+ allDeps.push(target.then((impl) => [name, impl]));
2286
+ }
2287
+ result = implementation.context.then(
2288
+ (context) => Promise.all(allDeps).then(
2289
+ (entries) => factory.factory(Object.fromEntries(entries), context)
2290
+ )
2291
+ ).catch((error) => {
2292
+ const cause = errors.stringifyError(error);
2293
+ throw new Error(
2294
+ `Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`
2295
+ );
2296
+ });
2297
+ implementation.byPlugin.set(pluginId, result);
2298
+ }
2299
+ return result;
2300
+ })
2301
+ );
2302
+ }).then((results) => ref.multiton ? results : results[0]);
2266
2303
  }
2267
2304
  }
2268
2305
 
@@ -2308,7 +2345,8 @@ class ServiceFactoryTester {
2308
2345
  */
2309
2346
  async getSubject(...args) {
2310
2347
  const [pluginId] = args;
2311
- return this.#registry.get(this.#subject, pluginId ?? "test");
2348
+ const instance = this.#registry.get(this.#subject, pluginId ?? "test");
2349
+ return instance;
2312
2350
  }
2313
2351
  /**
2314
2352
  * Return the service instance for any of the provided dependencies or built-in services.