@backstage/backend-defaults 0.17.1-next.1 → 0.17.1

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,45 @@
1
1
  # @backstage/backend-defaults
2
2
 
3
+ ## 0.17.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 90b572e: Adds an alpha `TracingService` to provide a unified interface for emitting trace spans across Backstage plugins.
8
+ - 97d3bd4: Fixed a race condition in `CachedUserInfoService` where a failed request could incorrectly evict a newer cache entry for the same token. The error handler now verifies the map entry is still the same promise before deleting it.
9
+ - 3595c97: Exported `defaultServiceFactories` to allow use with `createSpecializedBackend` for advanced configuration like `extensionPointFactoryMiddleware`.
10
+ - 89d3248: Fixed scheduler `sleep` firing immediately for durations longer than ~24.8 days, caused by Node.js `setTimeout` overflowing its 32-bit millisecond limit.
11
+ - d00a44b: Fixed Valkey cluster mode to use `iovalkey`'s `Cluster` class instead of
12
+ `createCluster` from `@keyv/redis`. The previous implementation passed a
13
+ `@redis/client` `RedisCluster` instance to `@keyv/valkey`, which expects an
14
+ `iovalkey` `Cluster` instance. This caused the cluster client to not be
15
+ recognized correctly, as the two libraries have incompatible object models.
16
+ - 2f0519c: Added a new `CachedUserInfoService` decorator that wraps `DefaultUserInfoService` with a 5-second TTL cache and in-flight request coalescing. The decorator is wired in via `userInfoServiceFactory` using a shared root-level cache. Repeated `getUserInfo()` calls for the same user token within the TTL window return the cached result without making an HTTP call to the auth backend. Note that custom `UserInfoService` implementations registered via their own factory will not benefit from this cache automatically.
17
+ - 744fa1f: Removed duplicated entries that appeared in both `dependencies` and `devDependencies`.
18
+ - e9b78e9: Removed the `uuid` dependency and replaced usage with the built-in `crypto.randomUUID()`.
19
+ - 6209065: Added `context` and `propagation` to the alpha `TracingService`. Plugins can bridge OpenTelemetry context across async boundaries via `tracing.propagation.extract(tracing.context.active(), carrier)` followed by `tracing.context.with(ctx, fn)`, and read propagated baggage via `tracing.propagation.getActiveBaggage()` or `tracing.propagation.getBaggage(ctx)`.
20
+ - Updated dependencies
21
+ - @backstage/errors@1.3.1
22
+ - @backstage/integration-aws-node@0.2.0
23
+ - @backstage/backend-plugin-api@1.9.1
24
+ - @backstage/backend-app-api@1.7.0
25
+ - @backstage/cli-node@0.3.2
26
+ - @backstage/integration@2.0.2
27
+ - @backstage/plugin-permission-node@0.11.0
28
+ - @backstage/plugin-auth-node@0.7.1
29
+ - @backstage/plugin-permission-common@0.9.9
30
+ - @backstage/config@1.3.8
31
+ - @backstage/config-loader@1.10.11
32
+ - @backstage/plugin-events-node@0.4.22
33
+
34
+ ## 0.17.1-next.2
35
+
36
+ ### Patch Changes
37
+
38
+ - 90b572e: Adds an alpha `TracingService` to provide a unified interface for emitting trace spans across Backstage plugins.
39
+ - Updated dependencies
40
+ - @backstage/integration-aws-node@0.2.0-next.1
41
+ - @backstage/backend-plugin-api@1.9.1-next.1
42
+
3
43
  ## 0.17.1-next.1
4
44
 
5
45
  ### Patch Changes
package/config.d.ts CHANGED
@@ -1186,6 +1186,52 @@ export interface Config {
1186
1186
  };
1187
1187
  };
1188
1188
 
1189
+ /**
1190
+ * Tracing-related backend configuration. Honored by Backstage backend
1191
+ * plugins that emit OpenTelemetry trace spans.
1192
+ */
1193
+ tracing?: {
1194
+ /**
1195
+ * Opt-in capture of attributes that may identify users or contain
1196
+ * sensitive data on backend trace spans.
1197
+ */
1198
+ capture?: {
1199
+ /**
1200
+ * When true, backend plugins emitting trace spans for authenticated
1201
+ * requests SHOULD include the authenticated principal's identity as
1202
+ * `enduser.id` (the user entity ref for a user principal, or the
1203
+ * service subject for a service principal). Defaults to false.
1204
+ */
1205
+ endUser?: boolean;
1206
+ };
1207
+ /**
1208
+ * Plugin-specific tracing configuration. Each plugin can override
1209
+ * tracer instrumentation scope metadata.
1210
+ */
1211
+ plugin?: {
1212
+ [pluginId: string]: {
1213
+ /**
1214
+ * Tracer configuration for this plugin.
1215
+ */
1216
+ tracer?: {
1217
+ /**
1218
+ * Custom tracer name. If not set, defaults to
1219
+ * backstage-plugin-{pluginId}.
1220
+ */
1221
+ name?: string;
1222
+ /**
1223
+ * Version for the tracer.
1224
+ */
1225
+ version?: string;
1226
+ /**
1227
+ * Schema URL for the tracer.
1228
+ */
1229
+ schemaUrl?: string;
1230
+ };
1231
+ };
1232
+ };
1233
+ };
1234
+
1189
1235
  /**
1190
1236
  * Options to configure the default RootLoggerService.
1191
1237
  */
