@backstage/plugin-events-backend-module-google-pubsub 0.1.8-next.0 → 0.2.0

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,18 @@
1
1
  # @backstage/plugin-events-backend-module-google-pubsub
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 80905b3: Added an optional `filter` property to PubSub consumers/publishers
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/backend-plugin-api@1.7.0
13
+ - @backstage/filter-predicates@0.1.0
14
+ - @backstage/plugin-events-node@0.4.19
15
+
3
16
  ## 0.1.8-next.0
4
17
 
5
18
  ### Patch Changes
package/config.d.ts CHANGED
@@ -14,6 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import { FilterPredicate } from '@backstage/filter-predicates';
18
+
17
19
  export interface Config {
18
20
  events?: {
19
21
  modules?: {
@@ -67,6 +69,37 @@ export interface Config {
67
69
  */
68
70
  targetTopic: string;
69
71
 
72
+ /**
73
+ * Message filter predicate expression.
74
+ *
75
+ * @remarks
76
+ *
77
+ * The value should be a JSON object that represents a filter predicate expression.
78
+ * The object being passed to the filter is on the following form:
79
+ *
80
+ * ```js
81
+ * {
82
+ * message: {
83
+ * // The raw JSON parsed message data
84
+ * data: { ... },
85
+ * // The message attributes as key-value pairs
86
+ * attributes: { key: 'value', ... },
87
+ * }
88
+ * }
89
+ * ```
90
+ *
91
+ * @example
92
+ *
93
+ * ```yaml
94
+ * filter:
95
+ * $any:
96
+ * - 'message.attributes.x-github-event': 'push'
97
+ * - 'message.attributes.x-github-event': 'repository'
98
+ * 'message.data.action': { $in: ['created', 'deleted'] }
99
+ * ```
100
+ */
101
+ filter?: FilterPredicate;
102
+
70
103
  /**
71
104
  * Pub/Sub message attributes are by default copied to the event
72
105
  * metadata field. This setting allows you to override or amend
@@ -128,6 +161,39 @@ export interface Config {
128
161
  */
129
162
  targetTopicName: string;
130
163
 
164
+ /**
165
+ * Event filter predicate expression.
166
+ *
167
+ * @remarks
168
+ *
169
+ * The value should be a JSON object that represents a filter predicate expression.
170
+ * The object being passed to the filter is on the following form:
171
+ *
172
+ * ```js
173
+ * {
174
+ * event: {
175
+ * // The event topic
176
+ * topic: '...',
177
+ * // The raw event payload
178
+ * eventPayload: { ... },
179
+ * // The event metadata as key-value pairs
180
+ * metadata: { key: 'value', ... },
181
+ * }
182
+ * }
183
+ * ```
184
+ *
185
+ * @example
186
+ *
187
+ * ```yaml
188
+ * filter:
189
+ * $any:
190
+ * - 'event.topic': 'github.push'
191
+ * - 'event.topic': 'github.repository'
192
+ * 'event.eventPayload.action': { $in: ['created', 'deleted'] }
193
+ * ```
194
+ */
195
+ filter?: FilterPredicate;
196
+
131
197
  /**
132
198
  * Event metadata fields are by default copied to the Pub/Sub
133
199
  * message attribute. This setting allows you to override or amend
@@ -54,7 +54,18 @@ class EventConsumingGooglePubSubPublisher {
54
54
  onEvent: async (event) => {
55
55
  let status = "failed";
56
56
  try {
57
- const topic = task.mapToTopic(event);
57
+ const context = {
58
+ event: {
59
+ topic: event.topic,
60
+ eventPayload: event.eventPayload,
61
+ metadata: event.metadata
62
+ }
63
+ };
64
+ if (!task.filter(context)) {
65
+ status = "ignored";
66
+ return;
67
+ }
68
+ const topic = task.mapToTopic(context);
58
69
  if (!topic) {
59
70
  status = "ignored";
60
71
  return;
@@ -66,7 +77,7 @@ class EventConsumingGooglePubSubPublisher {
66
77
  }
67
78
  await pubsub.topic(topic.topic).publishMessage({
68
79
  json: event.eventPayload,
69
- attributes: task.mapToAttributes(event)
80
+ attributes: task.mapToAttributes(context)
70
81
  });
71
82
  status = "success";
72
83
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"EventConsumingGooglePubSubPublisher.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts"],"sourcesContent":["/*\n * Copyright 2025 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 LoggerService,\n RootConfigService,\n RootLifecycleService,\n} from '@backstage/backend-plugin-api';\nimport { EventsService } from '@backstage/plugin-events-node';\nimport { PubSub } from '@google-cloud/pubsub';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { readSubscriptionTasksFromConfig } from './config';\nimport { SubscriptionTask } from './types';\n\n/**\n * Reads messages off of the events system and forwards them into Google Pub/Sub\n * topics.\n */\nexport class EventConsumingGooglePubSubPublisher {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #tasks: SubscriptionTask[];\n readonly #pubSubFactory: (projectId: string) => PubSub;\n readonly #metrics: { messages: Counter };\n #activeClientsByProjectId: Map<string, PubSub>;\n\n static create(options: {\n config: RootConfigService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n events: EventsService;\n }) {\n const publisher = new EventConsumingGooglePubSubPublisher({\n logger: options.logger,\n events: options.events,\n tasks: readSubscriptionTasksFromConfig(options.config),\n pubSubFactory: projectId => new PubSub({ projectId }),\n });\n\n options.rootLifecycle.addStartupHook(async () => {\n await publisher.start();\n });\n\n options.rootLifecycle.addBeforeShutdownHook(async () => {\n await publisher.stop();\n });\n\n return publisher;\n }\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n tasks: SubscriptionTask[];\n pubSubFactory: (projectId: string) => PubSub;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#tasks = options.tasks;\n this.#pubSubFactory = options.pubSubFactory;\n\n const meter = metrics.getMeter('default');\n this.#metrics = {\n messages: meter.createCounter(\n 'events.google.pubsub.publisher.messages.total',\n {\n description:\n 'Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher',\n unit: 'short',\n },\n ),\n };\n\n this.#activeClientsByProjectId = new Map();\n }\n\n async start() {\n for (const task of this.#tasks) {\n this.#logger.info(\n `Starting publisher: id=${\n task.id\n } sourceTopics=${task.sourceTopics.join(',')} targetTopic=${\n task.targetTopicPattern\n }`,\n );\n\n await this.#events.subscribe({\n id: `EventConsumingGooglePubSubPublisher.${task.id}`,\n topics: task.sourceTopics,\n onEvent: async event => {\n let status: 'success' | 'failed' | 'ignored' = 'failed';\n try {\n const topic = task.mapToTopic(event);\n if (!topic) {\n status = 'ignored';\n return;\n }\n\n let pubsub = this.#activeClientsByProjectId.get(topic.project);\n if (!pubsub) {\n pubsub = this.#pubSubFactory(topic.project);\n this.#activeClientsByProjectId.set(topic.project, pubsub);\n }\n\n await pubsub.topic(topic.topic).publishMessage({\n json: event.eventPayload,\n attributes: task.mapToAttributes(event),\n });\n\n status = 'success';\n } catch (error) {\n this.#logger.error(\n 'Error publishing Google Pub/Sub message',\n error,\n );\n status = 'failed';\n throw error;\n } finally {\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: status,\n });\n }\n },\n });\n }\n }\n\n async stop() {\n const clients = Array.from(this.#activeClientsByProjectId.values());\n this.#activeClientsByProjectId = new Map();\n\n await Promise.allSettled(\n clients.map(async client => {\n this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);\n await client.close();\n }),\n );\n }\n}\n"],"names":["readSubscriptionTasksFromConfig","PubSub","metrics"],"mappings":"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"}
1
+ {"version":3,"file":"EventConsumingGooglePubSubPublisher.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts"],"sourcesContent":["/*\n * Copyright 2025 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 LoggerService,\n RootConfigService,\n RootLifecycleService,\n} from '@backstage/backend-plugin-api';\nimport { EventsService } from '@backstage/plugin-events-node';\nimport { PubSub } from '@google-cloud/pubsub';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { readSubscriptionTasksFromConfig } from './config';\nimport { EventContext, SubscriptionTask } from './types';\nimport { JsonValue } from '@backstage/types';\n\n/**\n * Reads messages off of the events system and forwards them into Google Pub/Sub\n * topics.\n */\nexport class EventConsumingGooglePubSubPublisher {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #tasks: SubscriptionTask[];\n readonly #pubSubFactory: (projectId: string) => PubSub;\n readonly #metrics: { messages: Counter };\n #activeClientsByProjectId: Map<string, PubSub>;\n\n static create(options: {\n config: RootConfigService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n events: EventsService;\n }) {\n const publisher = new EventConsumingGooglePubSubPublisher({\n logger: options.logger,\n events: options.events,\n tasks: readSubscriptionTasksFromConfig(options.config),\n pubSubFactory: projectId => new PubSub({ projectId }),\n });\n\n options.rootLifecycle.addStartupHook(async () => {\n await publisher.start();\n });\n\n options.rootLifecycle.addBeforeShutdownHook(async () => {\n await publisher.stop();\n });\n\n return publisher;\n }\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n tasks: SubscriptionTask[];\n pubSubFactory: (projectId: string) => PubSub;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#tasks = options.tasks;\n this.#pubSubFactory = options.pubSubFactory;\n\n const meter = metrics.getMeter('default');\n this.#metrics = {\n messages: meter.createCounter(\n 'events.google.pubsub.publisher.messages.total',\n {\n description:\n 'Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher',\n unit: 'short',\n },\n ),\n };\n\n this.#activeClientsByProjectId = new Map();\n }\n\n async start() {\n for (const task of this.#tasks) {\n this.#logger.info(\n `Starting publisher: id=${\n task.id\n } sourceTopics=${task.sourceTopics.join(',')} targetTopic=${\n task.targetTopicPattern\n }`,\n );\n\n await this.#events.subscribe({\n id: `EventConsumingGooglePubSubPublisher.${task.id}`,\n topics: task.sourceTopics,\n onEvent: async event => {\n let status: 'success' | 'failed' | 'ignored' = 'failed';\n try {\n const context: EventContext = {\n event: {\n topic: event.topic,\n eventPayload: event.eventPayload as JsonValue,\n metadata: event.metadata,\n },\n };\n\n if (!task.filter(context)) {\n status = 'ignored';\n return;\n }\n\n const topic = task.mapToTopic(context);\n if (!topic) {\n status = 'ignored';\n return;\n }\n\n let pubsub = this.#activeClientsByProjectId.get(topic.project);\n if (!pubsub) {\n pubsub = this.#pubSubFactory(topic.project);\n this.#activeClientsByProjectId.set(topic.project, pubsub);\n }\n\n await pubsub.topic(topic.topic).publishMessage({\n json: event.eventPayload,\n attributes: task.mapToAttributes(context),\n });\n\n status = 'success';\n } catch (error) {\n this.#logger.error(\n 'Error publishing Google Pub/Sub message',\n error,\n );\n status = 'failed';\n throw error;\n } finally {\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: status,\n });\n }\n },\n });\n }\n }\n\n async stop() {\n const clients = Array.from(this.#activeClientsByProjectId.values());\n this.#activeClientsByProjectId = new Map();\n\n await Promise.allSettled(\n clients.map(async client => {\n this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);\n await client.close();\n }),\n );\n }\n}\n"],"names":["readSubscriptionTasksFromConfig","PubSub","metrics"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
+ var filterPredicates = require('@backstage/filter-predicates');
4
5
  var createPatternResolver = require('../util/createPatternResolver.cjs.js');
5
6
 
6
7
  function readSubscriptionTasksFromConfig(rootConfig) {
@@ -18,12 +19,14 @@ function readSubscriptionTasksFromConfig(rootConfig) {
18
19
  }
19
20
  const config = subscriptionsConfig.getConfig(subscriptionId);
20
21
  const sourceTopics = readSourceTopics(config);
22
+ const filter = readFilter(config);
21
23
  const mapToTopic = readTopicMapper(config);
22
24
  const mapToAttributes = readAttributeMapper(config);
23
25
  return {
24
26
  id: subscriptionId,
25
27
  sourceTopics,
26
28
  targetTopicPattern: config.getString("targetTopicName"),
29
+ filter,
27
30
  mapToTopic,
28
31
  mapToAttributes
29
32
  };
@@ -35,6 +38,15 @@ function readSourceTopics(config) {
35
38
  }
36
39
  return [config.getString("sourceTopic")];
37
40
  }
41
+ function readFilter(config) {
42
+ const predicate = filterPredicates.readOptionalFilterPredicateFromConfig(config, {
43
+ key: "filter"
44
+ });
45
+ if (!predicate) {
46
+ return () => true;
47
+ }
48
+ return filterPredicates.filterPredicateToFilterFunction(predicate);
49
+ }
38
50
  function readTopicMapper(config) {
39
51
  const regex = /^projects\/([^/]+)\/topics\/(.+)$/;
40
52
  const targetTopicPattern = config.getString("targetTopicName");
@@ -45,9 +57,9 @@ function readTopicMapper(config) {
45
57
  );
46
58
  }
47
59
  const patternResolver = createPatternResolver.createPatternResolver(targetTopicPattern);
48
- return (event) => {
60
+ return (context) => {
49
61
  try {
50
- parts = patternResolver({ event }).match(regex);
62
+ parts = patternResolver(context).match(regex);
51
63
  if (!parts) {
52
64
  return void 0;
53
65
  }
@@ -67,9 +79,9 @@ function readAttributeMapper(config) {
67
79
  for (const key of eventMetadata?.keys() ?? []) {
68
80
  const valuePattern = eventMetadata.getString(key);
69
81
  const patternResolver = createPatternResolver.createPatternResolver(valuePattern);
70
- setters.push(({ event, attributes }) => {
82
+ setters.push(({ context, attributes }) => {
71
83
  try {
72
- const value = patternResolver({ event });
84
+ const value = patternResolver(context);
73
85
  if (value) {
74
86
  attributes[key] = value;
75
87
  }
@@ -78,9 +90,9 @@ function readAttributeMapper(config) {
78
90
  });
79
91
  }
80
92
  }
81
- return (event) => {
93
+ return (context) => {
82
94
  const result = {};
83
- for (const [key, value] of Object.entries(event.metadata ?? {})) {
95
+ for (const [key, value] of Object.entries(context.event.metadata ?? {})) {
84
96
  if (value) {
85
97
  if (typeof value === "string") {
86
98
  result[key] = value;
@@ -90,7 +102,7 @@ function readAttributeMapper(config) {
90
102
  }
91
103
  }
92
104
  for (const setter of setters) {
93
- setter({ event, attributes: result });
105
+ setter({ context, attributes: result });
94
106
  }
95
107
  return result;
96
108
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/config.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { EventParams } from '@backstage/plugin-events-node';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.eventConsumingGooglePubSubPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const sourceTopics = readSourceTopics(config);\n const mapToTopic = readTopicMapper(config);\n const mapToAttributes = readAttributeMapper(config);\n\n return {\n id: subscriptionId,\n sourceTopics: sourceTopics,\n targetTopicPattern: config.getString('targetTopicName'),\n mapToTopic,\n mapToAttributes,\n };\n });\n}\n\nfunction readSourceTopics(config: Config): string[] {\n if (Array.isArray(config.getOptional('sourceTopic'))) {\n return config.getStringArray('sourceTopic');\n }\n return [config.getString('sourceTopic')];\n}\n\n/**\n * Handles the `targetTopicName` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (event: EventParams) => { project: string; topic: string } | undefined {\n const regex = /^projects\\/([^/]+)\\/topics\\/(.+)$/;\n\n const targetTopicPattern = config.getString('targetTopicName');\n let parts = targetTopicPattern.match(regex);\n if (!parts) {\n throw new InputError(\n `Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got '${targetTopicPattern}'`,\n );\n }\n\n const patternResolver = createPatternResolver(targetTopicPattern);\n\n return event => {\n try {\n parts = patternResolver({ event }).match(regex);\n if (!parts) {\n return undefined;\n }\n return {\n project: parts[1],\n topic: parts[2],\n };\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `messageAttributes` configuration field.\n */\nfunction readAttributeMapper(\n config: Config,\n): (event: EventParams) => Record<string, string> {\n const setters = new Array<\n (options: {\n event: EventParams;\n attributes: Record<string, string>;\n }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('messageAttributes');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ event, attributes }) => {\n try {\n const value = patternResolver({ event });\n if (value) {\n attributes[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return event => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(event.metadata ?? {})) {\n if (value) {\n if (typeof value === 'string') {\n result[key] = value;\n } else if (Array.isArray(value) && value.length > 0) {\n // Google Pub/Sub does not support array values\n result[key] = value.join(',');\n }\n }\n }\n for (const setter of setters) {\n setter({ event, attributes: result });\n }\n return result;\n };\n}\n"],"names":["InputError","createPatternResolver"],"mappings":"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"}
1
+ {"version":3,"file":"config.cjs.js","sources":["../../src/EventConsumingGooglePubSubPublisher/config.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport {\n readOptionalFilterPredicateFromConfig,\n filterPredicateToFilterFunction,\n} from '@backstage/filter-predicates';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { EventContext, SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.eventConsumingGooglePubSubPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const sourceTopics = readSourceTopics(config);\n const filter = readFilter(config);\n const mapToTopic = readTopicMapper(config);\n const mapToAttributes = readAttributeMapper(config);\n\n return {\n id: subscriptionId,\n sourceTopics: sourceTopics,\n targetTopicPattern: config.getString('targetTopicName'),\n filter,\n mapToTopic,\n mapToAttributes,\n };\n });\n}\n\nfunction readSourceTopics(config: Config): string[] {\n if (Array.isArray(config.getOptional('sourceTopic'))) {\n return config.getStringArray('sourceTopic');\n }\n return [config.getString('sourceTopic')];\n}\n\n/**\n * Handles the `filter` configuration field.\n */\nfunction readFilter(config: Config): (context: EventContext) => boolean {\n const predicate = readOptionalFilterPredicateFromConfig(config, {\n key: 'filter',\n });\n if (!predicate) {\n return () => true;\n }\n\n return filterPredicateToFilterFunction(predicate);\n}\n\n/**\n * Handles the `targetTopicName` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (context: EventContext) => { project: string; topic: string } | undefined {\n const regex = /^projects\\/([^/]+)\\/topics\\/(.+)$/;\n\n const targetTopicPattern = config.getString('targetTopicName');\n let parts = targetTopicPattern.match(regex);\n if (!parts) {\n throw new InputError(\n `Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got '${targetTopicPattern}'`,\n );\n }\n\n const patternResolver = createPatternResolver(targetTopicPattern);\n\n return context => {\n try {\n parts = patternResolver(context).match(regex);\n if (!parts) {\n return undefined;\n }\n return {\n project: parts[1],\n topic: parts[2],\n };\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `messageAttributes` configuration field.\n */\nfunction readAttributeMapper(\n config: Config,\n): (context: EventContext) => Record<string, string> {\n const setters = new Array<\n (options: {\n context: EventContext;\n attributes: Record<string, string>;\n }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('messageAttributes');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ context, attributes }) => {\n try {\n const value = patternResolver(context);\n if (value) {\n attributes[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return context => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(context.event.metadata ?? {})) {\n if (value) {\n if (typeof value === 'string') {\n result[key] = value;\n } else if (Array.isArray(value) && value.length > 0) {\n // Google Pub/Sub does not support array values\n result[key] = value.join(',');\n }\n }\n }\n for (const setter of setters) {\n setter({ context, attributes: result });\n }\n return result;\n };\n}\n"],"names":["InputError","readOptionalFilterPredicateFromConfig","filterPredicateToFilterFunction","createPatternResolver"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var errors = require('@backstage/errors');
3
4
  var pubsub = require('@google-cloud/pubsub');
4
5
  var api = require('@opentelemetry/api');
5
6
  var config = require('./config.cjs.js');
@@ -127,14 +128,31 @@ class GooglePubSubConsumingEventPublisher {
127
128
  );
128
129
  }
129
130
  #messageToEvent(message, task) {
130
- const topic = task.mapToTopic(message);
131
+ let eventPayload;
132
+ try {
133
+ eventPayload = JSON.parse(message.data.toString());
134
+ } catch (error) {
135
+ throw new errors.ForwardedError("Payload was not valid JSON", error);
136
+ }
137
+ const attributes = message.attributes;
138
+ const context = {
139
+ message: {
140
+ data: eventPayload,
141
+ attributes
142
+ }
143
+ };
144
+ if (!task.filter(context)) {
145
+ return void 0;
146
+ }
147
+ const topic = task.mapToTopic(context);
131
148
  if (!topic) {
132
149
  return void 0;
133
150
  }
151
+ const metadata = task.mapToMetadata(context);
134
152
  return {
135
153
  topic,
136
- eventPayload: JSON.parse(message.data.toString()),
137
- metadata: task.mapToMetadata(message)
154
+ eventPayload,
155
+ metadata
138
156
  };
139
157
  }
140
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GooglePubSubConsumingEventPublisher.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts"],"sourcesContent":["/*\n * Copyright 2025 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 LoggerService,\n RootConfigService,\n RootLifecycleService,\n} from '@backstage/backend-plugin-api';\nimport { EventParams, EventsService } from '@backstage/plugin-events-node';\nimport { Message, PubSub, Subscription } from '@google-cloud/pubsub';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { readSubscriptionTasksFromConfig } from './config';\nimport { SubscriptionTask } from './types';\n\n/**\n * Reads messages off of Google Pub/Sub subscriptions and forwards them into the\n * Backstage events system.\n */\nexport class GooglePubSubConsumingEventPublisher {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #tasks: SubscriptionTask[];\n readonly #pubSubFactory: (projectId: string) => PubSub;\n readonly #metrics: { messages: Counter };\n #activeClientsByProjectId: Map<string, PubSub>;\n #activeSubscriptions: Subscription[];\n\n static create(options: {\n config: RootConfigService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n events: EventsService;\n }) {\n const publisher = new GooglePubSubConsumingEventPublisher({\n logger: options.logger,\n events: options.events,\n tasks: readSubscriptionTasksFromConfig(options.config),\n pubSubFactory: projectId => new PubSub({ projectId }),\n });\n\n options.rootLifecycle.addStartupHook(async () => {\n await publisher.start();\n });\n\n options.rootLifecycle.addBeforeShutdownHook(async () => {\n await publisher.stop();\n });\n\n return publisher;\n }\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n tasks: SubscriptionTask[];\n pubSubFactory: (projectId: string) => PubSub;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#tasks = options.tasks;\n this.#pubSubFactory = options.pubSubFactory;\n\n const meter = metrics.getMeter('default');\n this.#metrics = {\n messages: meter.createCounter(\n 'events.google.pubsub.consumer.messages.total',\n {\n description:\n 'Number of Pub/Sub messages received by GooglePubSubConsumingEventPublisher',\n unit: 'short',\n },\n ),\n };\n\n this.#activeClientsByProjectId = new Map();\n this.#activeSubscriptions = [];\n }\n\n async start() {\n for (const task of this.#tasks) {\n this.#logger.info(\n `Starting subscription: id=${task.id} project=${task.project} subscription=${task.subscription}`,\n );\n\n let pubsub = this.#activeClientsByProjectId.get(task.project);\n if (!pubsub) {\n pubsub = this.#pubSubFactory(task.project);\n this.#activeClientsByProjectId.set(task.project, pubsub);\n }\n\n // You cannot control the actual batch size delivered to the client from\n // pubsub, so these settings actually instead control the rate at which\n // messages are released to our event handlers by the pubsub library. This\n // means that there may be significantly more than maxMessages messages\n // pending in memory before we see them. Thus, the settings here are rather\n // chosen so as to limit the concurrency of hammering consumers (the catalog\n // etc).\n const subscription = pubsub.subscription(task.subscription, {\n flowControl: {\n maxMessages: 5,\n allowExcessMessages: false,\n },\n });\n\n this.#activeSubscriptions.push(subscription);\n\n subscription.on('error', error => {\n this.#logger.error(\n `Error reading Google Pub/Sub subscription: ${task.id}`,\n error,\n );\n });\n\n subscription.on('message', async message => {\n let event: EventParams;\n try {\n event = this.#messageToEvent(message, task)!;\n if (!event) {\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'ignored',\n });\n return;\n }\n } catch (error) {\n this.#logger.error('Error processing Google Pub/Sub message', error);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'failed',\n });\n // We unconditionally ACK the message in this case, because if it's\n // broken, it will still be broken next time around, so there is no\n // point in re-delivering it.\n message.ack();\n return;\n }\n\n try {\n await this.#events.publish(event);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'success',\n });\n message.ack();\n } catch (error) {\n this.#logger.error('Error processing Google Pub/Sub message', error);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'failed',\n });\n // We fast-NACK the message in this case because this may be a\n // transient problem with the events backend.\n message.nack();\n }\n });\n }\n }\n\n async stop() {\n const subscriptions = this.#activeSubscriptions;\n const clients = Array.from(this.#activeClientsByProjectId.values());\n\n this.#activeSubscriptions = [];\n this.#activeClientsByProjectId = new Map();\n\n await Promise.allSettled(\n subscriptions.map(async subscription => {\n this.#logger.info(\n `Closing Google Pub/Sub subscription: ${subscription.name}`,\n );\n await subscription.close();\n }),\n );\n\n await Promise.allSettled(\n clients.map(async client => {\n this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);\n await client.close();\n }),\n );\n }\n\n #messageToEvent(\n message: Message,\n task: SubscriptionTask,\n ): EventParams | undefined {\n const topic = task.mapToTopic(message);\n if (!topic) {\n return undefined;\n }\n return {\n topic,\n eventPayload: JSON.parse(message.data.toString()),\n metadata: task.mapToMetadata(message),\n };\n }\n}\n"],"names":["readSubscriptionTasksFromConfig","PubSub","metrics"],"mappings":"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"}
1
+ {"version":3,"file":"GooglePubSubConsumingEventPublisher.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts"],"sourcesContent":["/*\n * Copyright 2025 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 LoggerService,\n RootConfigService,\n RootLifecycleService,\n} from '@backstage/backend-plugin-api';\nimport { ForwardedError } from '@backstage/errors';\nimport { EventParams, EventsService } from '@backstage/plugin-events-node';\nimport { JsonValue } from '@backstage/types';\nimport { Message, PubSub, Subscription } from '@google-cloud/pubsub';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { readSubscriptionTasksFromConfig } from './config';\nimport { MessageContext, SubscriptionTask } from './types';\n\n/**\n * Reads messages off of Google Pub/Sub subscriptions and forwards them into the\n * Backstage events system.\n */\nexport class GooglePubSubConsumingEventPublisher {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #tasks: SubscriptionTask[];\n readonly #pubSubFactory: (projectId: string) => PubSub;\n readonly #metrics: { messages: Counter };\n #activeClientsByProjectId: Map<string, PubSub>;\n #activeSubscriptions: Subscription[];\n\n static create(options: {\n config: RootConfigService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n events: EventsService;\n }) {\n const publisher = new GooglePubSubConsumingEventPublisher({\n logger: options.logger,\n events: options.events,\n tasks: readSubscriptionTasksFromConfig(options.config),\n pubSubFactory: projectId => new PubSub({ projectId }),\n });\n\n options.rootLifecycle.addStartupHook(async () => {\n await publisher.start();\n });\n\n options.rootLifecycle.addBeforeShutdownHook(async () => {\n await publisher.stop();\n });\n\n return publisher;\n }\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n tasks: SubscriptionTask[];\n pubSubFactory: (projectId: string) => PubSub;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#tasks = options.tasks;\n this.#pubSubFactory = options.pubSubFactory;\n\n const meter = metrics.getMeter('default');\n this.#metrics = {\n messages: meter.createCounter(\n 'events.google.pubsub.consumer.messages.total',\n {\n description:\n 'Number of Pub/Sub messages received by GooglePubSubConsumingEventPublisher',\n unit: 'short',\n },\n ),\n };\n\n this.#activeClientsByProjectId = new Map();\n this.#activeSubscriptions = [];\n }\n\n async start() {\n for (const task of this.#tasks) {\n this.#logger.info(\n `Starting subscription: id=${task.id} project=${task.project} subscription=${task.subscription}`,\n );\n\n let pubsub = this.#activeClientsByProjectId.get(task.project);\n if (!pubsub) {\n pubsub = this.#pubSubFactory(task.project);\n this.#activeClientsByProjectId.set(task.project, pubsub);\n }\n\n // You cannot control the actual batch size delivered to the client from\n // pubsub, so these settings actually instead control the rate at which\n // messages are released to our event handlers by the pubsub library. This\n // means that there may be significantly more than maxMessages messages\n // pending in memory before we see them. Thus, the settings here are rather\n // chosen so as to limit the concurrency of hammering consumers (the catalog\n // etc).\n const subscription = pubsub.subscription(task.subscription, {\n flowControl: {\n maxMessages: 5,\n allowExcessMessages: false,\n },\n });\n\n this.#activeSubscriptions.push(subscription);\n\n subscription.on('error', error => {\n this.#logger.error(\n `Error reading Google Pub/Sub subscription: ${task.id}`,\n error,\n );\n });\n\n subscription.on('message', async message => {\n let event: EventParams;\n try {\n event = this.#messageToEvent(message, task)!;\n if (!event) {\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'ignored',\n });\n return;\n }\n } catch (error) {\n this.#logger.error('Error processing Google Pub/Sub message', error);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'failed',\n });\n // We unconditionally ACK the message in this case, because if it's\n // broken, it will still be broken next time around, so there is no\n // point in re-delivering it.\n message.ack();\n return;\n }\n\n try {\n await this.#events.publish(event);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'success',\n });\n message.ack();\n } catch (error) {\n this.#logger.error('Error processing Google Pub/Sub message', error);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'failed',\n });\n // We fast-NACK the message in this case because this may be a\n // transient problem with the events backend.\n message.nack();\n }\n });\n }\n }\n\n async stop() {\n const subscriptions = this.#activeSubscriptions;\n const clients = Array.from(this.#activeClientsByProjectId.values());\n\n this.#activeSubscriptions = [];\n this.#activeClientsByProjectId = new Map();\n\n await Promise.allSettled(\n subscriptions.map(async subscription => {\n this.#logger.info(\n `Closing Google Pub/Sub subscription: ${subscription.name}`,\n );\n await subscription.close();\n }),\n );\n\n await Promise.allSettled(\n clients.map(async client => {\n this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);\n await client.close();\n }),\n );\n }\n\n #messageToEvent(\n message: Message,\n task: SubscriptionTask,\n ): EventParams | undefined {\n let eventPayload: JsonValue;\n try {\n eventPayload = JSON.parse(message.data.toString());\n } catch (error) {\n throw new ForwardedError('Payload was not valid JSON', error);\n }\n\n const attributes = message.attributes;\n\n const context: MessageContext = {\n message: {\n data: eventPayload,\n attributes,\n },\n };\n\n if (!task.filter(context)) {\n return undefined;\n }\n\n const topic = task.mapToTopic(context);\n if (!topic) {\n return undefined;\n }\n\n const metadata = task.mapToMetadata(context);\n return {\n topic,\n eventPayload,\n metadata,\n };\n }\n}\n"],"names":["readSubscriptionTasksFromConfig","PubSub","metrics","ForwardedError"],"mappings":";;;;;;;AAiCO,MAAM,mCAAA,CAAoC;AAAA,EACtC,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,cAAA;AAAA,EACA,QAAA;AAAA,EACT,yBAAA;AAAA,EACA,oBAAA;AAAA,EAEA,OAAO,OAAO,OAAA,EAKX;AACD,IAAA,MAAM,SAAA,GAAY,IAAI,mCAAA,CAAoC;AAAA,MACxD,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,KAAA,EAAOA,sCAAA,CAAgC,OAAA,CAAQ,MAAM,CAAA;AAAA,MACrD,eAAe,CAAA,SAAA,KAAa,IAAIC,aAAA,CAAO,EAAE,WAAW;AAAA,KACrD,CAAA;AAED,IAAA,OAAA,CAAQ,aAAA,CAAc,eAAe,YAAY;AAC/C,MAAA,MAAM,UAAU,KAAA,EAAM;AAAA,IACxB,CAAC,CAAA;AAED,IAAA,OAAA,CAAQ,aAAA,CAAc,sBAAsB,YAAY;AACtD,MAAA,MAAM,UAAU,IAAA,EAAK;AAAA,IACvB,CAAC,CAAA;AAED,IAAA,OAAO,SAAA;AAAA,EACT;AAAA,EAEA,YAAY,OAAA,EAKT;AACD,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,MAAA;AACvB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,MAAA;AACvB,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,KAAA;AACtB,IAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,aAAA;AAE9B,IAAA,MAAM,KAAA,GAAQC,WAAA,CAAQ,QAAA,CAAS,SAAS,CAAA;AACxC,IAAA,IAAA,CAAK,QAAA,GAAW;AAAA,MACd,UAAU,KAAA,CAAM,aAAA;AAAA,QACd,8CAAA;AAAA,QACA;AAAA,UACE,WAAA,EACE,4EAAA;AAAA,UACF,IAAA,EAAM;AAAA;AACR;AACF,KACF;AAEA,IAAA,IAAA,CAAK,yBAAA,uBAAgC,GAAA,EAAI;AACzC,IAAA,IAAA,CAAK,uBAAuB,EAAC;AAAA,EAC/B;AAAA,EAEA,MAAM,KAAA,GAAQ;AACZ,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,MAAA,EAAQ;AAC9B,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA;AAAA,QACX,CAAA,0BAAA,EAA6B,KAAK,EAAE,CAAA,SAAA,EAAY,KAAK,OAAO,CAAA,cAAA,EAAiB,KAAK,YAAY,CAAA;AAAA,OAChG;AAEA,MAAA,IAAI,MAAA,GAAS,IAAA,CAAK,yBAAA,CAA0B,GAAA,CAAI,KAAK,OAAO,CAAA;AAC5D,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,MAAA,GAAS,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,OAAO,CAAA;AACzC,QAAA,IAAA,CAAK,yBAAA,CAA0B,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,MAAM,CAAA;AAAA,MACzD;AASA,MAAA,MAAM,YAAA,GAAe,MAAA,CAAO,YAAA,CAAa,IAAA,CAAK,YAAA,EAAc;AAAA,QAC1D,WAAA,EAAa;AAAA,UACX,WAAA,EAAa,CAAA;AAAA,UACb,mBAAA,EAAqB;AAAA;AACvB,OACD,CAAA;AAED,MAAA,IAAA,CAAK,oBAAA,CAAqB,KAAK,YAAY,CAAA;AAE3C,MAAA,YAAA,CAAa,EAAA,CAAG,SAAS,CAAA,KAAA,KAAS;AAChC,QAAA,IAAA,CAAK,OAAA,CAAQ,KAAA;AAAA,UACX,CAAA,2CAAA,EAA8C,KAAK,EAAE,CAAA,CAAA;AAAA,UACrD;AAAA,SACF;AAAA,MACF,CAAC,CAAA;AAED,MAAA,YAAA,CAAa,EAAA,CAAG,SAAA,EAAW,OAAM,OAAA,KAAW;AAC1C,QAAA,IAAI,KAAA;AACJ,QAAA,IAAI;AACF,UAAA,KAAA,GAAQ,IAAA,CAAK,eAAA,CAAgB,OAAA,EAAS,IAAI,CAAA;AAC1C,UAAA,IAAI,CAAC,KAAA,EAAO;AACV,YAAA,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,CAAA,EAAG;AAAA,cAC5B,cAAc,IAAA,CAAK,EAAA;AAAA,cACnB,MAAA,EAAQ;AAAA,aACT,CAAA;AACD,YAAA;AAAA,UACF;AAAA,QACF,SAAS,KAAA,EAAO;AACd,UAAA,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,yCAAA,EAA2C,KAAK,CAAA;AACnE,UAAA,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,CAAA,EAAG;AAAA,YAC5B,cAAc,IAAA,CAAK,EAAA;AAAA,YACnB,MAAA,EAAQ;AAAA,WACT,CAAA;AAID,UAAA,OAAA,CAAQ,GAAA,EAAI;AACZ,UAAA;AAAA,QACF;AAEA,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,KAAK,CAAA;AAChC,UAAA,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,CAAA,EAAG;AAAA,YAC5B,cAAc,IAAA,CAAK,EAAA;AAAA,YACnB,MAAA,EAAQ;AAAA,WACT,CAAA;AACD,UAAA,OAAA,CAAQ,GAAA,EAAI;AAAA,QACd,SAAS,KAAA,EAAO;AACd,UAAA,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,yCAAA,EAA2C,KAAK,CAAA;AACnE,UAAA,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,CAAA,EAAG;AAAA,YAC5B,cAAc,IAAA,CAAK,EAAA;AAAA,YACnB,MAAA,EAAQ;AAAA,WACT,CAAA;AAGD,UAAA,OAAA,CAAQ,IAAA,EAAK;AAAA,QACf;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,IAAA,GAAO;AACX,IAAA,MAAM,gBAAgB,IAAA,CAAK,oBAAA;AAC3B,IAAA,MAAM,UAAU,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,yBAAA,CAA0B,QAAQ,CAAA;AAElE,IAAA,IAAA,CAAK,uBAAuB,EAAC;AAC7B,IAAA,IAAA,CAAK,yBAAA,uBAAgC,GAAA,EAAI;AAEzC,IAAA,MAAM,OAAA,CAAQ,UAAA;AAAA,MACZ,aAAA,CAAc,GAAA,CAAI,OAAM,YAAA,KAAgB;AACtC,QAAA,IAAA,CAAK,OAAA,CAAQ,IAAA;AAAA,UACX,CAAA,qCAAA,EAAwC,aAAa,IAAI,CAAA;AAAA,SAC3D;AACA,QAAA,MAAM,aAAa,KAAA,EAAM;AAAA,MAC3B,CAAC;AAAA,KACH;AAEA,IAAA,MAAM,OAAA,CAAQ,UAAA;AAAA,MACZ,OAAA,CAAQ,GAAA,CAAI,OAAM,MAAA,KAAU;AAC1B,QAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,CAAA,+BAAA,EAAkC,MAAA,CAAO,SAAS,CAAA,CAAE,CAAA;AACtE,QAAA,MAAM,OAAO,KAAA,EAAM;AAAA,MACrB,CAAC;AAAA,KACH;AAAA,EACF;AAAA,EAEA,eAAA,CACE,SACA,IAAA,EACyB;AACzB,IAAA,IAAI,YAAA;AACJ,IAAA,IAAI;AACF,MAAA,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA;AAAA,IACnD,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAIC,qBAAA,CAAe,4BAAA,EAA8B,KAAK,CAAA;AAAA,IAC9D;AAEA,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,OAAA,GAA0B;AAAA,MAC9B,OAAA,EAAS;AAAA,QACP,IAAA,EAAM,YAAA;AAAA,QACN;AAAA;AACF,KACF;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,EAAG;AACzB,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA;AACrC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,aAAA,CAAc,OAAO,CAAA;AAC3C,IAAA,OAAO;AAAA,MACL,KAAA;AAAA,MACA,YAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;;;;"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
+ var filterPredicates = require('@backstage/filter-predicates');
4
5
  var createPatternResolver = require('../util/createPatternResolver.cjs.js');
5
6
 
6
7
  function readSubscriptionTasksFromConfig(rootConfig) {
@@ -18,17 +19,28 @@ function readSubscriptionTasksFromConfig(rootConfig) {
18
19
  }
19
20
  const config = subscriptionsConfig.getConfig(subscriptionId);
20
21
  const { project, subscription } = readSubscriptionName(config);
22
+ const filter = readFilter(config);
21
23
  const mapToTopic = readTopicMapper(config);
22
24
  const mapToMetadata = readMetadataMapper(config);
23
25
  return {
24
26
  id: subscriptionId,
25
27
  project,
26
28
  subscription,
29
+ filter,
27
30
  mapToTopic,
28
31
  mapToMetadata
29
32
  };
30
33
  });
31
34
  }
35
+ function readFilter(config) {
36
+ const predicate = filterPredicates.readOptionalFilterPredicateFromConfig(config, {
37
+ key: "filter"
38
+ });
39
+ if (!predicate) {
40
+ return () => true;
41
+ }
42
+ return filterPredicates.filterPredicateToFilterFunction(predicate);
43
+ }
32
44
  function readSubscriptionName(config) {
33
45
  const subscriptionName = config.getString("subscriptionName");
34
46
  const parts = subscriptionName.match(
@@ -49,7 +61,7 @@ function readTopicMapper(config) {
49
61
  const patternResolver = createPatternResolver.createPatternResolver(targetTopicPattern);
50
62
  return (message) => {
51
63
  try {
52
- return patternResolver({ message });
64
+ return patternResolver(message);
53
65
  } catch {
54
66
  return void 0;
55
67
  }
@@ -62,9 +74,9 @@ function readMetadataMapper(config) {
62
74
  for (const key of eventMetadata?.keys() ?? []) {
63
75
  const valuePattern = eventMetadata.getString(key);
64
76
  const patternResolver = createPatternResolver.createPatternResolver(valuePattern);
65
- setters.push(({ message, metadata }) => {
77
+ setters.push(({ context, metadata }) => {
66
78
  try {
67
- const value = patternResolver({ message });
79
+ const value = patternResolver(context);
68
80
  if (value) {
69
81
  metadata[key] = value;
70
82
  }
@@ -73,12 +85,12 @@ function readMetadataMapper(config) {
73
85
  });
74
86
  }
75
87
  }
76
- return (message) => {
88
+ return (context) => {
77
89
  const result = {
78
- ...message.attributes
90
+ ...context.message.attributes
79
91
  };
80
92
  for (const setter of setters) {
81
- setter({ message, metadata: result });
93
+ setter({ context, metadata: result });
82
94
  }
83
95
  return result;
84
96
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/config.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { Message } from '@google-cloud/pubsub';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const { project, subscription } = readSubscriptionName(config);\n const mapToTopic = readTopicMapper(config);\n const mapToMetadata = readMetadataMapper(config);\n\n return {\n id: subscriptionId,\n project,\n subscription,\n mapToTopic,\n mapToMetadata,\n };\n });\n}\n\nfunction readSubscriptionName(config: Config): {\n project: string;\n subscription: string;\n} {\n const subscriptionName = config.getString('subscriptionName');\n const parts = subscriptionName.match(\n /^projects\\/([^/]+)\\/subscriptions\\/(.+)$/,\n );\n if (!parts) {\n throw new InputError(\n `Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`,\n );\n }\n return {\n project: parts[1],\n subscription: parts[2],\n };\n}\n\n/**\n * Handles the `targetTopic` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (message: Message) => string | undefined {\n const targetTopicPattern = config.getString('targetTopic');\n const patternResolver = createPatternResolver(targetTopicPattern);\n return message => {\n try {\n return patternResolver({ message });\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `eventMetadata` configuration field.\n */\nfunction readMetadataMapper(\n config: Config,\n): (message: Message) => Record<string, string> {\n const setters = new Array<\n (options: { message: Message; metadata: Record<string, string> }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('eventMetadata');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ message, metadata }) => {\n try {\n const value = patternResolver({ message });\n if (value) {\n metadata[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return message => {\n const result: Record<string, string> = {\n ...message.attributes,\n };\n for (const setter of setters) {\n setter({ message, metadata: result });\n }\n return result;\n };\n}\n"],"names":["InputError","createPatternResolver"],"mappings":"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"}
1
+ {"version":3,"file":"config.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/config.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport {\n readOptionalFilterPredicateFromConfig,\n filterPredicateToFilterFunction,\n} from '@backstage/filter-predicates';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { MessageContext, SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const { project, subscription } = readSubscriptionName(config);\n const filter = readFilter(config);\n const mapToTopic = readTopicMapper(config);\n const mapToMetadata = readMetadataMapper(config);\n\n return {\n id: subscriptionId,\n project,\n subscription,\n filter,\n mapToTopic,\n mapToMetadata,\n };\n });\n}\n\nfunction readFilter(config: Config): (message: MessageContext) => boolean {\n const predicate = readOptionalFilterPredicateFromConfig(config, {\n key: 'filter',\n });\n if (!predicate) {\n return () => true;\n }\n\n return filterPredicateToFilterFunction(predicate);\n}\n\nfunction readSubscriptionName(config: Config): {\n project: string;\n subscription: string;\n} {\n const subscriptionName = config.getString('subscriptionName');\n const parts = subscriptionName.match(\n /^projects\\/([^/]+)\\/subscriptions\\/(.+)$/,\n );\n if (!parts) {\n throw new InputError(\n `Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`,\n );\n }\n return {\n project: parts[1],\n subscription: parts[2],\n };\n}\n\n/**\n * Handles the `targetTopic` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (message: MessageContext) => string | undefined {\n const targetTopicPattern = config.getString('targetTopic');\n const patternResolver = createPatternResolver(targetTopicPattern);\n return message => {\n try {\n return patternResolver(message);\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `eventMetadata` configuration field.\n */\nfunction readMetadataMapper(\n config: Config,\n): (message: MessageContext) => Record<string, string> {\n const setters = new Array<\n (options: {\n context: MessageContext;\n metadata: Record<string, string>;\n }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('eventMetadata');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ context, metadata }) => {\n try {\n const value = patternResolver(context);\n if (value) {\n metadata[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return context => {\n const result: Record<string, string> = {\n ...context.message.attributes,\n };\n for (const setter of setters) {\n setter({ context, metadata: result });\n }\n return result;\n };\n}\n"],"names":["InputError","readOptionalFilterPredicateFromConfig","filterPredicateToFilterFunction","createPatternResolver"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
+ var filterPredicates = require('@backstage/filter-predicates');
4
5
 
5
6
  function createPatternResolver(pattern) {
6
7
  const patternParts = pattern.split(/{{\s*([\w\[\]'"_.-]*)\s*}}/g);
@@ -13,8 +14,8 @@ function createPatternResolver(pattern) {
13
14
  }
14
15
  if (placeholderPart) {
15
16
  const getter = createGetter(placeholderPart);
16
- resolvers.push((context) => {
17
- const value = getter(context);
17
+ resolvers.push((input) => {
18
+ const value = getter(input);
18
19
  if (typeof value === "string" || Number.isFinite(value)) {
19
20
  return String(value);
20
21
  } else if (!value) {
@@ -27,31 +28,39 @@ function createPatternResolver(pattern) {
27
28
  });
28
29
  }
29
30
  }
30
- return (context) => resolvers.map((resolver) => resolver(context)).join("");
31
+ return (input) => resolvers.map((resolver) => resolver(input)).join("");
31
32
  }
32
33
  function createGetter(path) {
33
- const parts = path.split(/\.|\[(?:(\d+)|'([^']+)'|"([^"]+)")\]\.?/g).filter(Boolean);
34
- return (context) => {
35
- let current = context;
36
- for (const part of parts) {
37
- if (typeof current !== "object" || !current) {
38
- return void 0;
34
+ const parts = path.split(/\[(?:(\d+)|'([^']+)'|"([^"]+)")\]\.?/g).filter(Boolean);
35
+ return (input) => {
36
+ let current = input;
37
+ for (let i = 0; i < parts.length; i += 2) {
38
+ const regularPart = parts[i];
39
+ const exactPart = parts[i + 1];
40
+ if (regularPart) {
41
+ current = filterPredicates.getJsonValueAtPath(current, regularPart);
39
42
  }
40
- if (Array.isArray(current)) {
41
- if (!part.match(/^\d+$/)) {
42
- return void 0;
43
- }
44
- current = current[Number(part)];
45
- } else {
46
- if (!Object.hasOwn(current, part)) {
43
+ if (exactPart) {
44
+ if (typeof current !== "object" || !current) {
47
45
  return void 0;
46
+ } else if (Array.isArray(current)) {
47
+ if (exactPart.match(/^\d+$/)) {
48
+ current = current[Number(exactPart)];
49
+ } else {
50
+ return void 0;
51
+ }
52
+ } else {
53
+ if (!Object.hasOwn(current, exactPart)) {
54
+ return void 0;
55
+ }
56
+ current = current[exactPart];
48
57
  }
49
- current = current[part];
50
58
  }
51
59
  }
52
60
  return current;
53
61
  };
54
62
  }
55
63
 
64
+ exports.createGetter = createGetter;
56
65
  exports.createPatternResolver = createPatternResolver;
57
66
  //# sourceMappingURL=createPatternResolver.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createPatternResolver.cjs.js","sources":["../../src/util/createPatternResolver.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { InputError } from '@backstage/errors';\n\n/**\n * Takes a pattern string that may contain `{{ path.to.value }}` placeholders,\n * and returns a function that accepts a context object and returns strings that\n * have had its placeholders filled in by following the dot separated path of\n * properties accordingly on the context.\n */\nexport function createPatternResolver<TContext extends object = object>(\n pattern: string,\n): (context: TContext) => string {\n // This split results in an array where even elements are static strings\n // between placeholders, and odd elements are the contents inside\n // placeholders.\n //\n // For example, the pattern:\n // \"{{ foo }}-{{bar}}{{baz}}.\"\n // will result in:\n // ['', 'foo', '-', 'bar', '', 'baz', '.']\n const patternParts = pattern.split(/{{\\s*([\\w\\[\\]'\"_.-]*)\\s*}}/g);\n\n const resolvers = new Array<(context: TContext) => string>();\n\n for (let i = 0; i < patternParts.length; i += 2) {\n const staticPart = patternParts[i];\n const placeholderPart = patternParts[i + 1];\n\n if (staticPart) {\n resolvers.push(() => staticPart);\n }\n\n if (placeholderPart) {\n const getter = createGetter<TContext>(placeholderPart);\n resolvers.push(context => {\n const value = getter(context);\n if (typeof value === 'string' || Number.isFinite(value)) {\n return String(value);\n } else if (!value) {\n throw new InputError(`No value for selector '${placeholderPart}'`);\n } else {\n throw new InputError(\n `Expected string or number value for selector '${placeholderPart}', got ${typeof value}`,\n );\n }\n });\n }\n }\n\n return context => resolvers.map(resolver => resolver(context)).join('');\n}\n\nfunction createGetter<TContext extends object = object>(\n path: string,\n): (context: TContext) => unknown | undefined {\n // The resulti of the split contains quads:\n //\n // - any \"regular\" part\n // - pure digits that were within brackets, if applicable\n // - contents of a single quoted string that was within brackets, if applicable\n // - contents of a double quoted string that was within brackets, if applicable\n //\n // For example, the path:\n // foo.bar[0].baz[\"qux.e\"]a\n // will result in:\n // [\n // 'foo', undefined, undefined, undefined,\n // 'bar', '0', undefined, undefined,\n // 'baz', undefined, 'qux.e', undefined,\n // 'a'\n // ]\n // and then the empty elements are stripped away\n const parts = path\n .split(/\\.|\\[(?:(\\d+)|'([^']+)'|\"([^\"]+)\")\\]\\.?/g)\n .filter(Boolean);\n\n return (context: TContext): unknown | undefined => {\n let current = context;\n for (const part of parts) {\n if (typeof current !== 'object' || !current) {\n return undefined;\n }\n\n if (Array.isArray(current)) {\n if (!part.match(/^\\d+$/)) {\n return undefined;\n }\n current = (current as any[])[Number(part)];\n } else {\n if (!Object.hasOwn(current, part)) {\n return undefined;\n }\n current = (current as any)[part];\n }\n }\n\n return current;\n };\n}\n"],"names":["InputError"],"mappings":";;;;AAwBO,SAAS,sBACd,OAAA,EAC+B;AAS/B,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,6BAA6B,CAAA;AAEhE,EAAA,MAAM,SAAA,GAAY,IAAI,KAAA,EAAqC;AAE3D,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,MAAA,EAAQ,KAAK,CAAA,EAAG;AAC/C,IAAA,MAAM,UAAA,GAAa,aAAa,CAAC,CAAA;AACjC,IAAA,MAAM,eAAA,GAAkB,YAAA,CAAa,CAAA,GAAI,CAAC,CAAA;AAE1C,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,SAAA,CAAU,IAAA,CAAK,MAAM,UAAU,CAAA;AAAA,IACjC;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,MAAM,MAAA,GAAS,aAAuB,eAAe,CAAA;AACrD,MAAA,SAAA,CAAU,KAAK,CAAA,OAAA,KAAW;AACxB,QAAA,MAAM,KAAA,GAAQ,OAAO,OAAO,CAAA;AAC5B,QAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AACvD,UAAA,OAAO,OAAO,KAAK,CAAA;AAAA,QACrB,CAAA,MAAA,IAAW,CAAC,KAAA,EAAO;AACjB,UAAA,MAAM,IAAIA,iBAAA,CAAW,CAAA,uBAAA,EAA0B,eAAe,CAAA,CAAA,CAAG,CAAA;AAAA,QACnE,CAAA,MAAO;AACL,UAAA,MAAM,IAAIA,iBAAA;AAAA,YACR,CAAA,8CAAA,EAAiD,eAAe,CAAA,OAAA,EAAU,OAAO,KAAK,CAAA;AAAA,WACxF;AAAA,QACF;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,CAAA,OAAA,KAAW,UAAU,GAAA,CAAI,CAAA,QAAA,KAAY,SAAS,OAAO,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA;AACxE;AAEA,SAAS,aACP,IAAA,EAC4C;AAkB5C,EAAA,MAAM,QAAQ,IAAA,CACX,KAAA,CAAM,0CAA0C,CAAA,CAChD,OAAO,OAAO,CAAA;AAEjB,EAAA,OAAO,CAAC,OAAA,KAA2C;AACjD,IAAA,IAAI,OAAA,GAAU,OAAA;AACd,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,CAAC,OAAA,EAAS;AAC3C,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,QAAA,IAAI,CAAC,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,EAAG;AACxB,UAAA,OAAO,MAAA;AAAA,QACT;AACA,QAAA,OAAA,GAAW,OAAA,CAAkB,MAAA,CAAO,IAAI,CAAC,CAAA;AAAA,MAC3C,CAAA,MAAO;AACL,QAAA,IAAI,CAAC,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,IAAI,CAAA,EAAG;AACjC,UAAA,OAAO,MAAA;AAAA,QACT;AACA,QAAA,OAAA,GAAW,QAAgB,IAAI,CAAA;AAAA,MACjC;AAAA,IACF;AAEA,IAAA,OAAO,OAAA;AAAA,EACT,CAAA;AACF;;;;"}
1
+ {"version":3,"file":"createPatternResolver.cjs.js","sources":["../../src/util/createPatternResolver.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { InputError } from '@backstage/errors';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { getJsonValueAtPath } from '@backstage/filter-predicates';\n\n/**\n * Takes a pattern string that may contain `{{ path.to.value }}` placeholders,\n * and returns a function that accepts an input object and returns strings that\n * have had its placeholders filled in by following the dot separated path of\n * properties accordingly on the input.\n *\n * @internal\n */\nexport function createPatternResolver(\n pattern: string,\n): (input: JsonObject) => string {\n // This split results in an array where even elements are static strings\n // between placeholders, and odd elements are the contents inside\n // placeholders.\n //\n // For example, the pattern:\n // \"{{ foo }}-{{bar}}{{baz}}.\"\n // will result in:\n // ['', 'foo', '-', 'bar', '', 'baz', '.']\n const patternParts = pattern.split(/{{\\s*([\\w\\[\\]'\"_.-]*)\\s*}}/g);\n\n const resolvers = new Array<(input: JsonObject) => string>();\n\n for (let i = 0; i < patternParts.length; i += 2) {\n const staticPart = patternParts[i];\n const placeholderPart = patternParts[i + 1];\n\n if (staticPart) {\n resolvers.push(() => staticPart);\n }\n\n if (placeholderPart) {\n const getter = createGetter(placeholderPart);\n resolvers.push(input => {\n const value = getter(input);\n if (typeof value === 'string' || Number.isFinite(value)) {\n return String(value);\n } else if (!value) {\n throw new InputError(`No value for selector '${placeholderPart}'`);\n } else {\n throw new InputError(\n `Expected string or number value for selector '${placeholderPart}', got ${typeof value}`,\n );\n }\n });\n }\n }\n\n return input => resolvers.map(resolver => resolver(input)).join('');\n}\n\n/**\n * Takes a path string that indexes into an object, and returns a function that\n * fetches values out of such objects.\n *\n * @internal\n */\nexport function createGetter(\n path: string,\n): (input: JsonObject) => JsonValue | undefined {\n // The result of the split contains pairs (with maybe no last element):\n //\n // - any \"regular\" dot separated parts, if applicable\n // - any \"exact\" match parts within square brackets, if applicable\n //\n // For example, the path:\n // foo.bar[0].baz[\"qux.e\"]a\n //\n // will result in:\n // [\n // 'foo.bar', '0',\n // 'baz', 'qux.e',\n // 'a',\n // ]\n const parts = path\n .split(/\\[(?:(\\d+)|'([^']+)'|\"([^\"]+)\")\\]\\.?/g)\n .filter(Boolean);\n\n return input => {\n let current: JsonValue | undefined = input;\n for (let i = 0; i < parts.length; i += 2) {\n const regularPart = parts[i];\n const exactPart = parts[i + 1];\n\n if (regularPart) {\n current = getJsonValueAtPath(current, regularPart);\n }\n\n if (exactPart) {\n if (typeof current !== 'object' || !current) {\n return undefined;\n } else if (Array.isArray(current)) {\n if (exactPart.match(/^\\d+$/)) {\n current = current[Number(exactPart)];\n } else {\n return undefined;\n }\n } else {\n if (!Object.hasOwn(current, exactPart)) {\n return undefined;\n }\n current = current[exactPart];\n }\n }\n }\n\n return current;\n };\n}\n"],"names":["InputError","getJsonValueAtPath"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-events-backend-module-google-pubsub",
3
- "version": "0.1.8-next.0",
3
+ "version": "0.2.0",
4
4
  "description": "The google-pubsub backend module for the events plugin.",
5
5
  "backstage": {
6
6
  "role": "backend-plugin-module",
@@ -37,19 +37,20 @@
37
37
  "test": "backstage-cli package test"
38
38
  },
39
39
  "dependencies": {
40
- "@backstage/backend-plugin-api": "1.7.0-next.0",
41
- "@backstage/config": "1.3.6",
42
- "@backstage/errors": "1.2.7",
43
- "@backstage/plugin-events-node": "0.4.19-next.0",
44
- "@backstage/types": "1.2.2",
40
+ "@backstage/backend-plugin-api": "^1.7.0",
41
+ "@backstage/config": "^1.3.6",
42
+ "@backstage/errors": "^1.2.7",
43
+ "@backstage/filter-predicates": "^0.1.0",
44
+ "@backstage/plugin-events-node": "^0.4.19",
45
+ "@backstage/types": "^1.2.2",
45
46
  "@google-cloud/pubsub": "^4.10.0",
46
47
  "@opentelemetry/api": "^1.9.0"
47
48
  },
48
49
  "devDependencies": {
49
- "@backstage/backend-defaults": "0.15.1-next.0",
50
- "@backstage/backend-test-utils": "1.10.4-next.0",
51
- "@backstage/cli": "0.35.3-next.0",
52
- "@backstage/plugin-events-backend": "0.5.11-next.0",
50
+ "@backstage/backend-defaults": "^0.15.2",
51
+ "@backstage/backend-test-utils": "^1.11.0",
52
+ "@backstage/cli": "^0.35.4",
53
+ "@backstage/plugin-events-backend": "^0.5.11",
53
54
  "wait-for-expect": "^3.0.2"
54
55
  },
55
56
  "configSchema": "config.d.ts",