@@ -49,6 +49,7 @@ const defaultServiceFactories = [
49
49
  alpha.actionsRegistryServiceFactory,
50
50
  alpha.actionsServiceFactory,
51
51
  alpha.metricsServiceFactory,
52
+ alpha.tracingServiceFactory,
52
53
  // Unexported alpha services kept around for compatibility reasons
53
54
  instanceMetadataServiceFactory.instanceMetadataServiceFactory
54
55
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"CreateBackend.cjs.js","sources":["../src/CreateBackend.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Backend, createSpecializedBackend } from '@backstage/backend-app-api';\nimport { ServiceFactory } from '@backstage/backend-plugin-api';\nimport { auditorServiceFactory } from '@backstage/backend-defaults/auditor';\nimport { authServiceFactory } from '@backstage/backend-defaults/auth';\nimport { cacheServiceFactory } from '@backstage/backend-defaults/cache';\nimport { databaseServiceFactory } from '@backstage/backend-defaults/database';\nimport { discoveryServiceFactory } from '@backstage/backend-defaults/discovery';\nimport { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth';\nimport { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter';\nimport { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';\nimport { loggerServiceFactory } from '@backstage/backend-defaults/logger';\nimport { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';\nimport { permissionsRegistryServiceFactory } from '@backstage/backend-defaults/permissionsRegistry';\nimport { rootConfigServiceFactory } from '@backstage/backend-defaults/rootConfig';\nimport { rootHealthServiceFactory } from '@backstage/backend-defaults/rootHealth';\nimport { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';\nimport { rootLoggerServiceFactory } from '@backstage/backend-defaults/rootLogger';\nimport { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler';\nimport { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader';\nimport { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo';\nimport { eventsServiceFactory } from '@backstage/plugin-events-node';\nimport {\n actionsRegistryServiceFactory,\n actionsServiceFactory,\n metricsServiceFactory,\n} from '@backstage/backend-defaults/alpha';\nimport { instanceMetadataServiceFactory } from './alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory';\n\n/** @public */\nexport const defaultServiceFactories: ServiceFactory[] = [\n auditorServiceFactory,\n authServiceFactory,\n cacheServiceFactory,\n rootConfigServiceFactory,\n databaseServiceFactory,\n discoveryServiceFactory,\n httpAuthServiceFactory,\n httpRouterServiceFactory,\n lifecycleServiceFactory,\n loggerServiceFactory,\n permissionsServiceFactory,\n permissionsRegistryServiceFactory,\n rootHealthServiceFactory,\n rootHttpRouterServiceFactory,\n rootLifecycleServiceFactory,\n rootLoggerServiceFactory,\n schedulerServiceFactory,\n userInfoServiceFactory,\n urlReaderServiceFactory,\n eventsServiceFactory,\n\n // alpha services\n actionsRegistryServiceFactory,\n actionsServiceFactory,\n metricsServiceFactory,\n\n // Unexported alpha services kept around for compatibility reasons\n instanceMetadataServiceFactory,\n];\n\n/**\n * @public\n */\nexport function createBackend(): Backend {\n return createSpecializedBackend({ defaultServiceFactories });\n}\n"],"names":["auditorServiceFactory","authServiceFactory","cacheServiceFactory","rootConfigServiceFactory","databaseServiceFactory","discoveryServiceFactory","httpAuthServiceFactory","httpRouterServiceFactory","lifecycleServiceFactory","loggerServiceFactory","permissionsServiceFactory","permissionsRegistryServiceFactory","rootHealthServiceFactory","rootHttpRouterServiceFactory","rootLifecycleServiceFactory","rootLoggerServiceFactory","schedulerServiceFactory","userInfoServiceFactory","urlReaderServiceFactory","eventsServiceFactory","actionsRegistryServiceFactory","actionsServiceFactory","metricsServiceFactory","instanceMetadataServiceFactory","createSpecializedBackend"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8CO,MAAM,uBAAA,GAA4C;AAAA,EACvDA,6BAAA;AAAA,EACAC,uBAAA;AAAA,EACAC,yBAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,+BAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,+BAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,2BAAA;AAAA,EACAC,qCAAA;AAAA,EACAC,qDAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,2CAAA;AAAA,EACAC,yCAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,+BAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,qCAAA;AAAA;AAAA,EAGAC,mCAAA;AAAA,EACAC,2BAAA;AAAA,EACAC,2BAAA;AAAA;AAAA,EAGAC;AACF;AAKO,SAAS,aAAA,GAAyB;AACvC,EAAA,OAAOC,sCAAA,CAAyB,EAAE,uBAAA,EAAyB,CAAA;AAC7D;;;;;"}
1
+ {"version":3,"file":"CreateBackend.cjs.js","sources":["../src/CreateBackend.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Backend, createSpecializedBackend } from '@backstage/backend-app-api';\nimport { ServiceFactory } from '@backstage/backend-plugin-api';\nimport { auditorServiceFactory } from '@backstage/backend-defaults/auditor';\nimport { authServiceFactory } from '@backstage/backend-defaults/auth';\nimport { cacheServiceFactory } from '@backstage/backend-defaults/cache';\nimport { databaseServiceFactory } from '@backstage/backend-defaults/database';\nimport { discoveryServiceFactory } from '@backstage/backend-defaults/discovery';\nimport { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth';\nimport { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter';\nimport { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';\nimport { loggerServiceFactory } from '@backstage/backend-defaults/logger';\nimport { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';\nimport { permissionsRegistryServiceFactory } from '@backstage/backend-defaults/permissionsRegistry';\nimport { rootConfigServiceFactory } from '@backstage/backend-defaults/rootConfig';\nimport { rootHealthServiceFactory } from '@backstage/backend-defaults/rootHealth';\nimport { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';\nimport { rootLoggerServiceFactory } from '@backstage/backend-defaults/rootLogger';\nimport { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler';\nimport { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader';\nimport { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo';\nimport { eventsServiceFactory } from '@backstage/plugin-events-node';\nimport {\n actionsRegistryServiceFactory,\n actionsServiceFactory,\n metricsServiceFactory,\n tracingServiceFactory,\n} from '@backstage/backend-defaults/alpha';\nimport { instanceMetadataServiceFactory } from './alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory';\n\n/** @public */\nexport const defaultServiceFactories: ServiceFactory[] = [\n auditorServiceFactory,\n authServiceFactory,\n cacheServiceFactory,\n rootConfigServiceFactory,\n databaseServiceFactory,\n discoveryServiceFactory,\n httpAuthServiceFactory,\n httpRouterServiceFactory,\n lifecycleServiceFactory,\n loggerServiceFactory,\n permissionsServiceFactory,\n permissionsRegistryServiceFactory,\n rootHealthServiceFactory,\n rootHttpRouterServiceFactory,\n rootLifecycleServiceFactory,\n rootLoggerServiceFactory,\n schedulerServiceFactory,\n userInfoServiceFactory,\n urlReaderServiceFactory,\n eventsServiceFactory,\n\n // alpha services\n actionsRegistryServiceFactory,\n actionsServiceFactory,\n metricsServiceFactory,\n tracingServiceFactory,\n\n // Unexported alpha services kept around for compatibility reasons\n instanceMetadataServiceFactory,\n];\n\n/**\n * @public\n */\nexport function createBackend(): Backend {\n return createSpecializedBackend({ defaultServiceFactories });\n}\n"],"names":["auditorServiceFactory","authServiceFactory","cacheServiceFactory","rootConfigServiceFactory","databaseServiceFactory","discoveryServiceFactory","httpAuthServiceFactory","httpRouterServiceFactory","lifecycleServiceFactory","loggerServiceFactory","permissionsServiceFactory","permissionsRegistryServiceFactory","rootHealthServiceFactory","rootHttpRouterServiceFactory","rootLifecycleServiceFactory","rootLoggerServiceFactory","schedulerServiceFactory","userInfoServiceFactory","urlReaderServiceFactory","eventsServiceFactory","actionsRegistryServiceFactory","actionsServiceFactory","metricsServiceFactory","tracingServiceFactory","instanceMetadataServiceFactory","createSpecializedBackend"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA+CO,MAAM,uBAAA,GAA4C;AAAA,EACvDA,6BAAA;AAAA,EACAC,uBAAA;AAAA,EACAC,yBAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,+BAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,+BAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,2BAAA;AAAA,EACAC,qCAAA;AAAA,EACAC,qDAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,2CAAA;AAAA,EACAC,yCAAA;AAAA,EACAC,mCAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,+BAAA;AAAA,EACAC,iCAAA;AAAA,EACAC,qCAAA;AAAA;AAAA,EAGAC,mCAAA;AAAA,EACAC,2BAAA;AAAA,EACAC,2BAAA;AAAA,EACAC,2BAAA;AAAA;AAAA,EAGAC;AACF;AAKO,SAAS,aAAA,GAAyB;AACvC,EAAA,OAAOC,sCAAA,CAAyB,EAAE,uBAAA,EAAyB,CAAA;AAC7D;;;;;"}
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ var api = require('@opentelemetry/api');
4
+
5
+ function toOtelContext(ctx) {
6
+ return ctx;
7
+ }
8
+ function fromOtelContext(ctx) {
9
+ return ctx;
10
+ }
11
+ function wrapOtelBaggage(baggage) {
12
+ if (!baggage) return void 0;
13
+ return {
14
+ getAllEntries: () => baggage.getAllEntries().map(([key, entry]) => [key, { value: entry.value }])
15
+ };
16
+ }
17
+ class DefaultTracingService {
18
+ tracer;
19
+ pluginId;
20
+ captureEndUser;
21
+ httpAuth;
22
+ context = {
23
+ active: () => fromOtelContext(api.context.active()),
24
+ // `otelContext.with` is synchronous: it activates `ctx`, invokes `fn`,
25
+ // then restores the previous active context before this call returns.
26
+ // When `fn` is async, the AsyncLocalStorage context manager installed
27
+ // by the OTel SDK is what keeps `ctx` active across the callback's
28
+ // `await`s. If no context manager is registered (e.g. in a test that
29
+ // does not wire up the OTel SDK) the `await` continuations will run
30
+ // outside `ctx`.
31
+ with: async (ctx, fn) => api.context.with(toOtelContext(ctx), fn)
32
+ };
33
+ propagation = {
34
+ extract: (ctx, carrier) => fromOtelContext(api.propagation.extract(toOtelContext(ctx), carrier)),
35
+ getBaggage: (ctx) => wrapOtelBaggage(api.propagation.getBaggage(toOtelContext(ctx))),
36
+ getActiveBaggage: () => wrapOtelBaggage(api.propagation.getActiveBaggage())
37
+ };
38
+ constructor(opts) {
39
+ this.tracer = api.trace.getTracerProvider().getTracer(opts.name, opts.version, { schemaUrl: opts.schemaUrl });
40
+ this.pluginId = opts.pluginId;
41
+ this.captureEndUser = opts.captureEndUser;
42
+ this.httpAuth = opts.httpAuth;
43
+ }
44
+ static create(opts) {
45
+ return new DefaultTracingService(opts);
46
+ }
47
+ async startActiveSpan(name, optionsOrFn, maybeFn) {
48
+ const [options, fn] = typeof optionsOrFn === "function" ? [{}, optionsOrFn] : [optionsOrFn, maybeFn];
49
+ let credentials = options.credentials;
50
+ if (!credentials && options.request) {
51
+ credentials = await this.httpAuth.credentials(options.request);
52
+ }
53
+ const principalAttributes = this.getPrincipalAttributes(credentials);
54
+ const attributes = {
55
+ "backstage.plugin.id": this.pluginId,
56
+ ...options.attributes,
57
+ ...principalAttributes
58
+ };
59
+ return this.tracer.startActiveSpan(
60
+ name,
61
+ { kind: toSpanKind(options.kind), attributes },
62
+ async (span) => {
63
+ try {
64
+ const wrapped = {
65
+ setAttribute(key, value) {
66
+ span.setAttribute(key, value);
67
+ },
68
+ setStatus(status) {
69
+ span.setStatus({
70
+ code: toSpanStatusCode(status.code),
71
+ message: status.message
72
+ });
73
+ }
74
+ };
75
+ const result = await fn(wrapped);
76
+ span.end();
77
+ return result;
78
+ } catch (err) {
79
+ const error = err;
80
+ span.recordException(error);
81
+ span.setAttribute("error.type", error.name || "Error");
82
+ span.setStatus({
83
+ code: api.SpanStatusCode.ERROR,
84
+ message: error.message || String(error)
85
+ });
86
+ span.end();
87
+ throw err;
88
+ }
89
+ }
90
+ );
91
+ }
92
+ getPrincipalAttributes(credentials) {
93
+ if (!credentials) return {};
94
+ const principal = credentials.principal;
95
+ if (!principal?.type) return {};
96
+ const attrs = {
97
+ "backstage.principal.type": principal.type
98
+ };
99
+ if (!this.captureEndUser) return attrs;
100
+ if (principal.type === "user" && principal.userEntityRef) {
101
+ attrs["enduser.id"] = principal.userEntityRef;
102
+ } else if (principal.type === "service" && principal.subject) {
103
+ attrs["enduser.id"] = principal.subject;
104
+ }
105
+ return attrs;
106
+ }
107
+ }
108
+ function toSpanKind(kind) {
109
+ switch (kind) {
110
+ case "internal":
111
+ return api.SpanKind.INTERNAL;
112
+ case "server":
113
+ return api.SpanKind.SERVER;
114
+ case "client":
115
+ return api.SpanKind.CLIENT;
116
+ case "producer":
117
+ return api.SpanKind.PRODUCER;
118
+ case "consumer":
119
+ return api.SpanKind.CONSUMER;
120
+ default:
121
+ return void 0;
122
+ }
123
+ }
124
+ function toSpanStatusCode(code) {
125
+ switch (code) {
126
+ case "ok":
127
+ return api.SpanStatusCode.OK;
128
+ case "error":
129
+ return api.SpanStatusCode.ERROR;
130
+ default:
131
+ return api.SpanStatusCode.UNSET;
132
+ }
133
+ }
134
+
135
+ exports.DefaultTracingService = DefaultTracingService;
136
+ //# sourceMappingURL=DefaultTracingService.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultTracingService.cjs.js","sources":["../../../../src/alpha/entrypoints/tracing/DefaultTracingService.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Context,\n SpanKind,\n SpanStatusCode,\n Tracer,\n context as otelContext,\n propagation as otelPropagation,\n trace,\n} from '@opentelemetry/api';\nimport {\n BackstageCredentials,\n HttpAuthService,\n} from '@backstage/backend-plugin-api';\nimport {\n TracingService,\n TracingServiceAttributes,\n TracingServiceBaggage,\n TracingServiceContext,\n TracingServiceContextAPI,\n TracingServicePropagationAPI,\n TracingServiceSpan,\n TracingServiceSpanKind,\n TracingServiceSpanOptions,\n TracingServiceSpanStatus,\n} from '@backstage/backend-plugin-api/alpha';\n\n/**\n * Options for creating a {@link DefaultTracingService}.\n *\n * @alpha\n */\nexport interface DefaultTracingServiceOptions {\n name: string;\n version?: string;\n schemaUrl?: string;\n pluginId: string;\n captureEndUser: boolean;\n httpAuth: HttpAuthService;\n}\n\n// `TracingServiceContext` is an opaque handle for an OTel `Context`. Internally\n// the value *is* the OTel context; we just narrow the type so consumers can't\n// poke at it directly.\nfunction toOtelContext(ctx: TracingServiceContext): Context {\n return ctx as unknown as Context;\n}\nfunction fromOtelContext(ctx: Context): TracingServiceContext {\n return ctx as unknown as TracingServiceContext;\n}\n\nfunction wrapOtelBaggage(\n baggage: ReturnType<typeof otelPropagation.getActiveBaggage>,\n): TracingServiceBaggage | undefined {\n if (!baggage) return undefined;\n return {\n getAllEntries: () =>\n baggage\n .getAllEntries()\n .map(([key, entry]) => [key, { value: entry.value }]),\n };\n}\n\n/**\n * Default implementation of the {@link TracingService} interface.\n *\n * @alpha\n */\nexport class DefaultTracingService implements TracingService {\n private readonly tracer: Tracer;\n private readonly pluginId: string;\n private readonly captureEndUser: boolean;\n private readonly httpAuth: HttpAuthService;\n\n readonly context: TracingServiceContextAPI = {\n active: () => fromOtelContext(otelContext.active()),\n // `otelContext.with` is synchronous: it activates `ctx`, invokes `fn`,\n // then restores the previous active context before this call returns.\n // When `fn` is async, the AsyncLocalStorage context manager installed\n // by the OTel SDK is what keeps `ctx` active across the callback's\n // `await`s. If no context manager is registered (e.g. in a test that\n // does not wire up the OTel SDK) the `await` continuations will run\n // outside `ctx`.\n with: async <T>(\n ctx: TracingServiceContext,\n fn: () => T | Promise<T>,\n ): Promise<T> => otelContext.with(toOtelContext(ctx), fn),\n };\n\n readonly propagation: TracingServicePropagationAPI = {\n extract: (\n ctx: TracingServiceContext,\n carrier: Record<string, string | string[] | undefined>,\n ): TracingServiceContext =>\n fromOtelContext(otelPropagation.extract(toOtelContext(ctx), carrier)),\n getBaggage: (ctx: TracingServiceContext) =>\n wrapOtelBaggage(otelPropagation.getBaggage(toOtelContext(ctx))),\n getActiveBaggage: () => wrapOtelBaggage(otelPropagation.getActiveBaggage()),\n };\n\n private constructor(opts: DefaultTracingServiceOptions) {\n this.tracer = trace\n .getTracerProvider()\n .getTracer(opts.name, opts.version, { schemaUrl: opts.schemaUrl });\n this.pluginId = opts.pluginId;\n this.captureEndUser = opts.captureEndUser;\n this.httpAuth = opts.httpAuth;\n }\n\n static create(opts: DefaultTracingServiceOptions): TracingService {\n return new DefaultTracingService(opts);\n }\n\n startActiveSpan<T>(\n name: string,\n fn: (span: TracingServiceSpan) => T | Promise<T>,\n ): Promise<T>;\n startActiveSpan<T>(\n name: string,\n options: TracingServiceSpanOptions,\n fn: (span: TracingServiceSpan) => T | Promise<T>,\n ): Promise<T>;\n async startActiveSpan<T>(\n name: string,\n optionsOrFn:\n | TracingServiceSpanOptions\n | ((span: TracingServiceSpan) => T | Promise<T>),\n maybeFn?: (span: TracingServiceSpan) => T | Promise<T>,\n ): Promise<T> {\n const [options, fn]: [\n TracingServiceSpanOptions,\n (span: TracingServiceSpan) => T | Promise<T>,\n ] =\n typeof optionsOrFn === 'function'\n ? [{}, optionsOrFn]\n : [optionsOrFn, maybeFn!];\n\n let credentials = options.credentials;\n if (!credentials && options.request) {\n credentials = await this.httpAuth.credentials(options.request);\n }\n\n const principalAttributes = this.getPrincipalAttributes(credentials);\n const attributes: TracingServiceAttributes = {\n 'backstage.plugin.id': this.pluginId,\n ...options.attributes,\n ...principalAttributes,\n };\n\n return this.tracer.startActiveSpan(\n name,\n { kind: toSpanKind(options.kind), attributes },\n async span => {\n try {\n const wrapped: TracingServiceSpan = {\n setAttribute(key, value) {\n span.setAttribute(key, value);\n },\n setStatus(status) {\n span.setStatus({\n code: toSpanStatusCode(status.code),\n message: status.message,\n });\n },\n };\n const result = await fn(wrapped);\n span.end();\n return result;\n } catch (err) {\n const error = err as Error;\n span.recordException(error);\n span.setAttribute('error.type', error.name || 'Error');\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message || String(error),\n });\n span.end();\n throw err;\n }\n },\n );\n }\n\n private getPrincipalAttributes(\n credentials: BackstageCredentials | undefined,\n ): TracingServiceAttributes {\n if (!credentials) return {};\n const principal = credentials.principal as\n | { type?: string; userEntityRef?: string; subject?: string }\n | undefined;\n if (!principal?.type) return {};\n const attrs: TracingServiceAttributes = {\n 'backstage.principal.type': principal.type,\n };\n if (!this.captureEndUser) return attrs;\n if (principal.type === 'user' && principal.userEntityRef) {\n attrs['enduser.id'] = principal.userEntityRef;\n } else if (principal.type === 'service' && principal.subject) {\n attrs['enduser.id'] = principal.subject;\n }\n return attrs;\n }\n}\n\nfunction toSpanKind(\n kind: TracingServiceSpanKind | undefined,\n): SpanKind | undefined {\n switch (kind) {\n case 'internal':\n return SpanKind.INTERNAL;\n case 'server':\n return SpanKind.SERVER;\n case 'client':\n return SpanKind.CLIENT;\n case 'producer':\n return SpanKind.PRODUCER;\n case 'consumer':\n return SpanKind.CONSUMER;\n default:\n return undefined;\n }\n}\n\nfunction toSpanStatusCode(\n code: TracingServiceSpanStatus['code'],\n): SpanStatusCode {\n switch (code) {\n case 'ok':\n return SpanStatusCode.OK;\n case 'error':\n return SpanStatusCode.ERROR;\n default:\n return SpanStatusCode.UNSET;\n }\n}\n"],"names":["otelContext","otelPropagation","trace","SpanStatusCode","SpanKind"],"mappings":";;;;AA2DA,SAAS,cAAc,GAAA,EAAqC;AAC1D,EAAA,OAAO,GAAA;AACT;AACA,SAAS,gBAAgB,GAAA,EAAqC;AAC5D,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,gBACP,OAAA,EACmC;AACnC,EAAA,IAAI,CAAC,SAAS,OAAO,MAAA;AACrB,EAAA,OAAO;AAAA,IACL,eAAe,MACb,OAAA,CACG,eAAc,CACd,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM,CAAC,GAAA,EAAK,EAAE,OAAO,KAAA,CAAM,KAAA,EAAO,CAAC;AAAA,GAC1D;AACF;AAOO,MAAM,qBAAA,CAAgD;AAAA,EAC1C,MAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,QAAA;AAAA,EAER,OAAA,GAAoC;AAAA,IAC3C,MAAA,EAAQ,MAAM,eAAA,CAAgBA,WAAA,CAAY,QAAQ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQlD,IAAA,EAAM,OACJ,GAAA,EACA,EAAA,KACeA,YAAY,IAAA,CAAK,aAAA,CAAc,GAAG,CAAA,EAAG,EAAE;AAAA,GAC1D;AAAA,EAES,WAAA,GAA4C;AAAA,IACnD,OAAA,EAAS,CACP,GAAA,EACA,OAAA,KAEA,eAAA,CAAgBC,eAAA,CAAgB,OAAA,CAAQ,aAAA,CAAc,GAAG,CAAA,EAAG,OAAO,CAAC,CAAA;AAAA,IACtE,UAAA,EAAY,CAAC,GAAA,KACX,eAAA,CAAgBA,gBAAgB,UAAA,CAAW,aAAA,CAAc,GAAG,CAAC,CAAC,CAAA;AAAA,IAChE,gBAAA,EAAkB,MAAM,eAAA,CAAgBA,eAAA,CAAgB,kBAAkB;AAAA,GAC5E;AAAA,EAEQ,YAAY,IAAA,EAAoC;AACtD,IAAA,IAAA,CAAK,MAAA,GAASC,SAAA,CACX,iBAAA,EAAkB,CAClB,SAAA,CAAU,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,OAAA,EAAS,EAAE,SAAA,EAAW,IAAA,CAAK,WAAW,CAAA;AACnE,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AACrB,IAAA,IAAA,CAAK,iBAAiB,IAAA,CAAK,cAAA;AAC3B,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AAAA,EACvB;AAAA,EAEA,OAAO,OAAO,IAAA,EAAoD;AAChE,IAAA,OAAO,IAAI,sBAAsB,IAAI,CAAA;AAAA,EACvC;AAAA,EAWA,MAAM,eAAA,CACJ,IAAA,EACA,WAAA,EAGA,OAAA,EACY;AACZ,IAAA,MAAM,CAAC,OAAA,EAAS,EAAE,CAAA,GAIhB,OAAO,WAAA,KAAgB,UAAA,GACnB,CAAC,EAAC,EAAG,WAAW,CAAA,GAChB,CAAC,aAAa,OAAQ,CAAA;AAE5B,IAAA,IAAI,cAAc,OAAA,CAAQ,WAAA;AAC1B,IAAA,IAAI,CAAC,WAAA,IAAe,OAAA,CAAQ,OAAA,EAAS;AACnC,MAAA,WAAA,GAAc,MAAM,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,QAAQ,OAAO,CAAA;AAAA,IAC/D;AAEA,IAAA,MAAM,mBAAA,GAAsB,IAAA,CAAK,sBAAA,CAAuB,WAAW,CAAA;AACnE,IAAA,MAAM,UAAA,GAAuC;AAAA,MAC3C,uBAAuB,IAAA,CAAK,QAAA;AAAA,MAC5B,GAAG,OAAA,CAAQ,UAAA;AAAA,MACX,GAAG;AAAA,KACL;AAEA,IAAA,OAAO,KAAK,MAAA,CAAO,eAAA;AAAA,MACjB,IAAA;AAAA,MACA,EAAE,IAAA,EAAM,UAAA,CAAW,OAAA,CAAQ,IAAI,GAAG,UAAA,EAAW;AAAA,MAC7C,OAAM,IAAA,KAAQ;AACZ,QAAA,IAAI;AACF,UAAA,MAAM,OAAA,GAA8B;AAAA,YAClC,YAAA,CAAa,KAAK,KAAA,EAAO;AACvB,cAAA,IAAA,CAAK,YAAA,CAAa,KAAK,KAAK,CAAA;AAAA,YAC9B,CAAA;AAAA,YACA,UAAU,MAAA,EAAQ;AAChB,cAAA,IAAA,CAAK,SAAA,CAAU;AAAA,gBACb,IAAA,EAAM,gBAAA,CAAiB,MAAA,CAAO,IAAI,CAAA;AAAA,gBAClC,SAAS,MAAA,CAAO;AAAA,eACjB,CAAA;AAAA,YACH;AAAA,WACF;AACA,UAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,OAAO,CAAA;AAC/B,UAAA,IAAA,CAAK,GAAA,EAAI;AACT,UAAA,OAAO,MAAA;AAAA,QACT,SAAS,GAAA,EAAK;AACZ,UAAA,MAAM,KAAA,GAAQ,GAAA;AACd,UAAA,IAAA,CAAK,gBAAgB,KAAK,CAAA;AAC1B,UAAA,IAAA,CAAK,YAAA,CAAa,YAAA,EAAc,KAAA,CAAM,IAAA,IAAQ,OAAO,CAAA;AACrD,UAAA,IAAA,CAAK,SAAA,CAAU;AAAA,YACb,MAAMC,kBAAA,CAAe,KAAA;AAAA,YACrB,OAAA,EAAS,KAAA,CAAM,OAAA,IAAW,MAAA,CAAO,KAAK;AAAA,WACvC,CAAA;AACD,UAAA,IAAA,CAAK,GAAA,EAAI;AACT,UAAA,MAAM,GAAA;AAAA,QACR;AAAA,MACF;AAAA,KACF;AAAA,EACF;AAAA,EAEQ,uBACN,WAAA,EAC0B;AAC1B,IAAA,IAAI,CAAC,WAAA,EAAa,OAAO,EAAC;AAC1B,IAAA,MAAM,YAAY,WAAA,CAAY,SAAA;AAG9B,IAAA,IAAI,CAAC,SAAA,EAAW,IAAA,EAAM,OAAO,EAAC;AAC9B,IAAA,MAAM,KAAA,GAAkC;AAAA,MACtC,4BAA4B,SAAA,CAAU;AAAA,KACxC;AACA,IAAA,IAAI,CAAC,IAAA,CAAK,cAAA,EAAgB,OAAO,KAAA;AACjC,IAAA,IAAI,SAAA,CAAU,IAAA,KAAS,MAAA,IAAU,SAAA,CAAU,aAAA,EAAe;AACxD,MAAA,KAAA,CAAM,YAAY,IAAI,SAAA,CAAU,aAAA;AAAA,IAClC,CAAA,MAAA,IAAW,SAAA,CAAU,IAAA,KAAS,SAAA,IAAa,UAAU,OAAA,EAAS;AAC5D,MAAA,KAAA,CAAM,YAAY,IAAI,SAAA,CAAU,OAAA;AAAA,IAClC;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAEA,SAAS,WACP,IAAA,EACsB;AACtB,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,UAAA;AACH,MAAA,OAAOC,YAAA,CAAS,QAAA;AAAA,IAClB,KAAK,QAAA;AACH,MAAA,OAAOA,YAAA,CAAS,MAAA;AAAA,IAClB,KAAK,QAAA;AACH,MAAA,OAAOA,YAAA,CAAS,MAAA;AAAA,IAClB,KAAK,UAAA;AACH,MAAA,OAAOA,YAAA,CAAS,QAAA;AAAA,IAClB,KAAK,UAAA;AACH,MAAA,OAAOA,YAAA,CAAS,QAAA;AAAA,IAClB;AACE,MAAA,OAAO,MAAA;AAAA;AAEb;AAEA,SAAS,iBACP,IAAA,EACgB;AAChB,EAAA,QAAQ,IAAA;AAAM,IACZ,KAAK,IAAA;AACH,MAAA,OAAOD,kBAAA,CAAe,EAAA;AAAA,IACxB,KAAK,OAAA;AACH,MAAA,OAAOA,kBAAA,CAAe,KAAA;AAAA,IACxB;AACE,MAAA,OAAOA,kBAAA,CAAe,KAAA;AAAA;AAE5B;;;;"}
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ var alpha = require('@backstage/backend-plugin-api/alpha');
4
+ var backendPluginApi = require('@backstage/backend-plugin-api');
5
+ var DefaultTracingService = require('./DefaultTracingService.cjs.js');
6
+
7
+ const tracingServiceFactory = backendPluginApi.createServiceFactory({
8
+ service: alpha.tracingServiceRef,
9
+ deps: {
10
+ config: backendPluginApi.coreServices.rootConfig,
11
+ pluginMetadata: backendPluginApi.coreServices.pluginMetadata,
12
+ httpAuth: backendPluginApi.coreServices.httpAuth
13
+ },
14
+ factory: ({ config, pluginMetadata, httpAuth }) => {
15
+ const pluginId = pluginMetadata.getId();
16
+ const tracerConfig = config.getOptionalConfig(
17
+ `backend.tracing.plugin.${pluginId}.tracer`
18
+ );
19
+ const scopeName = `backstage-plugin-${pluginId}`;
20
+ const name = tracerConfig?.getOptionalString("name") ?? scopeName;
21
+ const version = tracerConfig?.getOptionalString("version");
22
+ const schemaUrl = tracerConfig?.getOptionalString("schemaUrl");
23
+ const captureEndUser = config.getOptionalBoolean("backend.tracing.capture.endUser") ?? false;
24
+ return DefaultTracingService.DefaultTracingService.create({
25
+ name,
26
+ version,
27
+ schemaUrl,
28
+ pluginId,
29
+ captureEndUser,
30
+ httpAuth
31
+ });
32
+ }
33
+ });
34
+
35
+ exports.tracingServiceFactory = tracingServiceFactory;
36
+ //# sourceMappingURL=tracingServiceFactory.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracingServiceFactory.cjs.js","sources":["../../../../src/alpha/entrypoints/tracing/tracingServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { tracingServiceRef } from '@backstage/backend-plugin-api/alpha';\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultTracingService } from './DefaultTracingService';\n\n/**\n * Service factory for emitting plugin-scoped trace spans.\n *\n * @alpha\n */\nexport const tracingServiceFactory = createServiceFactory({\n service: tracingServiceRef,\n deps: {\n config: coreServices.rootConfig,\n pluginMetadata: coreServices.pluginMetadata,\n httpAuth: coreServices.httpAuth,\n },\n factory: ({ config, pluginMetadata, httpAuth }) => {\n const pluginId = pluginMetadata.getId();\n\n const tracerConfig = config.getOptionalConfig(\n `backend.tracing.plugin.${pluginId}.tracer`,\n );\n const scopeName = `backstage-plugin-${pluginId}`;\n const name = tracerConfig?.getOptionalString('name') ?? scopeName;\n const version = tracerConfig?.getOptionalString('version');\n const schemaUrl = tracerConfig?.getOptionalString('schemaUrl');\n\n const captureEndUser =\n config.getOptionalBoolean('backend.tracing.capture.endUser') ?? false;\n\n return DefaultTracingService.create({\n name,\n version,\n schemaUrl,\n pluginId,\n captureEndUser,\n httpAuth,\n });\n },\n});\n"],"names":["createServiceFactory","tracingServiceRef","coreServices","DefaultTracingService"],"mappings":";;;;;;AA4BO,MAAM,wBAAwBA,qCAAA,CAAqB;AAAA,EACxD,OAAA,EAASC,uBAAA;AAAA,EACT,IAAA,EAAM;AAAA,IACJ,QAAQC,6BAAA,CAAa,UAAA;AAAA,IACrB,gBAAgBA,6BAAA,CAAa,cAAA;AAAA,IAC7B,UAAUA,6BAAA,CAAa;AAAA,GACzB;AAAA,EACA,SAAS,CAAC,EAAE,MAAA,EAAQ,cAAA,EAAgB,UAAS,KAAM;AACjD,IAAA,MAAM,QAAA,GAAW,eAAe,KAAA,EAAM;AAEtC,IAAA,MAAM,eAAe,MAAA,CAAO,iBAAA;AAAA,MAC1B,0BAA0B,QAAQ,CAAA,OAAA;AAAA,KACpC;AACA,IAAA,MAAM,SAAA,GAAY,oBAAoB,QAAQ,CAAA,CAAA;AAC9C,IAAA,MAAM,IAAA,GAAO,YAAA,EAAc,iBAAA,CAAkB,MAAM,CAAA,IAAK,SAAA;AACxD,IAAA,MAAM,OAAA,GAAU,YAAA,EAAc,iBAAA,CAAkB,SAAS,CAAA;AACzD,IAAA,MAAM,SAAA,GAAY,YAAA,EAAc,iBAAA,CAAkB,WAAW,CAAA;AAE7D,IAAA,MAAM,cAAA,GACJ,MAAA,CAAO,kBAAA,CAAmB,iCAAiC,CAAA,IAAK,KAAA;AAElE,IAAA,OAAOC,4CAAsB,MAAA,CAAO;AAAA,MAClC,IAAA;AAAA,MACA,OAAA;AAAA,MACA,SAAA;AAAA,MACA,QAAA;AAAA,MACA,cAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
package/dist/alpha.cjs.js CHANGED
@@ -4,6 +4,7 @@ var actionsRegistryServiceFactory = require('./alpha/entrypoints/actionsRegistry
4
4
  var actionsServiceFactory = require('./alpha/entrypoints/actions/actionsServiceFactory.cjs.js');
5
5
  var metricsServiceFactory = require('./alpha/entrypoints/metrics/metricsServiceFactory.cjs.js');
6
6
  var rootSystemMetadataServiceFactory = require('./alpha/entrypoints/rootSystemMetadata/rootSystemMetadataServiceFactory.cjs.js');
7
+ var tracingServiceFactory = require('./alpha/entrypoints/tracing/tracingServiceFactory.cjs.js');
7
8
 
8
9
 
9
10
 
@@ -11,4 +12,5 @@ exports.actionsRegistryServiceFactory = actionsRegistryServiceFactory.actionsReg
11
12
  exports.actionsServiceFactory = actionsServiceFactory.actionsServiceFactory;
12
13
  exports.metricsServiceFactory = metricsServiceFactory.metricsServiceFactory;
13
14
  exports.rootSystemMetadataServiceFactory = rootSystemMetadataServiceFactory.rootSystemMetadataServiceFactory;
15
+ exports.tracingServiceFactory = tracingServiceFactory.tracingServiceFactory;
14
16
  //# sourceMappingURL=alpha.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
1
+ {"version":3,"file":"alpha.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
package/dist/alpha.d.ts CHANGED
@@ -25,4 +25,11 @@ declare const metricsServiceFactory: _backstage_backend_plugin_api.ServiceFactor
25
25
  */
26
26
  declare const rootSystemMetadataServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api_alpha.RootSystemMetadataService, "root", "singleton">;
27
27
 
28
- export { actionsRegistryServiceFactory, actionsServiceFactory, metricsServiceFactory, rootSystemMetadataServiceFactory };
28
+ /**
29
+ * Service factory for emitting plugin-scoped trace spans.
30
+ *
31
+ * @alpha
32
+ */
33
+ declare const tracingServiceFactory: _backstage_backend_plugin_api.ServiceFactory<_backstage_backend_plugin_api_alpha.TracingService, "plugin", "singleton">;
34
+
35
+ export { actionsRegistryServiceFactory, actionsServiceFactory, metricsServiceFactory, rootSystemMetadataServiceFactory, tracingServiceFactory };
@@ -80,7 +80,7 @@ interface HostDiscoveryOptions {
80
80
  * These endpoints have lower priority than any that are defined in
81
81
  * app-config, but higher priority than the fallback ones.
82
82
  *
83
- * This parameter is usedful for example if you want to provide a shared
83
+ * This parameter is useful for example if you want to provide a shared
84
84
  * library of core services to your plugin developers, which is set up for the
85
85
  * default behaviors in your org. This alleviates the need for replicating any
86
86
  * given set of endpoint config in every backend that you deploy.
@@ -160,14 +160,14 @@ class CacheManager {
160
160
  }
161
161
  valkeyOptions.cluster = {
162
162
  rootNodes: clusterConfig.get("rootNodes"),
163
- defaults: clusterConfig.getOptional("defaults"),
164
- minimizeConnections: clusterConfig.getOptionalBoolean(
165
- "minimizeConnections"
166
- ),
167
- useReplicas: clusterConfig.getOptionalBoolean("useReplicas"),
168
- maxCommandRedirections: clusterConfig.getOptionalNumber(
169
- "maxCommandRedirections"
170
- )
163
+ options: {
164
+ redisOptions: clusterConfig.getOptional("defaults"),
165
+ scaleReads: clusterConfig.getOptionalBoolean("useReplicas") ? "slave" : void 0,
166
+ maxRedirections: clusterConfig.getOptionalNumber(
167
+ "maxCommandRedirections"
168
+ ),
169
+ lazyConnect: clusterConfig.getOptionalBoolean("minimizeConnections")
170
+ }
171
171
  };
172
172
  }
173
173
  return valkeyOptions;
@@ -260,7 +260,7 @@ class CacheManager {
260
260
  }
261
261
  createValkeyStoreFactory() {
262
262
  const KeyvValkey = require("@keyv/valkey").default;
263
- const { createCluster } = require("@keyv/redis");
263
+ const { Cluster } = require("iovalkey");
264
264
  const stores = {};
265
265
  return (pluginId, defaultTtl) => {
266
266
  if (this.storeOptions?.type !== "valkey") {
@@ -271,7 +271,10 @@ class CacheManager {
271
271
  if (!stores[pluginId]) {
272
272
  const valkeyOptions = this.storeOptions?.client;
273
273
  if (this.storeOptions?.cluster) {
274
- const cluster = createCluster(this.storeOptions?.cluster);
274
+ const cluster = new Cluster(
275
+ this.storeOptions.cluster.rootNodes,
276
+ this.storeOptions.cluster.options
277
+ );
275
278
  stores[pluginId] = new KeyvValkey(cluster, valkeyOptions);
276
279
  } else {
277
280
  stores[pluginId] = new KeyvValkey(this.connection, valkeyOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"CacheManager.cjs.js","sources":["../../../src/entrypoints/cache/CacheManager.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CacheService,\n CacheServiceOptions,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport Keyv from 'keyv';\nimport { DefaultCacheClient } from './CacheClient';\nimport {\n CacheManagerOptions,\n ttlToMilliseconds,\n CacheStoreOptions,\n RedisCacheStoreOptions,\n InfinispanClientBehaviorOptions,\n InfinispanServerConfig,\n ValkeyCacheStoreOptions,\n} from './types';\nimport { InfinispanOptionsMapper } from './providers/infinispan/InfinispanOptionsMapper';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { ConfigReader, readDurationFromConfig } from '@backstage/config';\nimport {\n InfinispanClientCacheInterface,\n InfinispanKeyvStore,\n} from './providers/infinispan/InfinispanKeyvStore';\n\ntype StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv;\n\n/**\n * Implements a Cache Manager which will automatically create new cache clients\n * for plugins when requested. All requested cache clients are created with the\n * connection details provided.\n *\n * @public\n */\nexport class CacheManager {\n /**\n * Keys represent supported `backend.cache.store` values, mapped to factories\n * that return Keyv instances appropriate to the store.\n */\n private readonly storeFactories = {\n redis: this.createRedisStoreFactory(),\n valkey: this.createValkeyStoreFactory(),\n memcache: this.createMemcacheStoreFactory(),\n memory: this.createMemoryStoreFactory(),\n infinispan: this.createInfinispanStoreFactory(),\n };\n\n private readonly logger?: LoggerService;\n private readonly store: keyof CacheManager['storeFactories'];\n private readonly connection: string;\n private readonly errorHandler: CacheManagerOptions['onError'];\n private readonly defaultTtl?: number;\n private readonly storeOptions?: CacheStoreOptions;\n\n /**\n * Creates a new {@link CacheManager} instance by reading from the `backend`\n * config section, specifically the `.cache` key.\n *\n * @param config - The loaded application configuration.\n * @param options - Optional configuration for the CacheManager.\n * @returns A new CacheManager instance.\n */\n static fromConfig(\n config: RootConfigService,\n options: CacheManagerOptions = {},\n ): CacheManager {\n // If no `backend.cache` config is provided, instantiate the CacheManager\n // with an in-memory cache client.\n const store = config.getOptionalString('backend.cache.store') || 'memory';\n const defaultTtlConfig = config.getOptional('backend.cache.defaultTtl');\n const connectionString =\n config.getOptionalString('backend.cache.connection') || '';\n const logger = options.logger?.child({\n type: 'cacheManager',\n });\n\n if (config.has('backend.cache.useRedisSets')) {\n logger?.warn(\n \"The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' and '@keyv/valkey' libraries no longer support redis sets.\",\n );\n }\n\n let defaultTtl: number | undefined;\n if (defaultTtlConfig !== undefined) {\n if (typeof defaultTtlConfig === 'number') {\n defaultTtl = defaultTtlConfig;\n } else {\n defaultTtl = durationToMilliseconds(\n readDurationFromConfig(config, { key: 'backend.cache.defaultTtl' }),\n );\n }\n }\n\n // Read store-specific options from config\n const storeOptions = CacheManager.parseStoreOptions(store, config, logger);\n\n return new CacheManager(\n store,\n connectionString,\n options.onError,\n logger,\n defaultTtl,\n storeOptions,\n );\n }\n\n /**\n * Parse store-specific options from configuration.\n *\n * @param store - The cache store type ('redis', 'valkey', 'memcache', 'infinispan', or 'memory')\n * @param config - The configuration service\n * @param logger - Optional logger for warnings\n * @returns The parsed store options\n */\n private static parseStoreOptions(\n store: string,\n config: RootConfigService,\n logger?: LoggerService,\n ): CacheStoreOptions | undefined {\n const storeConfigPath = `backend.cache.${store}`;\n\n if (store !== 'memory' && !config.has(storeConfigPath)) {\n logger?.warn(\n `No configuration found for cache store '${store}' at '${storeConfigPath}'.`,\n );\n }\n\n switch (store) {\n case 'redis':\n return CacheManager.parseRedisOptions(storeConfigPath, config, logger);\n case 'valkey':\n return CacheManager.parseValkeyOptions(storeConfigPath, config, logger);\n case 'infinispan':\n return InfinispanOptionsMapper.parseInfinispanOptions(\n storeConfigPath,\n config,\n logger,\n );\n default:\n return undefined;\n }\n }\n\n /**\n * Parse Redis-specific options from configuration.\n */\n private static parseRedisOptions(\n storeConfigPath: string,\n config: RootConfigService,\n logger?: LoggerService,\n ): RedisCacheStoreOptions {\n const redisOptions: RedisCacheStoreOptions = {\n type: 'redis',\n };\n\n const redisConfig =\n config.getOptionalConfig(storeConfigPath) ?? new ConfigReader({});\n\n redisOptions.client = {\n namespace: redisConfig.getOptionalString('client.namespace'),\n keyPrefixSeparator:\n redisConfig.getOptionalString('client.keyPrefixSeparator') || ':',\n clearBatchSize: redisConfig.getOptionalNumber('client.clearBatchSize'),\n useUnlink: redisConfig.getOptionalBoolean('client.useUnlink'),\n noNamespaceAffectsAll: redisConfig.getOptionalBoolean(\n 'client.noNamespaceAffectsAll',\n ),\n };\n\n if (redisConfig.has('cluster')) {\n const clusterConfig = redisConfig.getConfig('cluster');\n\n if (!clusterConfig.has('rootNodes')) {\n logger?.warn(\n `Redis cluster config has no 'rootNodes' key, defaulting to non-clustered mode`,\n );\n return redisOptions;\n }\n\n redisOptions.cluster = {\n rootNodes: clusterConfig.get('rootNodes'),\n defaults: clusterConfig.getOptional('defaults'),\n minimizeConnections: clusterConfig.getOptionalBoolean(\n 'minimizeConnections',\n ),\n useReplicas: clusterConfig.getOptionalBoolean('useReplicas'),\n maxCommandRedirections: clusterConfig.getOptionalNumber(\n 'maxCommandRedirections',\n ),\n };\n }\n\n return redisOptions;\n }\n\n /**\n * Parse Valkey-specific options from configuration.\n */\n private static parseValkeyOptions(\n storeConfigPath: string,\n config: RootConfigService,\n logger?: LoggerService,\n ): ValkeyCacheStoreOptions {\n const valkeyOptions: ValkeyCacheStoreOptions = {\n type: 'valkey',\n };\n\n const valkeyConfig =\n config.getOptionalConfig(storeConfigPath) ?? new ConfigReader({});\n\n valkeyOptions.client = {\n keyPrefix: valkeyConfig.getOptionalString('client.keyPrefix'),\n };\n\n if (valkeyConfig.has('cluster')) {\n const clusterConfig = valkeyConfig.getConfig('cluster');\n\n if (!clusterConfig.has('rootNodes')) {\n logger?.warn(\n `Valkey cluster config has no 'rootNodes' key, defaulting to non-clustered mode`,\n );\n return valkeyOptions;\n }\n\n valkeyOptions.cluster = {\n rootNodes: clusterConfig.get('rootNodes'),\n defaults: clusterConfig.getOptional('defaults'),\n minimizeConnections: clusterConfig.getOptionalBoolean(\n 'minimizeConnections',\n ),\n useReplicas: clusterConfig.getOptionalBoolean('useReplicas'),\n maxCommandRedirections: clusterConfig.getOptionalNumber(\n 'maxCommandRedirections',\n ),\n };\n }\n\n return valkeyOptions;\n }\n\n /**\n * Construct the full namespace based on the options and pluginId.\n *\n * @param pluginId - The plugin ID to namespace\n * @param storeOptions - Optional cache store configuration options\n * @returns The constructed namespace string combining the configured namespace with pluginId\n */\n private static constructNamespace(\n pluginId: string,\n storeOptions: RedisCacheStoreOptions | ValkeyCacheStoreOptions | undefined,\n ): string {\n let prefix: string;\n switch (storeOptions?.type) {\n case 'redis':\n prefix = storeOptions?.client?.namespace\n ? `${storeOptions.client.namespace}${\n storeOptions.client.keyPrefixSeparator ?? ':'\n }`\n : '';\n break;\n case 'valkey':\n prefix = storeOptions.client?.keyPrefix ?? '';\n break;\n default:\n prefix = '';\n }\n\n return `${prefix}${pluginId}`;\n }\n\n /** @internal */\n constructor(\n store: string,\n connectionString: string,\n errorHandler: CacheManagerOptions['onError'],\n logger?: LoggerService,\n defaultTtl?: number,\n storeOptions?: CacheStoreOptions,\n ) {\n if (!this.storeFactories.hasOwnProperty(store)) {\n throw new Error(`Unknown cache store: ${store}`);\n }\n this.logger = logger;\n this.store = store as keyof CacheManager['storeFactories'];\n this.connection = connectionString;\n this.errorHandler = errorHandler;\n this.defaultTtl = defaultTtl;\n this.storeOptions = storeOptions;\n }\n\n /**\n * Generates a PluginCacheManager for consumption by plugins.\n *\n * @param pluginId - The plugin that the cache manager should be created for.\n * Plugin names should be unique.\n */\n forPlugin(pluginId: string): CacheService {\n const clientFactory = (options: CacheServiceOptions) => {\n const ttl = options.defaultTtl ?? this.defaultTtl;\n return this.getClientWithTtl(\n pluginId,\n ttl !== undefined ? ttlToMilliseconds(ttl) : undefined,\n );\n };\n\n return new DefaultCacheClient(clientFactory({}), clientFactory, {});\n }\n\n private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv {\n return this.storeFactories[this.store](pluginId, ttl);\n }\n\n private createRedisStoreFactory(): StoreFactory {\n const KeyvRedis = require('@keyv/redis').default;\n const { createCluster } = require('@keyv/redis');\n const stores: Record<string, typeof KeyvRedis> = {};\n\n return (pluginId, defaultTtl) => {\n if (this.storeOptions?.type !== 'redis') {\n throw new Error(\n `Internal error: Wrong config type passed to redis factory: ${this.storeOptions?.type}`,\n );\n }\n if (!stores[pluginId]) {\n const redisOptions = this.storeOptions?.client || {\n keyPrefixSeparator: ':',\n };\n if (this.storeOptions?.cluster) {\n // Create a Redis cluster\n const cluster = createCluster(this.storeOptions?.cluster);\n stores[pluginId] = new KeyvRedis(cluster, redisOptions);\n } else {\n // Create a regular Redis connection\n stores[pluginId] = new KeyvRedis(this.connection, redisOptions);\n }\n\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create redis cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: CacheManager.constructNamespace(pluginId, this.storeOptions),\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n useKeyPrefix: false,\n });\n };\n }\n\n private createValkeyStoreFactory(): StoreFactory {\n const KeyvValkey = require('@keyv/valkey').default;\n // `@keyv/valkey` doesn't export a `createCluster` function, but is compatible with the one from `@keyv/redis`\n // See https://keyv.org/docs/storage-adapters/valkey\n const { createCluster } = require('@keyv/redis');\n const stores: Record<string, typeof KeyvValkey> = {};\n\n return (pluginId, defaultTtl) => {\n if (this.storeOptions?.type !== 'valkey') {\n throw new Error(\n `Internal error: Wrong config type passed to valkey factory: ${this.storeOptions?.type}`,\n );\n }\n if (!stores[pluginId]) {\n const valkeyOptions = this.storeOptions?.client;\n if (this.storeOptions?.cluster) {\n // Create a Valkey cluster (Redis cluster under the hood)\n const cluster = createCluster(this.storeOptions?.cluster);\n stores[pluginId] = new KeyvValkey(cluster, valkeyOptions);\n } else {\n // Create a regular Valkey connection\n stores[pluginId] = new KeyvValkey(this.connection, valkeyOptions);\n }\n\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create valkey cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: CacheManager.constructNamespace(pluginId, this.storeOptions),\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n useKeyPrefix: false,\n });\n };\n }\n\n private createMemcacheStoreFactory(): StoreFactory {\n const KeyvMemcache = require('@keyv/memcache').default;\n const stores: Record<string, typeof KeyvMemcache> = {};\n\n return (pluginId, defaultTtl) => {\n if (!stores[pluginId]) {\n stores[pluginId] = new KeyvMemcache(this.connection);\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create memcache cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store: stores[pluginId],\n });\n };\n }\n\n private createMemoryStoreFactory(): StoreFactory {\n const store = new Map();\n return (pluginId, defaultTtl) =>\n new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store,\n });\n }\n\n private createInfinispanStoreFactory(): StoreFactory {\n const stores: Record<string, InfinispanKeyvStore> = {};\n\n return (pluginId, defaultTtl) => {\n if (this.storeOptions?.type !== 'infinispan') {\n throw new Error(\n `Internal error: Wrong config type passed to infinispan factory: ${this.storeOptions?.type}`,\n );\n }\n\n if (!stores[pluginId]) {\n // Use sync version for testing environments\n const isTest =\n process.env.NODE_ENV === 'test' || typeof jest !== 'undefined';\n\n // Create the client promise ONCE and reuse it\n const clientPromise: Promise<InfinispanClientCacheInterface> = isTest\n ? this.createInfinispanClientSync()\n : this.createInfinispanClientAsync();\n\n this.logger?.info(\n `Creating Infinispan cache client for plugin ${pluginId} isTest = ${isTest}`,\n );\n const storeInstance = new InfinispanKeyvStore({\n clientPromise,\n logger: this.logger!,\n });\n\n stores[pluginId] = storeInstance;\n\n // Always provide an error handler to avoid stopping the process\n storeInstance.on('error', (err: Error) => {\n this.logger?.error('Failed to create infinispan cache client', err);\n this.errorHandler?.(err);\n });\n }\n\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n });\n };\n }\n\n /**\n * Creates an Infinispan client using dynamic import (production use).\n * @returns Promise that resolves to an Infinispan client\n */\n private async createInfinispanClientAsync(): Promise<InfinispanClientCacheInterface> {\n return this.createInfinispanClient(false);\n }\n\n /**\n * Creates an Infinispan client using synchronous import (testing purposes).\n * @returns Promise that resolves to an Infinispan client\n */\n private createInfinispanClientSync(): Promise<InfinispanClientCacheInterface> {\n return this.createInfinispanClient(true);\n }\n\n /**\n * Creates an Infinispan client based on the provided configuration.\n * @param useSync - Whether to use synchronous import (for testing) or dynamic import\n * @returns Promise that resolves to an Infinispan client\n */\n private async createInfinispanClient(\n useSync: boolean = false,\n ): Promise<InfinispanClientCacheInterface> {\n try {\n this.logger?.info('Creating Infinispan client');\n\n if (this.storeOptions?.type === 'infinispan') {\n // Import infinispan based on the useSync parameter\n const infinispan = useSync\n ? require('infinispan')\n : await import('infinispan');\n\n const client = await infinispan.client(\n this.storeOptions.servers as InfinispanServerConfig[],\n this.storeOptions.options as InfinispanClientBehaviorOptions,\n );\n\n this.logger?.info('Infinispan client created successfully');\n return client;\n }\n throw new Error('Infinispan store options are not defined');\n } catch (error: any) {\n this.logger?.error('Failed to create Infinispan client', {\n error: error.message,\n });\n throw error;\n }\n }\n}\n"],"names":["config","durationToMilliseconds","readDurationFromConfig","InfinispanOptionsMapper","ConfigReader","ttlToMilliseconds","DefaultCacheClient","Keyv","InfinispanKeyvStore"],"mappings":"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"}
1
+ {"version":3,"file":"CacheManager.cjs.js","sources":["../../../src/entrypoints/cache/CacheManager.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CacheService,\n CacheServiceOptions,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport Keyv from 'keyv';\nimport { DefaultCacheClient } from './CacheClient';\nimport {\n CacheManagerOptions,\n ttlToMilliseconds,\n CacheStoreOptions,\n RedisCacheStoreOptions,\n InfinispanClientBehaviorOptions,\n InfinispanServerConfig,\n ValkeyCacheStoreOptions,\n} from './types';\nimport { InfinispanOptionsMapper } from './providers/infinispan/InfinispanOptionsMapper';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { ConfigReader, readDurationFromConfig } from '@backstage/config';\nimport {\n InfinispanClientCacheInterface,\n InfinispanKeyvStore,\n} from './providers/infinispan/InfinispanKeyvStore';\n\ntype StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv;\n\n/**\n * Implements a Cache Manager which will automatically create new cache clients\n * for plugins when requested. All requested cache clients are created with the\n * connection details provided.\n *\n * @public\n */\nexport class CacheManager {\n /**\n * Keys represent supported `backend.cache.store` values, mapped to factories\n * that return Keyv instances appropriate to the store.\n */\n private readonly storeFactories = {\n redis: this.createRedisStoreFactory(),\n valkey: this.createValkeyStoreFactory(),\n memcache: this.createMemcacheStoreFactory(),\n memory: this.createMemoryStoreFactory(),\n infinispan: this.createInfinispanStoreFactory(),\n };\n\n private readonly logger?: LoggerService;\n private readonly store: keyof CacheManager['storeFactories'];\n private readonly connection: string;\n private readonly errorHandler: CacheManagerOptions['onError'];\n private readonly defaultTtl?: number;\n private readonly storeOptions?: CacheStoreOptions;\n\n /**\n * Creates a new {@link CacheManager} instance by reading from the `backend`\n * config section, specifically the `.cache` key.\n *\n * @param config - The loaded application configuration.\n * @param options - Optional configuration for the CacheManager.\n * @returns A new CacheManager instance.\n */\n static fromConfig(\n config: RootConfigService,\n options: CacheManagerOptions = {},\n ): CacheManager {\n // If no `backend.cache` config is provided, instantiate the CacheManager\n // with an in-memory cache client.\n const store = config.getOptionalString('backend.cache.store') || 'memory';\n const defaultTtlConfig = config.getOptional('backend.cache.defaultTtl');\n const connectionString =\n config.getOptionalString('backend.cache.connection') || '';\n const logger = options.logger?.child({\n type: 'cacheManager',\n });\n\n if (config.has('backend.cache.useRedisSets')) {\n logger?.warn(\n \"The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' and '@keyv/valkey' libraries no longer support redis sets.\",\n );\n }\n\n let defaultTtl: number | undefined;\n if (defaultTtlConfig !== undefined) {\n if (typeof defaultTtlConfig === 'number') {\n defaultTtl = defaultTtlConfig;\n } else {\n defaultTtl = durationToMilliseconds(\n readDurationFromConfig(config, { key: 'backend.cache.defaultTtl' }),\n );\n }\n }\n\n // Read store-specific options from config\n const storeOptions = CacheManager.parseStoreOptions(store, config, logger);\n\n return new CacheManager(\n store,\n connectionString,\n options.onError,\n logger,\n defaultTtl,\n storeOptions,\n );\n }\n\n /**\n * Parse store-specific options from configuration.\n *\n * @param store - The cache store type ('redis', 'valkey', 'memcache', 'infinispan', or 'memory')\n * @param config - The configuration service\n * @param logger - Optional logger for warnings\n * @returns The parsed store options\n */\n private static parseStoreOptions(\n store: string,\n config: RootConfigService,\n logger?: LoggerService,\n ): CacheStoreOptions | undefined {\n const storeConfigPath = `backend.cache.${store}`;\n\n if (store !== 'memory' && !config.has(storeConfigPath)) {\n logger?.warn(\n `No configuration found for cache store '${store}' at '${storeConfigPath}'.`,\n );\n }\n\n switch (store) {\n case 'redis':\n return CacheManager.parseRedisOptions(storeConfigPath, config, logger);\n case 'valkey':\n return CacheManager.parseValkeyOptions(storeConfigPath, config, logger);\n case 'infinispan':\n return InfinispanOptionsMapper.parseInfinispanOptions(\n storeConfigPath,\n config,\n logger,\n );\n default:\n return undefined;\n }\n }\n\n /**\n * Parse Redis-specific options from configuration.\n */\n private static parseRedisOptions(\n storeConfigPath: string,\n config: RootConfigService,\n logger?: LoggerService,\n ): RedisCacheStoreOptions {\n const redisOptions: RedisCacheStoreOptions = {\n type: 'redis',\n };\n\n const redisConfig =\n config.getOptionalConfig(storeConfigPath) ?? new ConfigReader({});\n\n redisOptions.client = {\n namespace: redisConfig.getOptionalString('client.namespace'),\n keyPrefixSeparator:\n redisConfig.getOptionalString('client.keyPrefixSeparator') || ':',\n clearBatchSize: redisConfig.getOptionalNumber('client.clearBatchSize'),\n useUnlink: redisConfig.getOptionalBoolean('client.useUnlink'),\n noNamespaceAffectsAll: redisConfig.getOptionalBoolean(\n 'client.noNamespaceAffectsAll',\n ),\n };\n\n if (redisConfig.has('cluster')) {\n const clusterConfig = redisConfig.getConfig('cluster');\n\n if (!clusterConfig.has('rootNodes')) {\n logger?.warn(\n `Redis cluster config has no 'rootNodes' key, defaulting to non-clustered mode`,\n );\n return redisOptions;\n }\n\n redisOptions.cluster = {\n rootNodes: clusterConfig.get('rootNodes'),\n defaults: clusterConfig.getOptional('defaults'),\n minimizeConnections: clusterConfig.getOptionalBoolean(\n 'minimizeConnections',\n ),\n useReplicas: clusterConfig.getOptionalBoolean('useReplicas'),\n maxCommandRedirections: clusterConfig.getOptionalNumber(\n 'maxCommandRedirections',\n ),\n };\n }\n\n return redisOptions;\n }\n\n /**\n * Parse Valkey-specific options from configuration.\n */\n private static parseValkeyOptions(\n storeConfigPath: string,\n config: RootConfigService,\n logger?: LoggerService,\n ): ValkeyCacheStoreOptions {\n const valkeyOptions: ValkeyCacheStoreOptions = {\n type: 'valkey',\n };\n\n const valkeyConfig =\n config.getOptionalConfig(storeConfigPath) ?? new ConfigReader({});\n\n valkeyOptions.client = {\n keyPrefix: valkeyConfig.getOptionalString('client.keyPrefix'),\n };\n\n if (valkeyConfig.has('cluster')) {\n const clusterConfig = valkeyConfig.getConfig('cluster');\n\n if (!clusterConfig.has('rootNodes')) {\n logger?.warn(\n `Valkey cluster config has no 'rootNodes' key, defaulting to non-clustered mode`,\n );\n return valkeyOptions;\n }\n\n valkeyOptions.cluster = {\n rootNodes: clusterConfig.get('rootNodes'),\n options: {\n redisOptions: clusterConfig.getOptional('defaults'),\n scaleReads: clusterConfig.getOptionalBoolean('useReplicas')\n ? 'slave'\n : undefined,\n maxRedirections: clusterConfig.getOptionalNumber(\n 'maxCommandRedirections',\n ),\n lazyConnect: clusterConfig.getOptionalBoolean('minimizeConnections'),\n },\n };\n }\n\n return valkeyOptions;\n }\n\n /**\n * Construct the full namespace based on the options and pluginId.\n *\n * @param pluginId - The plugin ID to namespace\n * @param storeOptions - Optional cache store configuration options\n * @returns The constructed namespace string combining the configured namespace with pluginId\n */\n private static constructNamespace(\n pluginId: string,\n storeOptions: RedisCacheStoreOptions | ValkeyCacheStoreOptions | undefined,\n ): string {\n let prefix: string;\n switch (storeOptions?.type) {\n case 'redis':\n prefix = storeOptions?.client?.namespace\n ? `${storeOptions.client.namespace}${\n storeOptions.client.keyPrefixSeparator ?? ':'\n }`\n : '';\n break;\n case 'valkey':\n prefix = storeOptions.client?.keyPrefix ?? '';\n break;\n default:\n prefix = '';\n }\n\n return `${prefix}${pluginId}`;\n }\n\n /** @internal */\n constructor(\n store: string,\n connectionString: string,\n errorHandler: CacheManagerOptions['onError'],\n logger?: LoggerService,\n defaultTtl?: number,\n storeOptions?: CacheStoreOptions,\n ) {\n if (!this.storeFactories.hasOwnProperty(store)) {\n throw new Error(`Unknown cache store: ${store}`);\n }\n this.logger = logger;\n this.store = store as keyof CacheManager['storeFactories'];\n this.connection = connectionString;\n this.errorHandler = errorHandler;\n this.defaultTtl = defaultTtl;\n this.storeOptions = storeOptions;\n }\n\n /**\n * Generates a PluginCacheManager for consumption by plugins.\n *\n * @param pluginId - The plugin that the cache manager should be created for.\n * Plugin names should be unique.\n */\n forPlugin(pluginId: string): CacheService {\n const clientFactory = (options: CacheServiceOptions) => {\n const ttl = options.defaultTtl ?? this.defaultTtl;\n return this.getClientWithTtl(\n pluginId,\n ttl !== undefined ? ttlToMilliseconds(ttl) : undefined,\n );\n };\n\n return new DefaultCacheClient(clientFactory({}), clientFactory, {});\n }\n\n private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv {\n return this.storeFactories[this.store](pluginId, ttl);\n }\n\n private createRedisStoreFactory(): StoreFactory {\n const KeyvRedis = require('@keyv/redis').default;\n const { createCluster } = require('@keyv/redis');\n const stores: Record<string, typeof KeyvRedis> = {};\n\n return (pluginId, defaultTtl) => {\n if (this.storeOptions?.type !== 'redis') {\n throw new Error(\n `Internal error: Wrong config type passed to redis factory: ${this.storeOptions?.type}`,\n );\n }\n if (!stores[pluginId]) {\n const redisOptions = this.storeOptions?.client || {\n keyPrefixSeparator: ':',\n };\n if (this.storeOptions?.cluster) {\n // Create a Redis cluster\n const cluster = createCluster(this.storeOptions?.cluster);\n stores[pluginId] = new KeyvRedis(cluster, redisOptions);\n } else {\n // Create a regular Redis connection\n stores[pluginId] = new KeyvRedis(this.connection, redisOptions);\n }\n\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create redis cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: CacheManager.constructNamespace(pluginId, this.storeOptions),\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n useKeyPrefix: false,\n });\n };\n }\n\n private createValkeyStoreFactory(): StoreFactory {\n const KeyvValkey = require('@keyv/valkey').default;\n const { Cluster } = require('iovalkey');\n const stores: Record<string, typeof KeyvValkey> = {};\n\n return (pluginId, defaultTtl) => {\n if (this.storeOptions?.type !== 'valkey') {\n throw new Error(\n `Internal error: Wrong config type passed to valkey factory: ${this.storeOptions?.type}`,\n );\n }\n if (!stores[pluginId]) {\n const valkeyOptions = this.storeOptions?.client;\n if (this.storeOptions?.cluster) {\n // Create an iovalkey Cluster instance, which is the type that @keyv/valkey expects\n const cluster = new Cluster(\n this.storeOptions.cluster.rootNodes,\n this.storeOptions.cluster.options,\n );\n stores[pluginId] = new KeyvValkey(cluster, valkeyOptions);\n } else {\n // Create a regular Valkey connection\n stores[pluginId] = new KeyvValkey(this.connection, valkeyOptions);\n }\n\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create valkey cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: CacheManager.constructNamespace(pluginId, this.storeOptions),\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n useKeyPrefix: false,\n });\n };\n }\n\n private createMemcacheStoreFactory(): StoreFactory {\n const KeyvMemcache = require('@keyv/memcache').default;\n const stores: Record<string, typeof KeyvMemcache> = {};\n\n return (pluginId, defaultTtl) => {\n if (!stores[pluginId]) {\n stores[pluginId] = new KeyvMemcache(this.connection);\n // Always provide an error handler to avoid stopping the process\n stores[pluginId].on('error', (err: Error) => {\n this.logger?.error('Failed to create memcache cache client', err);\n this.errorHandler?.(err);\n });\n }\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store: stores[pluginId],\n });\n };\n }\n\n private createMemoryStoreFactory(): StoreFactory {\n const store = new Map();\n return (pluginId, defaultTtl) =>\n new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n emitErrors: false,\n store,\n });\n }\n\n private createInfinispanStoreFactory(): StoreFactory {\n const stores: Record<string, InfinispanKeyvStore> = {};\n\n return (pluginId, defaultTtl) => {\n if (this.storeOptions?.type !== 'infinispan') {\n throw new Error(\n `Internal error: Wrong config type passed to infinispan factory: ${this.storeOptions?.type}`,\n );\n }\n\n if (!stores[pluginId]) {\n // Use sync version for testing environments\n const isTest =\n process.env.NODE_ENV === 'test' || typeof jest !== 'undefined';\n\n // Create the client promise ONCE and reuse it\n const clientPromise: Promise<InfinispanClientCacheInterface> = isTest\n ? this.createInfinispanClientSync()\n : this.createInfinispanClientAsync();\n\n this.logger?.info(\n `Creating Infinispan cache client for plugin ${pluginId} isTest = ${isTest}`,\n );\n const storeInstance = new InfinispanKeyvStore({\n clientPromise,\n logger: this.logger!,\n });\n\n stores[pluginId] = storeInstance;\n\n // Always provide an error handler to avoid stopping the process\n storeInstance.on('error', (err: Error) => {\n this.logger?.error('Failed to create infinispan cache client', err);\n this.errorHandler?.(err);\n });\n }\n\n return new Keyv({\n namespace: pluginId,\n ttl: defaultTtl,\n store: stores[pluginId],\n emitErrors: false,\n });\n };\n }\n\n /**\n * Creates an Infinispan client using dynamic import (production use).\n * @returns Promise that resolves to an Infinispan client\n */\n private async createInfinispanClientAsync(): Promise<InfinispanClientCacheInterface> {\n return this.createInfinispanClient(false);\n }\n\n /**\n * Creates an Infinispan client using synchronous import (testing purposes).\n * @returns Promise that resolves to an Infinispan client\n */\n private createInfinispanClientSync(): Promise<InfinispanClientCacheInterface> {\n return this.createInfinispanClient(true);\n }\n\n /**\n * Creates an Infinispan client based on the provided configuration.\n * @param useSync - Whether to use synchronous import (for testing) or dynamic import\n * @returns Promise that resolves to an Infinispan client\n */\n private async createInfinispanClient(\n useSync: boolean = false,\n ): Promise<InfinispanClientCacheInterface> {\n try {\n this.logger?.info('Creating Infinispan client');\n\n if (this.storeOptions?.type === 'infinispan') {\n // Import infinispan based on the useSync parameter\n const infinispan = useSync\n ? require('infinispan')\n : await import('infinispan');\n\n const client = await infinispan.client(\n this.storeOptions.servers as InfinispanServerConfig[],\n this.storeOptions.options as InfinispanClientBehaviorOptions,\n );\n\n this.logger?.info('Infinispan client created successfully');\n return client;\n }\n throw new Error('Infinispan store options are not defined');\n } catch (error: any) {\n this.logger?.error('Failed to create Infinispan client', {\n error: error.message,\n });\n throw error;\n }\n }\n}\n"],"names":["config","durationToMilliseconds","readDurationFromConfig","InfinispanOptionsMapper","ConfigReader","ttlToMilliseconds","DefaultCacheClient","Keyv","InfinispanKeyvStore"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs.js","sources":["../../../src/entrypoints/cache/types.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { RedisClusterOptions, KeyvRedisOptions } from '@keyv/redis';\nimport { KeyvValkeyOptions } from '@keyv/valkey';\n\n/**\n * Options for Redis cache store.\n *\n * @public\n */\nexport type RedisCacheStoreOptions = {\n type: 'redis';\n client?: KeyvRedisOptions;\n cluster?: RedisClusterOptions;\n};\n\n/**\n * Options for Valkey cache store.\n *\n * @public\n */\nexport type ValkeyCacheStoreOptions = {\n type: 'valkey';\n client?: KeyvValkeyOptions;\n cluster?: RedisClusterOptions;\n};\n\n/**\n * Union type of all cache store options.\n *\n * @public\n */\nexport type CacheStoreOptions =\n | RedisCacheStoreOptions\n | ValkeyCacheStoreOptions\n | InfinispanCacheStoreOptions;\n\n/**\n * Options given when constructing a {@link CacheManager}.\n *\n * @public\n */\nexport type CacheManagerOptions = {\n /**\n * An optional logger for use by the PluginCacheManager.\n */\n logger?: LoggerService;\n\n /**\n * An optional handler for connection errors emitted from the underlying data\n * store.\n */\n onError?: (err: Error) => void;\n};\n\nexport function ttlToMilliseconds(ttl: number | HumanDuration): number {\n return typeof ttl === 'number' ? ttl : durationToMilliseconds(ttl);\n}\n\n/**\n * Configuration for a single Infinispan server.\n */\nexport type InfinispanServerConfig = {\n host: string;\n port: number;\n};\n\n/**\n * Options for putting values into Infinispan cache.\n */\nexport type InfinispanPutOptions = {\n lifespan?: string;\n maxIdle?: string;\n previous?: boolean;\n flags?: string[];\n};\n/**\n * SSL/TLS options for the Infinispan client.\n */\nexport type InfinispanSslOptions = {\n enabled: boolean;\n secureProtocol?: string;\n trustCerts?: string[]; // Array of trusted CA certificates\n clientAuth?: InfinispanClientAuthOptions;\n cryptoStore?: InfinispanCryptoStoreOptions;\n sniHostName?: string;\n};\n\n/**\n * Authentication options for the Infinispan client.\n * This is used for client-side authentication with the Infinispan server.\n */\nexport type InfinispanClientAuthOptions = {\n key?: string;\n passphrase?: string;\n cert?: string;\n};\n\n/**\n * Options for the Infinispan client crypto store.\n * This is used for storing keys and certificates securely.\n */\nexport type InfinispanCryptoStoreOptions = {\n path?: string;\n passphrase?: string;\n};\n\n/**\n * Authentication options for the Infinispan client.\n */\nexport type InfinispanAuthOptions = {\n enabled: boolean;\n saslMechanism?: string;\n userName?: string;\n password?: string;\n token?: string;\n authzid?: string;\n};\n\n/**\n * Options for the Infinispan cache store, designed to be configured\n * in app-config.yaml under `backend.cache.infinispan`.\n */\nexport type InfinispanCacheStoreOptions = {\n type: 'infinispan';\n servers: InfinispanServerConfig | InfinispanServerConfig[];\n options?: InfinispanClientBehaviorOptions;\n};\n\nexport type InfinispanClusterConfig = {\n name?: string;\n servers: InfinispanServerConfig[];\n};\n\nexport type DataFormatOptions = {\n keyType: 'text/plain' | 'application/json';\n valueType: 'text/plain' | 'application/json';\n};\n\n/**\n * Detailed client behavior options for the Infinispan client.\n * @public\n */\nexport type InfinispanClientBehaviorOptions = {\n version?: '2.9' | '2.5' | '2.2';\n cacheName?: string;\n maxRetries?: number;\n connectionTimeout?: number;\n socketTimeout?: number;\n authentication?: InfinispanAuthOptions;\n ssl?: InfinispanSslOptions;\n mediaType?: 'text/plain' | 'application/json';\n topologyUpdates?: boolean;\n clusters?: InfinispanClusterConfig[];\n dataFormat: DataFormatOptions;\n};\n"],"names":["durationToMilliseconds"],"mappings":";;;;AAuEO,SAAS,kBAAkB,GAAA,EAAqC;AACrE,EAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAMA,6BAAuB,GAAG,CAAA;AACnE;;;;"}
1
+ {"version":3,"file":"types.cjs.js","sources":["../../../src/entrypoints/cache/types.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\nimport { RedisClusterOptions, KeyvRedisOptions } from '@keyv/redis';\nimport { KeyvValkeyOptions } from '@keyv/valkey';\nimport { ClusterNode, ClusterOptions } from 'iovalkey';\n\n/**\n * Options for Redis cache store.\n *\n * @public\n */\nexport type RedisCacheStoreOptions = {\n type: 'redis';\n client?: KeyvRedisOptions;\n cluster?: RedisClusterOptions;\n};\n\n/**\n * Options for Valkey cache store.\n *\n * @public\n */\nexport type ValkeyCacheStoreOptions = {\n type: 'valkey';\n client?: KeyvValkeyOptions;\n cluster?: {\n rootNodes: Array<ClusterNode>;\n options?: ClusterOptions;\n };\n};\n\n/**\n * Union type of all cache store options.\n *\n * @public\n */\nexport type CacheStoreOptions =\n | RedisCacheStoreOptions\n | ValkeyCacheStoreOptions\n | InfinispanCacheStoreOptions;\n\n/**\n * Options given when constructing a {@link CacheManager}.\n *\n * @public\n */\nexport type CacheManagerOptions = {\n /**\n * An optional logger for use by the PluginCacheManager.\n */\n logger?: LoggerService;\n\n /**\n * An optional handler for connection errors emitted from the underlying data\n * store.\n */\n onError?: (err: Error) => void;\n};\n\nexport function ttlToMilliseconds(ttl: number | HumanDuration): number {\n return typeof ttl === 'number' ? ttl : durationToMilliseconds(ttl);\n}\n\n/**\n * Configuration for a single Infinispan server.\n */\nexport type InfinispanServerConfig = {\n host: string;\n port: number;\n};\n\n/**\n * Options for putting values into Infinispan cache.\n */\nexport type InfinispanPutOptions = {\n lifespan?: string;\n maxIdle?: string;\n previous?: boolean;\n flags?: string[];\n};\n/**\n * SSL/TLS options for the Infinispan client.\n */\nexport type InfinispanSslOptions = {\n enabled: boolean;\n secureProtocol?: string;\n trustCerts?: string[]; // Array of trusted CA certificates\n clientAuth?: InfinispanClientAuthOptions;\n cryptoStore?: InfinispanCryptoStoreOptions;\n sniHostName?: string;\n};\n\n/**\n * Authentication options for the Infinispan client.\n * This is used for client-side authentication with the Infinispan server.\n */\nexport type InfinispanClientAuthOptions = {\n key?: string;\n passphrase?: string;\n cert?: string;\n};\n\n/**\n * Options for the Infinispan client crypto store.\n * This is used for storing keys and certificates securely.\n */\nexport type InfinispanCryptoStoreOptions = {\n path?: string;\n passphrase?: string;\n};\n\n/**\n * Authentication options for the Infinispan client.\n */\nexport type InfinispanAuthOptions = {\n enabled: boolean;\n saslMechanism?: string;\n userName?: string;\n password?: string;\n token?: string;\n authzid?: string;\n};\n\n/**\n * Options for the Infinispan cache store, designed to be configured\n * in app-config.yaml under `backend.cache.infinispan`.\n */\nexport type InfinispanCacheStoreOptions = {\n type: 'infinispan';\n servers: InfinispanServerConfig | InfinispanServerConfig[];\n options?: InfinispanClientBehaviorOptions;\n};\n\nexport type InfinispanClusterConfig = {\n name?: string;\n servers: InfinispanServerConfig[];\n};\n\nexport type DataFormatOptions = {\n keyType: 'text/plain' | 'application/json';\n valueType: 'text/plain' | 'application/json';\n};\n\n/**\n * Detailed client behavior options for the Infinispan client.\n * @public\n */\nexport type InfinispanClientBehaviorOptions = {\n version?: '2.9' | '2.5' | '2.2';\n cacheName?: string;\n maxRetries?: number;\n connectionTimeout?: number;\n socketTimeout?: number;\n authentication?: InfinispanAuthOptions;\n ssl?: InfinispanSslOptions;\n mediaType?: 'text/plain' | 'application/json';\n topologyUpdates?: boolean;\n clusters?: InfinispanClusterConfig[];\n dataFormat: DataFormatOptions;\n};\n"],"names":["durationToMilliseconds"],"mappings":";;;;AA2EO,SAAS,kBAAkB,GAAA,EAAqC;AACrE,EAAA,OAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAMA,6BAAuB,GAAG,CAAA;AACnE;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\nimport { getEndpoints } from './parsing';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is usedful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n // The getExternalBaseUrl implementation relies on the backend base URL\n // being a valid, non-local URL that others will be able to route to.\n const baseUrl = config.getString('backend.baseUrl');\n try {\n const { hostname } = new URL(baseUrl);\n const isLocalhost =\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '::1' ||\n hostname === '::';\n if (isLocalhost && process.env.NODE_ENV === 'production') {\n options?.logger?.warn(\n `backend.baseUrl is set to a localhost URL and NODE_ENV is '${process.env.NODE_ENV}'. This is likely a misconfiguration — localhost URLs are not reachable by other services in a deployed environment. Prefer setting it to a routable URL that can be resolved and reached both by your app and by other plugin deployments / services.`,\n );\n }\n } catch {\n options?.logger?.warn(\n `backend.baseUrl config value '${baseUrl}' does not appear to be a valid URL.`,\n );\n }\n\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n endpoints.push(...getEndpoints(config));\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions","getEndpoints"],"mappings":"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"}
1
+ {"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\nimport { getEndpoints } from './parsing';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is useful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n // The getExternalBaseUrl implementation relies on the backend base URL\n // being a valid, non-local URL that others will be able to route to.\n const baseUrl = config.getString('backend.baseUrl');\n try {\n const { hostname } = new URL(baseUrl);\n const isLocalhost =\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '::1' ||\n hostname === '::';\n if (isLocalhost && process.env.NODE_ENV === 'production') {\n options?.logger?.warn(\n `backend.baseUrl is set to a localhost URL and NODE_ENV is '${process.env.NODE_ENV}'. This is likely a misconfiguration — localhost URLs are not reachable by other services in a deployed environment. Prefer setting it to a routable URL that can be resolved and reached both by your app and by other plugin deployments / services.`,\n );\n }\n } catch {\n options?.logger?.warn(\n `backend.baseUrl config value '${baseUrl}' does not appear to be a valid URL.`,\n );\n }\n\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n endpoints.push(...getEndpoints(config));\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions","getEndpoints"],"mappings":";;;;;;;AA8IO,MAAM,aAAA,CAA0C;AAAA,EACrD,YAAA;AAAA,EACA,kBAAA,uBAAgD,GAAA,EAAI;AAAA,EACpD,kBAAA,uBAAgD,GAAA,EAAI;AAAA,EACpD,4BAAsC,YAAY;AAChD,IAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,EACnC,CAAA;AAAA,EACA,4BAAsC,YAAY;AAChD,IAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,EACnC,CAAA;AAAA,EAEA,OAAO,UAAA,CAAW,MAAA,EAA2B,OAAA,EAAgC;AAG3E,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,SAAA,CAAU,iBAAiB,CAAA;AAClD,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,IAAI,IAAI,OAAO,CAAA;AACpC,MAAA,MAAM,cACJ,QAAA,KAAa,WAAA,IACb,aAAa,WAAA,IACb,QAAA,KAAa,SACb,QAAA,KAAa,IAAA;AACf,MAAA,IAAI,WAAA,IAAe,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA,EAAc;AACxD,QAAA,OAAA,EAAS,MAAA,EAAQ,IAAA;AAAA,UACf,CAAA,2DAAA,EAA8D,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,2PAAA;AAAA,SACpF;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,OAAA,EAAS,MAAA,EAAQ,IAAA;AAAA,QACf,iCAAiC,OAAO,CAAA,oCAAA;AAAA,OAC1C;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,IAAI,aAAA,CAAc,IAAIA,2BAAc,CAAA;AAEtD,IAAA,SAAA,CAAU,gBAAA,CAAiB,MAAA,EAAQ,OAAA,EAAS,gBAAgB,CAAA;AAC5D,IAAA,MAAA,CAAO,YAAY,MAAM;AACvB,MAAA,IAAI;AACF,QAAA,SAAA,CAAU,gBAAA,CAAiB,MAAA,EAAQ,OAAA,EAAS,gBAAgB,CAAA;AAAA,MAC9D,SAAS,CAAA,EAAG;AACV,QAAA,OAAA,EAAS,MAAA,CAAO,KAAA,CAAM,CAAA,oCAAA,EAAuC,CAAC,CAAA,CAAE,CAAA;AAAA,MAClE;AAAA,IACF,CAAC,CAAA;AAED,IAAA,OAAO,SAAA;AAAA,EACT;AAAA,EAEQ,YAAY,WAAA,EAA2B;AAC7C,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,kBAAA,uBAAyB,GAAA,EAAI;AAClC,IAAA,IAAA,CAAK,kBAAA,uBAAyB,GAAA,EAAI;AAClC,IAAA,IAAA,CAAK,4BAA4B,MAAM;AACrC,MAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,IACnC,CAAA;AACA,IAAA,IAAA,CAAK,4BAA4B,MAAM;AACrC,MAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,IACnC,CAAA;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,QAAA,EAAmC;AAClD,IAAA,MAAM,QAAA,GACJ,IAAA,CAAK,kBAAA,CAAmB,GAAA,CAAI,QAAQ,CAAA,IACpC,IAAA,CAAK,kBAAA,CAAmB,GAAA,CAAI,GAAG,CAAA,IAC/B,IAAA,CAAK,yBAAA;AACP,IAAA,OAAO,MAAM,SAAS,QAAQ,CAAA;AAAA,EAChC;AAAA,EAEA,MAAM,mBAAmB,QAAA,EAAmC;AAC1D,IAAA,MAAM,QAAA,GACJ,IAAA,CAAK,kBAAA,CAAmB,GAAA,CAAI,QAAQ,CAAA,IACpC,IAAA,CAAK,kBAAA,CAAmB,GAAA,CAAI,GAAG,CAAA,IAC/B,IAAA,CAAK,yBAAA;AACP,IAAA,OAAO,MAAM,SAAS,QAAQ,CAAA;AAAA,EAChC;AAAA,EAEA,gBAAA,CAAiB,QAAgB,gBAAA,EAA4C;AAC3E,IAAA,IAAA,CAAK,yBAAyB,MAAM,CAAA;AACpC,IAAA,IAAA,CAAK,sBAAA,CAAuB,QAAQ,gBAAgB,CAAA;AAAA,EACtD;AAAA,EAEA,yBAAyBC,QAAA,EAAgB;AACvC,IAAA,MAAM,iBAAiBC,cAAA,CAAQD,QAAA,CAAO,SAAA,CAAU,iBAAiB,GAAG,GAAG,CAAA;AAEvE,IAAA,MAAM;AAAA,MACJ,QAAQ,EAAE,IAAA,EAAM,UAAA,GAAa,IAAA,EAAM,MAAM,UAAA;AAAW,KACtD,GAAIE,4BAAA,CAAsBF,QAAA,CAAO,SAAA,CAAU,SAAS,CAAC,CAAA;AACrD,IAAA,MAAM,QAAA,GAAWA,QAAA,CAAO,GAAA,CAAI,eAAe,IAAI,OAAA,GAAU,MAAA;AAGzD,IAAA,IAAI,IAAA,GAAO,UAAA;AACX,IAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,EAAA,EAAI;AAIhC,MAAA,IAAA,GAAO,WAAA;AAAA,IACT,CAAA,MAAA,IAAW,SAAS,SAAA,EAAW;AAC7B,MAAA,IAAA,GAAO,WAAA;AAAA,IACT;AACA,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,EAAG;AACtB,MAAA,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA,CAAA;AAAA,IACjB;AAEA,IAAA,IAAA,CAAK,4BAA4B,IAAA,CAAK,aAAA;AAAA,MACpC,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,IAAI,IAAI,UAAU,CAAA,iBAAA,CAAA;AAAA,MACnC;AAAA,KACF;AACA,IAAA,IAAA,CAAK,4BAA4B,IAAA,CAAK,aAAA;AAAA,MACpC,GAAG,cAAc,CAAA,iBAAA,CAAA;AAAA,MACjB;AAAA,KACF;AAAA,EACF;AAAA,EAEA,sBAAA,CACE,QACA,gBAAA,EACA;AAEA,IAAA,MAAM,SAAA,GAAY,gBAAA,EAAkB,KAAA,EAAM,IAAK,EAAC;AAGhD,IAAA,SAAA,CAAU,IAAA,CAAK,GAAGG,oBAAA,CAAa,MAAM,CAAC,CAAA;AAGtC,IAAA,MAAM,iBAAA,uBAA+C,GAAA,EAAI;AACzD,IAAA,MAAM,iBAAA,uBAA+C,GAAA,EAAI;AACzD,IAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,OAAA,EAAQ,IAAK,SAAA,EAAW;AAC3C,MAAA,IAAI,gBAAA;AACJ,MAAA,IAAI,gBAAA;AAEJ,MAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAC9B,QAAA,gBAAA,GAAmB,gBAAA,GAAmB,IAAA,CAAK,aAAA,CAAc,MAAA,EAAQ,KAAK,CAAA;AAAA,MACxE,CAAA,MAAO;AACL,QAAA,IAAI,OAAO,QAAA,EAAU;AACnB,UAAA,gBAAA,GAAmB,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,QAAA,EAAU,IAAI,CAAA;AAAA,QAC7D;AACA,QAAA,IAAI,OAAO,QAAA,EAAU;AACnB,UAAA,gBAAA,GAAmB,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,QAAA,EAAU,KAAK,CAAA;AAAA,QAC9D;AAAA,MACF;AAEA,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,KAAA,MAAW,YAAY,OAAA,EAAS;AAC9B,UAAA,iBAAA,CAAkB,GAAA,CAAI,UAAU,gBAAgB,CAAA;AAAA,QAClD;AAAA,MACF;AACA,MAAA,IAAI,gBAAA,EAAkB;AACpB,QAAA,KAAA,MAAW,YAAY,OAAA,EAAS;AAC9B,UAAA,iBAAA,CAAkB,GAAA,CAAI,UAAU,gBAAgB,CAAA;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,kBAAA,GAAqB,iBAAA;AAC1B,IAAA,IAAA,CAAK,kBAAA,GAAqB,iBAAA;AAAA,EAC5B;AAAA,EAEA,aAAA,CAAc,YAAoB,QAAA,EAA6B;AAC7D,IAAA,MAAM,YAAA,GAAe,CAAC,QAAA,EAAkB,GAAA,KAAgB;AACtD,MAAA,OAAO,GAAA,CAAI,OAAA;AAAA,QACT,yBAAA;AAAA,QACA,mBAAmB,QAAQ;AAAA,OAC7B;AAAA,IACF,CAAA;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,QAAA,CAAS,UAAU,CAAA,EAAG;AAC3C,MAAA,OAAO,OAAM,QAAA,KAAY,YAAA,CAAa,QAAA,EAAU,UAAU,CAAA;AAAA,IAC5D;AAEA,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,qEAAA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,aAAA,uBAAoB,GAAA,EAAmC;AAC7D,IAAA,OAAO,OAAM,QAAA,KAAY;AACvB,MAAA,IAAI,YAAA,GAAe,aAAA,CAAc,GAAA,CAAI,QAAQ,CAAA;AAC7C,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,YAAA,GAAe,KAAK,YAAA,CAAa,WAAA;AAAA,UAC/B,YAAA,CAAa,UAAU,UAAU;AAAA,SACnC;AACA,QAAA,aAAA,CAAc,GAAA,CAAI,UAAU,YAAY,CAAA;AAAA,MAC1C;AACA,MAAA,OAAO,MAAM,YAAA,EAAa;AAAA,IAC5B,CAAA;AAAA,EACF;AACF;;;;"}
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ var helpers = require('../auth/helpers.cjs.js');
4
+
5
+ const DEFAULT_TTL_MS = 5e3;
6
+ const SWEEP_INTERVAL_MS = 3e4;
7
+ class CachedUserInfoService {
8
+ #delegate;
9
+ #entries;
10
+ #ttlMs;
11
+ #lastSweep = Date.now();
12
+ constructor(delegate, options) {
13
+ this.#delegate = delegate;
14
+ this.#entries = options?.entries ?? /* @__PURE__ */ new Map();
15
+ this.#ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
16
+ }
17
+ async getUserInfo(credentials) {
18
+ const internalCredentials = helpers.toInternalBackstageCredentials(credentials);
19
+ const token = internalCredentials.token;
20
+ if (!token) {
21
+ return this.#delegate.getUserInfo(credentials);
22
+ }
23
+ const now = Date.now();
24
+ if (now - this.#lastSweep > SWEEP_INTERVAL_MS) {
25
+ this.#lastSweep = now;
26
+ for (const [key, entry] of this.#entries) {
27
+ if (entry.expiresAt <= now) {
28
+ this.#entries.delete(key);
29
+ }
30
+ }
31
+ }
32
+ const cached = this.#entries.get(token);
33
+ if (cached && cached.expiresAt > now) {
34
+ return cached.promise;
35
+ }
36
+ const promise = this.#delegate.getUserInfo(credentials).catch((error) => {
37
+ if (this.#entries.get(token)?.promise === promise) {
38
+ this.#entries.delete(token);
39
+ }
40
+ throw error;
41
+ });
42
+ this.#entries.set(token, {
43
+ promise,
44
+ expiresAt: now + this.#ttlMs
45
+ });
46
+ return promise;
47
+ }
48
+ }
49
+
50
+ exports.CachedUserInfoService = CachedUserInfoService;
51
+ //# sourceMappingURL=CachedUserInfoService.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachedUserInfoService.cjs.js","sources":["../../../src/entrypoints/userInfo/CachedUserInfoService.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageUserInfo,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { toInternalBackstageCredentials } from '../auth/helpers';\n\nconst DEFAULT_TTL_MS = 5_000;\nconst SWEEP_INTERVAL_MS = 30_000;\n\nexport type UserInfoCacheEntry = {\n promise: Promise<BackstageUserInfo>;\n expiresAt: number;\n};\n\nexport class CachedUserInfoService implements UserInfoService {\n readonly #delegate: UserInfoService;\n readonly #entries: Map<string, UserInfoCacheEntry>;\n readonly #ttlMs: number;\n #lastSweep: number = Date.now();\n\n constructor(\n delegate: UserInfoService,\n options?: {\n entries?: Map<string, UserInfoCacheEntry>;\n ttlMs?: number;\n },\n ) {\n this.#delegate = delegate;\n this.#entries = options?.entries ?? new Map();\n this.#ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;\n }\n\n async getUserInfo(\n credentials: BackstageCredentials,\n ): Promise<BackstageUserInfo> {\n const internalCredentials = toInternalBackstageCredentials(credentials);\n const token = internalCredentials.token;\n if (!token) {\n return this.#delegate.getUserInfo(credentials);\n }\n\n const now = Date.now();\n\n if (now - this.#lastSweep > SWEEP_INTERVAL_MS) {\n this.#lastSweep = now;\n for (const [key, entry] of this.#entries) {\n if (entry.expiresAt <= now) {\n this.#entries.delete(key);\n }\n }\n }\n\n const cached = this.#entries.get(token);\n if (cached && cached.expiresAt > now) {\n return cached.promise;\n }\n\n const promise = this.#delegate.getUserInfo(credentials).catch(error => {\n if (this.#entries.get(token)?.promise === promise) {\n this.#entries.delete(token);\n }\n throw error;\n });\n\n this.#entries.set(token, {\n promise,\n expiresAt: now + this.#ttlMs,\n });\n\n return promise;\n }\n}\n"],"names":["toInternalBackstageCredentials"],"mappings":";;;;AAuBA,MAAM,cAAA,GAAiB,GAAA;AACvB,MAAM,iBAAA,GAAoB,GAAA;AAOnB,MAAM,qBAAA,CAAiD;AAAA,EACnD,SAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACT,UAAA,GAAqB,KAAK,GAAA,EAAI;AAAA,EAE9B,WAAA,CACE,UACA,OAAA,EAIA;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AACjB,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA,EAAS,OAAA,oBAAW,IAAI,GAAA,EAAI;AAC5C,IAAA,IAAA,CAAK,MAAA,GAAS,SAAS,KAAA,IAAS,cAAA;AAAA,EAClC;AAAA,EAEA,MAAM,YACJ,WAAA,EAC4B;AAC5B,IAAA,MAAM,mBAAA,GAAsBA,uCAA+B,WAAW,CAAA;AACtE,IAAA,MAAM,QAAQ,mBAAA,CAAoB,KAAA;AAClC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,IAAA,CAAK,SAAA,CAAU,WAAA,CAAY,WAAW,CAAA;AAAA,IAC/C;AAEA,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,IAAA,IAAI,GAAA,GAAM,IAAA,CAAK,UAAA,GAAa,iBAAA,EAAmB;AAC7C,MAAA,IAAA,CAAK,UAAA,GAAa,GAAA;AAClB,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,KAAK,QAAA,EAAU;AACxC,QAAA,IAAI,KAAA,CAAM,aAAa,GAAA,EAAK;AAC1B,UAAA,IAAA,CAAK,QAAA,CAAS,OAAO,GAAG,CAAA;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AACtC,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,SAAA,GAAY,GAAA,EAAK;AACpC,MAAA,OAAO,MAAA,CAAO,OAAA;AAAA,IAChB;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,SAAA,CAAU,YAAY,WAAW,CAAA,CAAE,MAAM,CAAA,KAAA,KAAS;AACrE,MAAA,IAAI,KAAK,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA,EAAG,YAAY,OAAA,EAAS;AACjD,QAAA,IAAA,CAAK,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA,MAC5B;AACA,MAAA,MAAM,KAAA;AAAA,IACR,CAAC,CAAA;AAED,IAAA,IAAA,CAAK,QAAA,CAAS,IAAI,KAAA,EAAO;AAAA,MACvB,OAAA;AAAA,MACA,SAAA,EAAW,MAAM,IAAA,CAAK;AAAA,KACvB,CAAA;AAED,IAAA,OAAO,OAAA;AAAA,EACT;AACF;;;;"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var CachedUserInfoService = require('./CachedUserInfoService.cjs.js');
4
5
  var DefaultUserInfoService = require('./DefaultUserInfoService.cjs.js');
5
6
 
6
7
  const userInfoServiceFactory = backendPluginApi.createServiceFactory({
@@ -8,8 +9,14 @@ const userInfoServiceFactory = backendPluginApi.createServiceFactory({
8
9
  deps: {
9
10
  discovery: backendPluginApi.coreServices.discovery
10
11
  },
11
- async factory({ discovery }) {
12
- return new DefaultUserInfoService.DefaultUserInfoService({ discovery });
12
+ createRootContext() {
13
+ return /* @__PURE__ */ new Map();
14
+ },
15
+ async factory({ discovery }, entries) {
16
+ return new CachedUserInfoService.CachedUserInfoService(
17
+ new DefaultUserInfoService.DefaultUserInfoService({ discovery }),
18
+ { entries }
19
+ );
13
20
  }
14
21
  });
15
22
 
@@ -1 +1 @@
1
- {"version":3,"file":"userInfoServiceFactory.cjs.js","sources":["../../../src/entrypoints/userInfo/userInfoServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultUserInfoService } from './DefaultUserInfoService';\n\n/**\n * Authenticated user information retrieval.\n *\n * See {@link @backstage/code-plugin-api#UserInfoService}\n * and {@link https://backstage.io/docs/backend-system/core-services/user-info | the service docs}\n * for more information.\n *\n * @public\n */\nexport const userInfoServiceFactory = createServiceFactory({\n service: coreServices.userInfo,\n deps: {\n discovery: coreServices.discovery,\n },\n async factory({ discovery }) {\n return new DefaultUserInfoService({ discovery });\n },\n});\n"],"names":["createServiceFactory","coreServices","DefaultUserInfoService"],"mappings":";;;;;AA+BO,MAAM,yBAAyBA,qCAAA,CAAqB;AAAA,EACzD,SAASC,6BAAA,CAAa,QAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,WAAWA,6BAAA,CAAa;AAAA,GAC1B;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,SAAA,EAAU,EAAG;AAC3B,IAAA,OAAO,IAAIC,6CAAA,CAAuB,EAAE,SAAA,EAAW,CAAA;AAAA,EACjD;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"userInfoServiceFactory.cjs.js","sources":["../../../src/entrypoints/userInfo/userInfoServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n CachedUserInfoService,\n UserInfoCacheEntry,\n} from './CachedUserInfoService';\nimport { DefaultUserInfoService } from './DefaultUserInfoService';\n\n/**\n * Authenticated user information retrieval.\n *\n * See {@link @backstage/code-plugin-api#UserInfoService}\n * and {@link https://backstage.io/docs/backend-system/core-services/user-info | the service docs}\n * for more information.\n *\n * @public\n */\nexport const userInfoServiceFactory = createServiceFactory({\n service: coreServices.userInfo,\n deps: {\n discovery: coreServices.discovery,\n },\n createRootContext() {\n return new Map<string, UserInfoCacheEntry>();\n },\n async factory({ discovery }, entries) {\n return new CachedUserInfoService(\n new DefaultUserInfoService({ discovery }),\n { entries },\n );\n },\n});\n"],"names":["createServiceFactory","coreServices","CachedUserInfoService","DefaultUserInfoService"],"mappings":";;;;;;AAmCO,MAAM,yBAAyBA,qCAAA,CAAqB;AAAA,EACzD,SAASC,6BAAA,CAAa,QAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,WAAWA,6BAAA,CAAa;AAAA,GAC1B;AAAA,EACA,iBAAA,GAAoB;AAClB,IAAA,2BAAW,GAAA,EAAgC;AAAA,EAC7C,CAAA;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,SAAA,IAAa,OAAA,EAAS;AACpC,IAAA,OAAO,IAAIC,2CAAA;AAAA,MACT,IAAIC,6CAAA,CAAuB,EAAE,SAAA,EAAW,CAAA;AAAA,MACxC,EAAE,OAAA;AAAQ,KACZ;AAAA,EACF;AACF,CAAC;;;;"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "0.17.1-next.1";
5
+ var version = "0.17.1";
6
6
  var packageinfo = {
7
7
  version: version};
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-defaults",
3
- "version": "0.17.1-next.1",
3
+ "version": "0.17.1",
4
4
  "description": "Backend defaults used by Backstage backend apps",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -218,20 +218,20 @@
218
218
  "@aws-sdk/types": "^3.347.0",
219
219
  "@azure/identity": "^4.0.0",
220
220
  "@azure/storage-blob": "^12.5.0",
221
- "@backstage/backend-app-api": "1.7.0-next.0",
222
- "@backstage/backend-dev-utils": "0.1.7",
223
- "@backstage/backend-plugin-api": "1.9.1-next.0",
224
- "@backstage/cli-node": "0.3.2-next.1",
225
- "@backstage/config": "1.3.8-next.0",
226
- "@backstage/config-loader": "1.10.11-next.0",
227
- "@backstage/errors": "1.3.1-next.0",
228
- "@backstage/integration": "2.0.2-next.0",
229
- "@backstage/integration-aws-node": "0.1.22-next.0",
230
- "@backstage/plugin-auth-node": "0.7.1-next.1",
231
- "@backstage/plugin-events-node": "0.4.22-next.0",
232
- "@backstage/plugin-permission-common": "0.9.9-next.1",
233
- "@backstage/plugin-permission-node": "0.10.13-next.0",
234
- "@backstage/types": "1.2.2",
221
+ "@backstage/backend-app-api": "^1.7.0",
222
+ "@backstage/backend-dev-utils": "^0.1.7",
223
+ "@backstage/backend-plugin-api": "^1.9.1",
224
+ "@backstage/cli-node": "^0.3.2",
225
+ "@backstage/config": "^1.3.8",
226
+ "@backstage/config-loader": "^1.10.11",
227
+ "@backstage/errors": "^1.3.1",
228
+ "@backstage/integration": "^2.0.2",
229
+ "@backstage/integration-aws-node": "^0.2.0",
230
+ "@backstage/plugin-auth-node": "^0.7.1",
231
+ "@backstage/plugin-events-node": "^0.4.22",
232
+ "@backstage/plugin-permission-common": "^0.9.9",
233
+ "@backstage/plugin-permission-node": "^0.11.0",
234
+ "@backstage/types": "^1.2.2",
235
235
  "@google-cloud/storage": "^7.0.0",
236
236
  "@keyv/memcache": "^2.0.1",
237
237
  "@keyv/redis": "^4.0.1",
@@ -255,6 +255,7 @@
255
255
  "git-url-parse": "^15.0.0",
256
256
  "helmet": "^6.0.0",
257
257
  "infinispan": "^0.12.0",
258
+ "iovalkey": "^0.3.3",
258
259
  "is-glob": "^4.0.3",
259
260
  "jose": "^5.0.0",
260
261
  "keyv": "^5.2.1",
@@ -285,8 +286,8 @@
285
286
  },
286
287
  "devDependencies": {
287
288
  "@aws-sdk/util-stream-node": "^3.350.0",
288
- "@backstage/backend-test-utils": "1.11.3-next.1",
289
- "@backstage/cli": "0.36.2-next.1",
289
+ "@backstage/backend-test-utils": "^1.11.3",
290
+ "@backstage/cli": "^0.36.2",
290
291
  "@google-cloud/cloud-sql-connector": "^1.4.0",
291
292
  "@types/archiver": "^7.0.0",
292
293
  "@types/base64-stream": "^1.0.2",