@backstage/plugin-events-backend 0.5.4 → 0.5.5
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 +21 -0
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/schema/openapi/generated/router.cjs.js.map +1 -1
- package/dist/service/EventsPlugin.cjs.js.map +1 -1
- package/dist/service/http/HttpPostIngressEventPublisher.cjs.js.map +1 -1
- package/dist/service/http/body-parser/HttpApplicationJsonBodyParser.cjs.js.map +1 -1
- package/dist/service/http/body-parser/index.cjs.js.map +1 -1
- package/dist/service/http/errors.cjs.js.map +1 -1
- package/dist/service/http/validation/RequestValidationContextImpl.cjs.js.map +1 -1
- package/dist/service/hub/DatabaseEventBusStore.cjs.js.map +1 -1
- package/dist/service/hub/MemoryEventBusStore.cjs.js.map +1 -1
- package/dist/service/hub/createEventBusRouter.cjs.js.map +1 -1
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @backstage/plugin-events-backend
|
|
2
2
|
|
|
3
|
+
## 0.5.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @backstage/backend-openapi-utils@0.6.0
|
|
9
|
+
- @backstage/backend-plugin-api@1.4.2
|
|
10
|
+
- @backstage/plugin-events-node@0.4.14
|
|
11
|
+
|
|
12
|
+
## 0.5.5-next.0
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- @backstage/backend-openapi-utils@0.6.0-next.0
|
|
18
|
+
- @backstage/backend-plugin-api@1.4.2-next.0
|
|
19
|
+
- @backstage/plugin-events-node@0.4.14-next.0
|
|
20
|
+
- @backstage/config@1.3.3
|
|
21
|
+
- @backstage/errors@1.2.7
|
|
22
|
+
- @backstage/types@1.2.1
|
|
23
|
+
|
|
3
24
|
## 0.5.4
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/alpha.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { eventsPlugin } from './service/EventsPlugin';\n\n/** @alpha */\nconst _feature = eventsPlugin;\nexport default _feature;\n"],"names":["eventsPlugin"],"mappings":";;;;;;AAmBA,MAAM,
|
|
1
|
+
{"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { eventsPlugin } from './service/EventsPlugin';\n\n/** @alpha */\nconst _feature = eventsPlugin;\nexport default _feature;\n"],"names":["eventsPlugin"],"mappings":";;;;;;AAmBA,MAAM,QAAA,GAAWA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.cjs.js","sources":["../../../../src/schema/openapi/generated/router.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\n// ******************************************************************\n// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *\n// ******************************************************************\nimport { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils';\nimport { EndpointMap } from './apis';\n\nexport const spec = {\n openapi: '3.0.3',\n info: {\n title: 'events',\n version: '1',\n description: 'The Backstage backend plugin that powers the events system.',\n license: {\n name: 'Apache-2.0',\n url: 'http://www.apache.org/licenses/LICENSE-2.0.html',\n },\n contact: {},\n },\n servers: [\n {\n url: '/',\n },\n ],\n components: {\n examples: {},\n headers: {},\n parameters: {\n subscriptionId: {\n name: 'subscriptionId',\n in: 'path',\n required: true,\n allowReserved: true,\n schema: {\n type: 'string',\n },\n },\n },\n requestBodies: {},\n responses: {\n ErrorResponse: {\n description: 'An error response from the backend.',\n content: {\n 'application/json; charset=utf-8': {\n schema: {\n $ref: '#/components/schemas/Error',\n },\n },\n },\n },\n },\n schemas: {\n Event: {\n type: 'object',\n required: ['topic', 'payload'],\n properties: {\n topic: {\n type: 'string',\n description: 'The topic that the event is published on',\n },\n payload: {\n description: 'The event payload',\n },\n },\n },\n Error: {\n type: 'object',\n properties: {\n error: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n message: {\n type: 'string',\n },\n },\n required: ['name', 'message'],\n },\n request: {\n type: 'object',\n properties: {\n method: {\n type: 'string',\n },\n url: {\n type: 'string',\n },\n },\n required: ['method', 'url'],\n },\n response: {\n type: 'object',\n properties: {\n statusCode: {\n type: 'number',\n },\n },\n required: ['statusCode'],\n },\n },\n required: ['error', 'request', 'response'],\n },\n },\n securitySchemes: {\n JWT: {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n },\n },\n },\n paths: {\n '/bus/v1/events': {\n post: {\n operationId: 'PostEvent',\n description: 'Publish a new event',\n responses: {\n '201': {\n description: 'The event was published successfully',\n },\n '204': {\n description:\n 'The event did not need to be published as all subscribers have already been notified',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['event'],\n properties: {\n event: {\n $ref: '#/components/schemas/Event',\n },\n notifiedSubscribers: {\n type: 'array',\n description:\n 'The IDs of subscriptions that have already received this event',\n items: {\n type: 'string',\n },\n },\n },\n },\n examples: {\n 'Publishing a simple Event': {\n value: {\n event: {\n topic: 'test-topic',\n payload: {\n myData: 'foo',\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n '/bus/v1/subscriptions/{subscriptionId}': {\n put: {\n operationId: 'PutSubscription',\n description:\n 'Ensures that the subscription exists with the provided configuration',\n responses: {\n '201': {\n description: 'The subscription exists or was created successfully',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n parameters: [\n {\n $ref: '#/components/parameters/subscriptionId',\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['topics'],\n properties: {\n topics: {\n type: 'array',\n description: 'The topics to subscribe to',\n items: {\n type: 'string',\n },\n },\n },\n },\n examples: {\n 'Subscribing to a single topic': {\n value: {\n topics: ['test-topic'],\n },\n },\n },\n },\n },\n },\n },\n },\n '/bus/v1/subscriptions/{subscriptionId}/events': {\n get: {\n operationId: 'GetSubscriptionEvents',\n description: 'Get new events for the provided subscription',\n responses: {\n '200': {\n description: 'New events',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['events'],\n properties: {\n events: {\n type: 'array',\n items: {\n $ref: '#/components/schemas/Event',\n },\n },\n },\n },\n },\n },\n },\n '202': {\n description:\n 'No new events are available. Response will block until the client should try again.',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n parameters: [\n {\n $ref: '#/components/parameters/subscriptionId',\n },\n ],\n },\n },\n },\n} as const;\nexport const createOpenApiRouter = async (\n options?: Parameters<\n typeof createValidatedOpenApiRouterFromGeneratedEndpointMap\n >['1'],\n) =>\n createValidatedOpenApiRouterFromGeneratedEndpointMap<EndpointMap>(\n spec,\n options,\n );\n"],"names":["createValidatedOpenApiRouterFromGeneratedEndpointMap"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../../../../src/schema/openapi/generated/router.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\n// ******************************************************************\n// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *\n// ******************************************************************\nimport { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils';\nimport { EndpointMap } from './apis';\n\nexport const spec = {\n openapi: '3.0.3',\n info: {\n title: 'events',\n version: '1',\n description: 'The Backstage backend plugin that powers the events system.',\n license: {\n name: 'Apache-2.0',\n url: 'http://www.apache.org/licenses/LICENSE-2.0.html',\n },\n contact: {},\n },\n servers: [\n {\n url: '/',\n },\n ],\n components: {\n examples: {},\n headers: {},\n parameters: {\n subscriptionId: {\n name: 'subscriptionId',\n in: 'path',\n required: true,\n allowReserved: true,\n schema: {\n type: 'string',\n },\n },\n },\n requestBodies: {},\n responses: {\n ErrorResponse: {\n description: 'An error response from the backend.',\n content: {\n 'application/json; charset=utf-8': {\n schema: {\n $ref: '#/components/schemas/Error',\n },\n },\n },\n },\n },\n schemas: {\n Event: {\n type: 'object',\n required: ['topic', 'payload'],\n properties: {\n topic: {\n type: 'string',\n description: 'The topic that the event is published on',\n },\n payload: {\n description: 'The event payload',\n },\n },\n },\n Error: {\n type: 'object',\n properties: {\n error: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n },\n message: {\n type: 'string',\n },\n },\n required: ['name', 'message'],\n },\n request: {\n type: 'object',\n properties: {\n method: {\n type: 'string',\n },\n url: {\n type: 'string',\n },\n },\n required: ['method', 'url'],\n },\n response: {\n type: 'object',\n properties: {\n statusCode: {\n type: 'number',\n },\n },\n required: ['statusCode'],\n },\n },\n required: ['error', 'request', 'response'],\n },\n },\n securitySchemes: {\n JWT: {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n },\n },\n },\n paths: {\n '/bus/v1/events': {\n post: {\n operationId: 'PostEvent',\n description: 'Publish a new event',\n responses: {\n '201': {\n description: 'The event was published successfully',\n },\n '204': {\n description:\n 'The event did not need to be published as all subscribers have already been notified',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['event'],\n properties: {\n event: {\n $ref: '#/components/schemas/Event',\n },\n notifiedSubscribers: {\n type: 'array',\n description:\n 'The IDs of subscriptions that have already received this event',\n items: {\n type: 'string',\n },\n },\n },\n },\n examples: {\n 'Publishing a simple Event': {\n value: {\n event: {\n topic: 'test-topic',\n payload: {\n myData: 'foo',\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n '/bus/v1/subscriptions/{subscriptionId}': {\n put: {\n operationId: 'PutSubscription',\n description:\n 'Ensures that the subscription exists with the provided configuration',\n responses: {\n '201': {\n description: 'The subscription exists or was created successfully',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n parameters: [\n {\n $ref: '#/components/parameters/subscriptionId',\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['topics'],\n properties: {\n topics: {\n type: 'array',\n description: 'The topics to subscribe to',\n items: {\n type: 'string',\n },\n },\n },\n },\n examples: {\n 'Subscribing to a single topic': {\n value: {\n topics: ['test-topic'],\n },\n },\n },\n },\n },\n },\n },\n },\n '/bus/v1/subscriptions/{subscriptionId}/events': {\n get: {\n operationId: 'GetSubscriptionEvents',\n description: 'Get new events for the provided subscription',\n responses: {\n '200': {\n description: 'New events',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['events'],\n properties: {\n events: {\n type: 'array',\n items: {\n $ref: '#/components/schemas/Event',\n },\n },\n },\n },\n },\n },\n },\n '202': {\n description:\n 'No new events are available. Response will block until the client should try again.',\n },\n default: {\n $ref: '#/components/responses/ErrorResponse',\n },\n },\n security: [\n {},\n {\n JWT: [],\n },\n ],\n parameters: [\n {\n $ref: '#/components/parameters/subscriptionId',\n },\n ],\n },\n },\n },\n} as const;\nexport const createOpenApiRouter = async (\n options?: Parameters<\n typeof createValidatedOpenApiRouterFromGeneratedEndpointMap\n >['1'],\n) =>\n createValidatedOpenApiRouterFromGeneratedEndpointMap<EndpointMap>(\n spec,\n options,\n );\n"],"names":["createValidatedOpenApiRouterFromGeneratedEndpointMap"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventsPlugin.cjs.js","sources":["../../src/service/EventsPlugin.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 {\n createBackendPlugin,\n coreServices,\n} from '@backstage/backend-plugin-api';\nimport {\n eventsExtensionPoint,\n EventsExtensionPoint,\n} from '@backstage/plugin-events-node/alpha';\nimport {\n eventsServiceRef,\n HttpBodyParserOptions,\n HttpPostIngressOptions,\n} from '@backstage/plugin-events-node';\nimport Router from 'express-promise-router';\nimport { HttpPostIngressEventPublisher } from './http';\nimport { createEventBusRouter } from './hub';\n\nclass EventsExtensionPointImpl implements EventsExtensionPoint {\n readonly #httpPostIngresses: HttpPostIngressOptions[] = [];\n readonly #httpBodyParsers: HttpBodyParserOptions[] = [];\n\n setEventBroker(_: any): void {\n throw new Error(\n 'setEventBroker is not supported anymore; use eventsServiceRef instead',\n );\n }\n\n addPublishers(_: any): void {\n throw new Error(\n 'addPublishers is not supported anymore; use EventsService instead',\n );\n }\n\n addSubscribers(_: any): void {\n throw new Error(\n 'addSubscribers is not supported anymore; use EventsService instead',\n );\n }\n\n addHttpPostIngress(options: HttpPostIngressOptions) {\n this.#httpPostIngresses.push(options);\n }\n\n addHttpPostBodyParser(options: HttpBodyParserOptions): void {\n this.#httpBodyParsers.push(options);\n }\n\n get httpPostIngresses() {\n return this.#httpPostIngresses;\n }\n\n get httpBodyParsers() {\n return this.#httpBodyParsers;\n }\n}\n\n/**\n * Events plugin\n *\n * @public\n */\nexport const eventsPlugin = createBackendPlugin({\n pluginId: 'events',\n register(env) {\n const extensionPoint = new EventsExtensionPointImpl();\n env.registerExtensionPoint(eventsExtensionPoint, extensionPoint);\n\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n events: eventsServiceRef,\n database: coreServices.database,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n lifecycle: coreServices.lifecycle,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({\n config,\n events,\n database,\n httpAuth,\n httpRouter,\n lifecycle,\n logger,\n scheduler,\n }) {\n const ingresses = Object.fromEntries(\n extensionPoint.httpPostIngresses.map(ingress => [\n ingress.topic,\n ingress as Omit<HttpPostIngressOptions, 'topic'>,\n ]),\n );\n\n const bodyParsers = Object.fromEntries(\n extensionPoint.httpBodyParsers.map(option => [\n option.contentType,\n option.parser,\n ]),\n );\n\n const http = HttpPostIngressEventPublisher.fromConfig({\n config,\n events,\n ingresses,\n bodyParsers,\n logger,\n });\n const eventsRouter = Router();\n http.bind(eventsRouter);\n\n // MUST be registered *before* the event bus router.\n // Otherwise, it would already make use of `express.json()`\n // that is used there as part of the middleware stack.\n httpRouter.use(eventsRouter);\n\n const notifyTimeoutMs = config.getOptionalNumber(\n 'events.notifyTimeoutMs',\n );\n\n httpRouter.use(\n await createEventBusRouter({\n database,\n lifecycle,\n logger,\n httpAuth,\n scheduler,\n notifyTimeoutMs,\n }),\n );\n\n httpRouter.addAuthPolicy({\n allow: 'unauthenticated',\n path: '/http',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","eventsExtensionPoint","coreServices","eventsServiceRef","HttpPostIngressEventPublisher","Router","createEventBusRouter"],"mappings":";;;;;;;;;;;;;AAiCA,MAAM,
|
|
1
|
+
{"version":3,"file":"EventsPlugin.cjs.js","sources":["../../src/service/EventsPlugin.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 {\n createBackendPlugin,\n coreServices,\n} from '@backstage/backend-plugin-api';\nimport {\n eventsExtensionPoint,\n EventsExtensionPoint,\n} from '@backstage/plugin-events-node/alpha';\nimport {\n eventsServiceRef,\n HttpBodyParserOptions,\n HttpPostIngressOptions,\n} from '@backstage/plugin-events-node';\nimport Router from 'express-promise-router';\nimport { HttpPostIngressEventPublisher } from './http';\nimport { createEventBusRouter } from './hub';\n\nclass EventsExtensionPointImpl implements EventsExtensionPoint {\n readonly #httpPostIngresses: HttpPostIngressOptions[] = [];\n readonly #httpBodyParsers: HttpBodyParserOptions[] = [];\n\n setEventBroker(_: any): void {\n throw new Error(\n 'setEventBroker is not supported anymore; use eventsServiceRef instead',\n );\n }\n\n addPublishers(_: any): void {\n throw new Error(\n 'addPublishers is not supported anymore; use EventsService instead',\n );\n }\n\n addSubscribers(_: any): void {\n throw new Error(\n 'addSubscribers is not supported anymore; use EventsService instead',\n );\n }\n\n addHttpPostIngress(options: HttpPostIngressOptions) {\n this.#httpPostIngresses.push(options);\n }\n\n addHttpPostBodyParser(options: HttpBodyParserOptions): void {\n this.#httpBodyParsers.push(options);\n }\n\n get httpPostIngresses() {\n return this.#httpPostIngresses;\n }\n\n get httpBodyParsers() {\n return this.#httpBodyParsers;\n }\n}\n\n/**\n * Events plugin\n *\n * @public\n */\nexport const eventsPlugin = createBackendPlugin({\n pluginId: 'events',\n register(env) {\n const extensionPoint = new EventsExtensionPointImpl();\n env.registerExtensionPoint(eventsExtensionPoint, extensionPoint);\n\n env.registerInit({\n deps: {\n config: coreServices.rootConfig,\n events: eventsServiceRef,\n database: coreServices.database,\n httpAuth: coreServices.httpAuth,\n httpRouter: coreServices.httpRouter,\n lifecycle: coreServices.lifecycle,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({\n config,\n events,\n database,\n httpAuth,\n httpRouter,\n lifecycle,\n logger,\n scheduler,\n }) {\n const ingresses = Object.fromEntries(\n extensionPoint.httpPostIngresses.map(ingress => [\n ingress.topic,\n ingress as Omit<HttpPostIngressOptions, 'topic'>,\n ]),\n );\n\n const bodyParsers = Object.fromEntries(\n extensionPoint.httpBodyParsers.map(option => [\n option.contentType,\n option.parser,\n ]),\n );\n\n const http = HttpPostIngressEventPublisher.fromConfig({\n config,\n events,\n ingresses,\n bodyParsers,\n logger,\n });\n const eventsRouter = Router();\n http.bind(eventsRouter);\n\n // MUST be registered *before* the event bus router.\n // Otherwise, it would already make use of `express.json()`\n // that is used there as part of the middleware stack.\n httpRouter.use(eventsRouter);\n\n const notifyTimeoutMs = config.getOptionalNumber(\n 'events.notifyTimeoutMs',\n );\n\n httpRouter.use(\n await createEventBusRouter({\n database,\n lifecycle,\n logger,\n httpAuth,\n scheduler,\n notifyTimeoutMs,\n }),\n );\n\n httpRouter.addAuthPolicy({\n allow: 'unauthenticated',\n path: '/http',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","eventsExtensionPoint","coreServices","eventsServiceRef","HttpPostIngressEventPublisher","Router","createEventBusRouter"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpPostIngressEventPublisher.cjs.js","sources":["../../../src/service/http/HttpPostIngressEventPublisher.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport {\n EventsService,\n HttpBodyParser,\n HttpPostIngressOptions,\n RequestValidator,\n} from '@backstage/plugin-events-node';\nimport contentType from 'content-type';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { defaultHttpBodyParsers } from './body-parser';\nimport { RequestValidationContextImpl } from './validation';\nimport { UnsupportedMediaTypeError } from './errors';\n/**\n * Publishes events received from their origin (e.g., webhook events from an SCM system)\n * via HTTP POST endpoint and passes the request body as event payload to the registered subscribers.\n *\n * @public\n */\n// TODO(pjungermann): add prom metrics? (see plugins/catalog-backend/src/util/metrics.ts, etc.)\nexport class HttpPostIngressEventPublisher {\n static fromConfig(env: {\n config: Config;\n events: EventsService;\n ingresses?: { [topic: string]: Omit<HttpPostIngressOptions, 'topic'> };\n bodyParsers?: { [contentType: string]: HttpBodyParser };\n logger: LoggerService;\n }): HttpPostIngressEventPublisher {\n const topics =\n env.config.getOptionalStringArray('events.http.topics') ?? [];\n\n const ingresses = env.ingresses ?? {};\n topics.forEach(topic => {\n // don't overwrite topic settings\n // (e.g., added at the config as well as argument)\n if (!ingresses[topic]) {\n ingresses[topic] = {};\n }\n });\n\n const parsers = { ...defaultHttpBodyParsers, ...env.bodyParsers };\n\n return new HttpPostIngressEventPublisher(\n env.events,\n env.logger,\n ingresses,\n parsers,\n );\n }\n\n private constructor(\n private readonly events: EventsService,\n private readonly logger: LoggerService,\n private readonly ingresses: {\n [topic: string]: Omit<HttpPostIngressOptions, 'topic'>;\n },\n private readonly bodyParsers: {\n [contentType: string]: HttpBodyParser;\n },\n ) {}\n\n bind(router: express.Router): void {\n router.use('/http', this.createRouter(this.ingresses));\n }\n\n private createRouter(ingresses: {\n [topic: string]: Omit<HttpPostIngressOptions, 'topic'>;\n }): express.Router {\n const router = Router();\n router.use(express.raw({ type: '*/*', limit: '5mb' }));\n\n Object.keys(ingresses).forEach(topic =>\n this.addRouteForTopic(router, topic, ingresses[topic].validator),\n );\n\n return router;\n }\n\n private addRouteForTopic(\n router: express.Router,\n topic: string,\n validator?: RequestValidator,\n ): void {\n const path = `/${topic}`;\n const logger = this.logger;\n\n router.post(path, async (request, response) => {\n const requestContentType = contentType.parse(request);\n const bodyParser = this.bodyParsers[requestContentType.type ?? ''];\n\n if (!bodyParser) {\n throw new UnsupportedMediaTypeError(requestContentType.type);\n }\n\n const { bodyParsed, bodyBuffer, encoding } = await bodyParser(\n request,\n requestContentType,\n topic,\n );\n\n if (validator) {\n const requestDetails = {\n body: bodyParsed,\n headers: request.headers,\n raw: {\n body: bodyBuffer,\n encoding: encoding as BufferEncoding,\n },\n };\n\n const context = new RequestValidationContextImpl();\n await validator(requestDetails, context);\n\n if (context.wasRejected()) {\n response\n .status(context.rejectionDetails!.status)\n .json(context.rejectionDetails!.payload);\n return;\n }\n }\n\n await this.events.publish({\n topic,\n eventPayload: bodyParsed,\n metadata: request.headers,\n });\n\n response.status(202).json({ status: 'accepted' });\n });\n\n // TODO(pjungermann): We don't really know the externally defined path prefix here,\n // however it is more useful for users to have it. Is there a better way?\n logger.info(`Registered /api/events/http${path} to receive events`);\n }\n}\n"],"names":["defaultHttpBodyParsers","Router","express","contentType","UnsupportedMediaTypeError","RequestValidationContextImpl"],"mappings":";;;;;;;;;;;;;;;AAqCO,MAAM,
|
|
1
|
+
{"version":3,"file":"HttpPostIngressEventPublisher.cjs.js","sources":["../../../src/service/http/HttpPostIngressEventPublisher.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport {\n EventsService,\n HttpBodyParser,\n HttpPostIngressOptions,\n RequestValidator,\n} from '@backstage/plugin-events-node';\nimport contentType from 'content-type';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { defaultHttpBodyParsers } from './body-parser';\nimport { RequestValidationContextImpl } from './validation';\nimport { UnsupportedMediaTypeError } from './errors';\n/**\n * Publishes events received from their origin (e.g., webhook events from an SCM system)\n * via HTTP POST endpoint and passes the request body as event payload to the registered subscribers.\n *\n * @public\n */\n// TODO(pjungermann): add prom metrics? (see plugins/catalog-backend/src/util/metrics.ts, etc.)\nexport class HttpPostIngressEventPublisher {\n static fromConfig(env: {\n config: Config;\n events: EventsService;\n ingresses?: { [topic: string]: Omit<HttpPostIngressOptions, 'topic'> };\n bodyParsers?: { [contentType: string]: HttpBodyParser };\n logger: LoggerService;\n }): HttpPostIngressEventPublisher {\n const topics =\n env.config.getOptionalStringArray('events.http.topics') ?? [];\n\n const ingresses = env.ingresses ?? {};\n topics.forEach(topic => {\n // don't overwrite topic settings\n // (e.g., added at the config as well as argument)\n if (!ingresses[topic]) {\n ingresses[topic] = {};\n }\n });\n\n const parsers = { ...defaultHttpBodyParsers, ...env.bodyParsers };\n\n return new HttpPostIngressEventPublisher(\n env.events,\n env.logger,\n ingresses,\n parsers,\n );\n }\n\n private constructor(\n private readonly events: EventsService,\n private readonly logger: LoggerService,\n private readonly ingresses: {\n [topic: string]: Omit<HttpPostIngressOptions, 'topic'>;\n },\n private readonly bodyParsers: {\n [contentType: string]: HttpBodyParser;\n },\n ) {}\n\n bind(router: express.Router): void {\n router.use('/http', this.createRouter(this.ingresses));\n }\n\n private createRouter(ingresses: {\n [topic: string]: Omit<HttpPostIngressOptions, 'topic'>;\n }): express.Router {\n const router = Router();\n router.use(express.raw({ type: '*/*', limit: '5mb' }));\n\n Object.keys(ingresses).forEach(topic =>\n this.addRouteForTopic(router, topic, ingresses[topic].validator),\n );\n\n return router;\n }\n\n private addRouteForTopic(\n router: express.Router,\n topic: string,\n validator?: RequestValidator,\n ): void {\n const path = `/${topic}`;\n const logger = this.logger;\n\n router.post(path, async (request, response) => {\n const requestContentType = contentType.parse(request);\n const bodyParser = this.bodyParsers[requestContentType.type ?? ''];\n\n if (!bodyParser) {\n throw new UnsupportedMediaTypeError(requestContentType.type);\n }\n\n const { bodyParsed, bodyBuffer, encoding } = await bodyParser(\n request,\n requestContentType,\n topic,\n );\n\n if (validator) {\n const requestDetails = {\n body: bodyParsed,\n headers: request.headers,\n raw: {\n body: bodyBuffer,\n encoding: encoding as BufferEncoding,\n },\n };\n\n const context = new RequestValidationContextImpl();\n await validator(requestDetails, context);\n\n if (context.wasRejected()) {\n response\n .status(context.rejectionDetails!.status)\n .json(context.rejectionDetails!.payload);\n return;\n }\n }\n\n await this.events.publish({\n topic,\n eventPayload: bodyParsed,\n metadata: request.headers,\n });\n\n response.status(202).json({ status: 'accepted' });\n });\n\n // TODO(pjungermann): We don't really know the externally defined path prefix here,\n // however it is more useful for users to have it. Is there a better way?\n logger.info(`Registered /api/events/http${path} to receive events`);\n }\n}\n"],"names":["defaultHttpBodyParsers","Router","express","contentType","UnsupportedMediaTypeError","RequestValidationContextImpl"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpApplicationJsonBodyParser.cjs.js","sources":["../../../../src/service/http/body-parser/HttpApplicationJsonBodyParser.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 */\nimport { HttpBodyParser } from '@backstage/plugin-events-node';\nimport { UnsupportedCharsetError } from '../errors';\n\nexport const HttpApplicationJsonBodyParser: HttpBodyParser = async (\n request,\n parsedMediaType,\n topic,\n) => {\n const requestBody = request.body;\n if (!Buffer.isBuffer(requestBody)) {\n throw new Error(\n `Failed to retrieve raw body from incoming event for topic ${topic}; not a buffer: ${typeof requestBody}`,\n );\n }\n\n const bodyBuffer: Buffer = requestBody;\n\n const encoding = parsedMediaType.parameters.charset ?? 'utf-8';\n if (!Buffer.isEncoding(encoding)) {\n throw new UnsupportedCharsetError(encoding);\n }\n\n const bodyString = bodyBuffer.toString(encoding);\n const bodyParsed =\n parsedMediaType.type === 'application/json'\n ? JSON.parse(bodyString)\n : bodyString;\n return { bodyParsed, bodyBuffer, encoding };\n};\n"],"names":["UnsupportedCharsetError"],"mappings":";;;;AAkBO,MAAM,
|
|
1
|
+
{"version":3,"file":"HttpApplicationJsonBodyParser.cjs.js","sources":["../../../../src/service/http/body-parser/HttpApplicationJsonBodyParser.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 */\nimport { HttpBodyParser } from '@backstage/plugin-events-node';\nimport { UnsupportedCharsetError } from '../errors';\n\nexport const HttpApplicationJsonBodyParser: HttpBodyParser = async (\n request,\n parsedMediaType,\n topic,\n) => {\n const requestBody = request.body;\n if (!Buffer.isBuffer(requestBody)) {\n throw new Error(\n `Failed to retrieve raw body from incoming event for topic ${topic}; not a buffer: ${typeof requestBody}`,\n );\n }\n\n const bodyBuffer: Buffer = requestBody;\n\n const encoding = parsedMediaType.parameters.charset ?? 'utf-8';\n if (!Buffer.isEncoding(encoding)) {\n throw new UnsupportedCharsetError(encoding);\n }\n\n const bodyString = bodyBuffer.toString(encoding);\n const bodyParsed =\n parsedMediaType.type === 'application/json'\n ? JSON.parse(bodyString)\n : bodyString;\n return { bodyParsed, bodyBuffer, encoding };\n};\n"],"names":["UnsupportedCharsetError"],"mappings":";;;;AAkBO,MAAM,6BAAA,GAAgD,OAC3D,OAAA,EACA,eAAA,EACA,KAAA,KACG;AACH,EAAA,MAAM,cAAc,OAAA,CAAQ,IAAA;AAC5B,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,WAAW,CAAA,EAAG;AACjC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,0DAAA,EAA6D,KAAK,CAAA,gBAAA,EAAmB,OAAO,WAAW,CAAA;AAAA,KACzG;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAqB,WAAA;AAE3B,EAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,UAAA,CAAW,OAAA,IAAW,OAAA;AACvD,EAAA,IAAI,CAAC,MAAA,CAAO,UAAA,CAAW,QAAQ,CAAA,EAAG;AAChC,IAAA,MAAM,IAAIA,+BAAwB,QAAQ,CAAA;AAAA,EAC5C;AAEA,EAAA,MAAM,UAAA,GAAa,UAAA,CAAW,QAAA,CAAS,QAAQ,CAAA;AAC/C,EAAA,MAAM,aACJ,eAAA,CAAgB,IAAA,KAAS,qBACrB,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,GACrB,UAAA;AACN,EAAA,OAAO,EAAE,UAAA,EAAY,UAAA,EAAY,QAAA,EAAS;AAC5C;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../../../../src/service/http/body-parser/index.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 */\nimport { HttpBodyParser } from '@backstage/plugin-events-node';\nimport { HttpApplicationJsonBodyParser } from './HttpApplicationJsonBodyParser';\n\nexport const defaultHttpBodyParsers: { [contentType: string]: HttpBodyParser } =\n {\n 'application/json': HttpApplicationJsonBodyParser,\n };\n"],"names":["HttpApplicationJsonBodyParser"],"mappings":";;;;AAkBO,MAAM,
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../../../src/service/http/body-parser/index.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 */\nimport { HttpBodyParser } from '@backstage/plugin-events-node';\nimport { HttpApplicationJsonBodyParser } from './HttpApplicationJsonBodyParser';\n\nexport const defaultHttpBodyParsers: { [contentType: string]: HttpBodyParser } =\n {\n 'application/json': HttpApplicationJsonBodyParser,\n };\n"],"names":["HttpApplicationJsonBodyParser"],"mappings":";;;;AAkBO,MAAM,sBAAA,GACX;AAAA,EACE,kBAAA,EAAoBA;AACtB;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs.js","sources":["../../../src/service/http/errors.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 */\nimport { CustomErrorBase } from '@backstage/errors';\n\nexport class UnsupportedCharsetError extends CustomErrorBase {\n name = 'UnsupportedCharsetError' as const;\n statusCode = 415 as const;\n\n constructor(charset: string) {\n super(`Unsupported charset: ${charset}`);\n }\n}\n\nexport class UnsupportedMediaTypeError extends CustomErrorBase {\n name = 'UnsupportedMediaTypeError' as const;\n statusCode = 415 as const;\n\n constructor(mediaType?: string) {\n super(\n `Unsupported media type: ${\n mediaType ?? 'unknown'\n }. You need to provide a custom body parser for this media type using the EventsExtensionPoint.`,\n );\n }\n}\n"],"names":["CustomErrorBase"],"mappings":";;;;AAiBO,MAAM,gCAAgCA,
|
|
1
|
+
{"version":3,"file":"errors.cjs.js","sources":["../../../src/service/http/errors.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 */\nimport { CustomErrorBase } from '@backstage/errors';\n\nexport class UnsupportedCharsetError extends CustomErrorBase {\n name = 'UnsupportedCharsetError' as const;\n statusCode = 415 as const;\n\n constructor(charset: string) {\n super(`Unsupported charset: ${charset}`);\n }\n}\n\nexport class UnsupportedMediaTypeError extends CustomErrorBase {\n name = 'UnsupportedMediaTypeError' as const;\n statusCode = 415 as const;\n\n constructor(mediaType?: string) {\n super(\n `Unsupported media type: ${\n mediaType ?? 'unknown'\n }. You need to provide a custom body parser for this media type using the EventsExtensionPoint.`,\n );\n }\n}\n"],"names":["CustomErrorBase"],"mappings":";;;;AAiBO,MAAM,gCAAgCA,sBAAA,CAAgB;AAAA,EAC3D,IAAA,GAAO,yBAAA;AAAA,EACP,UAAA,GAAa,GAAA;AAAA,EAEb,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,CAAA,qBAAA,EAAwB,OAAO,CAAA,CAAE,CAAA;AAAA,EACzC;AACF;AAEO,MAAM,kCAAkCA,sBAAA,CAAgB;AAAA,EAC7D,IAAA,GAAO,2BAAA;AAAA,EACP,UAAA,GAAa,GAAA;AAAA,EAEb,YAAY,SAAA,EAAoB;AAC9B,IAAA,KAAA;AAAA,MACE,CAAA,wBAAA,EACE,aAAa,SACf,CAAA,8FAAA;AAAA,KACF;AAAA,EACF;AACF;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestValidationContextImpl.cjs.js","sources":["../../../../src/service/http/validation/RequestValidationContextImpl.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 {\n RequestRejectionDetails,\n RequestValidationContext,\n} from '@backstage/plugin-events-node';\n\nexport class RequestValidationContextImpl implements RequestValidationContext {\n #rejectionDetails: RequestRejectionDetails | undefined;\n\n reject(details?: Partial<RequestRejectionDetails>): void {\n this.#rejectionDetails = {\n status: details?.status ?? 403,\n payload: details?.payload ?? {},\n };\n }\n\n wasRejected(): boolean {\n return this.#rejectionDetails !== undefined;\n }\n\n get rejectionDetails() {\n return this.#rejectionDetails;\n }\n}\n"],"names":[],"mappings":";;AAqBO,MAAM,
|
|
1
|
+
{"version":3,"file":"RequestValidationContextImpl.cjs.js","sources":["../../../../src/service/http/validation/RequestValidationContextImpl.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 {\n RequestRejectionDetails,\n RequestValidationContext,\n} from '@backstage/plugin-events-node';\n\nexport class RequestValidationContextImpl implements RequestValidationContext {\n #rejectionDetails: RequestRejectionDetails | undefined;\n\n reject(details?: Partial<RequestRejectionDetails>): void {\n this.#rejectionDetails = {\n status: details?.status ?? 403,\n payload: details?.payload ?? {},\n };\n }\n\n wasRejected(): boolean {\n return this.#rejectionDetails !== undefined;\n }\n\n get rejectionDetails() {\n return this.#rejectionDetails;\n }\n}\n"],"names":[],"mappings":";;AAqBO,MAAM,4BAAA,CAAiE;AAAA,EAC5E,iBAAA;AAAA,EAEA,OAAO,OAAA,EAAkD;AACvD,IAAA,IAAA,CAAK,iBAAA,GAAoB;AAAA,MACvB,MAAA,EAAQ,SAAS,MAAA,IAAU,GAAA;AAAA,MAC3B,OAAA,EAAS,OAAA,EAAS,OAAA,IAAW;AAAC,KAChC;AAAA,EACF;AAAA,EAEA,WAAA,GAAuB;AACrB,IAAA,OAAO,KAAK,iBAAA,KAAsB,MAAA;AAAA,EACpC;AAAA,EAEA,IAAI,gBAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,iBAAA;AAAA,EACd;AACF;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatabaseEventBusStore.cjs.js","sources":["../../../src/service/hub/DatabaseEventBusStore.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 */\nimport { EventParams } from '@backstage/plugin-events-node';\nimport { EventBusStore } from './types';\nimport { Knex } from 'knex';\nimport {\n BackstageCredentials,\n BackstageServicePrincipal,\n DatabaseService,\n LifecycleService,\n LoggerService,\n SchedulerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { ForwardedError, NotFoundError } from '@backstage/errors';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\n\nconst WINDOW_MAX_COUNT_DEFAULT = 10_000;\nconst WINDOW_MIN_AGE_DEFAULT = { minutes: 10 };\nconst WINDOW_MAX_AGE_DEFAULT = { days: 1 };\n\nconst MAX_BATCH_SIZE = 10;\nconst LISTENER_CONNECTION_TIMEOUT_MS = 60_000;\nconst KEEPALIVE_INTERVAL_MS = 60_000;\n\nconst TABLE_EVENTS = 'event_bus_events';\nconst TABLE_SUBSCRIPTIONS = 'event_bus_subscriptions';\nconst TOPIC_PUBLISH = 'event_bus_publish';\n\ntype EventsRow = {\n id: string;\n created_by: string;\n created_at: Date;\n topic: string;\n data_json: string;\n notified_subscribers: string[];\n};\n\ntype SubscriptionsRow = {\n id: string;\n created_by: string;\n created_at: Date;\n updated_at: Date;\n read_until: string;\n topics: string[];\n};\n\nfunction creatorId(\n credentials: BackstageCredentials<BackstageServicePrincipal>,\n) {\n return `service=${credentials.principal.subject}`;\n}\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-events-backend',\n 'migrations',\n);\n\ninterface InternalDbClient {\n acquireRawConnection(): Promise<InternalDbConnection>;\n destroyRawConnection(conn: InternalDbConnection): Promise<void>;\n}\n\ninterface InternalDbConnection {\n query(sql: string): Promise<void>;\n end(): Promise<void>;\n on(\n event: 'notification',\n listener: (event: { channel: string; payload: string }) => void,\n ): void;\n on(event: 'error', listener: (error: Error) => void): void;\n on(event: 'end', listener: (error?: Error) => void): void;\n removeAllListeners(): void;\n}\n\n// This internal class manages a single connection to the database that all listeners share\nclass DatabaseEventBusListener {\n readonly #client: InternalDbClient;\n readonly #logger: LoggerService;\n\n readonly #listeners = new Set<{\n topics: Set<string>;\n resolve: (result: { topic: string }) => void;\n reject: (error: Error) => void;\n }>();\n\n #isShuttingDown = false;\n #connPromise?: Promise<InternalDbConnection>;\n #connTimeout?: NodeJS.Timeout;\n #keepaliveInterval?: NodeJS.Timeout;\n\n constructor(client: InternalDbClient, logger: LoggerService) {\n this.#client = client;\n this.#logger = logger.child({ type: 'DatabaseEventBusListener' });\n }\n\n async setupListener(\n topics: Set<string>,\n signal: AbortSignal,\n ): Promise<{ waitForUpdate(): Promise<{ topic: string }> }> {\n if (this.#connTimeout) {\n clearTimeout(this.#connTimeout);\n this.#connTimeout = undefined;\n }\n\n await this.#ensureConnection();\n\n const updatePromise = new Promise<{ topic: string }>((resolve, reject) => {\n const listener = {\n topics,\n resolve(result: { topic: string }) {\n resolve(result);\n cleanup();\n },\n reject(err: Error) {\n reject(err);\n cleanup();\n },\n };\n this.#listeners.add(listener);\n\n const onAbort = () => {\n this.#listeners.delete(listener);\n this.#maybeTimeoutConnection();\n reject(signal.reason);\n cleanup();\n };\n\n function cleanup() {\n signal.removeEventListener('abort', onAbort);\n }\n\n signal.addEventListener('abort', onAbort);\n });\n\n // Ignore unhandled rejections\n updatePromise.catch(() => {});\n\n return { waitForUpdate: () => updatePromise };\n }\n\n async shutdown() {\n if (this.#isShuttingDown) {\n return;\n }\n this.#isShuttingDown = true;\n const conn = await this.#connPromise?.catch(() => undefined);\n if (conn) {\n this.#destroyConnection(conn);\n }\n }\n\n #handleNotify(topic: string) {\n this.#logger.debug(`Listener received notification for topic '${topic}'`);\n for (const l of this.#listeners) {\n if (l.topics.has(topic)) {\n l.resolve({ topic });\n this.#listeners.delete(l);\n }\n }\n this.#maybeTimeoutConnection();\n }\n\n // We don't try to reconnect on error, instead we notify all listeners and let\n // them try to establish a new connection\n #handleError(error: Error) {\n this.#logger.error(\n `Listener connection failed, notifying all listeners`,\n error,\n );\n for (const l of this.#listeners) {\n l.reject(new Error('Listener connection failed'));\n }\n this.#listeners.clear();\n this.#maybeTimeoutConnection();\n }\n\n #maybeTimeoutConnection() {\n // If we don't have any listeners, destroy the connection after a timeout\n if (this.#listeners.size === 0 && !this.#connTimeout) {\n this.#connTimeout = setTimeout(() => {\n this.#connTimeout = undefined;\n this.#connPromise?.then(conn => {\n this.#logger.info('Listener connection timed out, destroying');\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n });\n }, LISTENER_CONNECTION_TIMEOUT_MS);\n }\n }\n\n #destroyConnection(conn: InternalDbConnection) {\n if (this.#keepaliveInterval) {\n clearInterval(this.#keepaliveInterval);\n this.#keepaliveInterval = undefined;\n }\n this.#client.destroyRawConnection(conn).catch(error => {\n this.#logger.error(`Listener failed to destroy connection`, error);\n });\n conn.removeAllListeners();\n }\n\n async #ensureConnection() {\n if (this.#isShuttingDown) {\n throw new Error('Listener is shutting down');\n }\n if (this.#connPromise) {\n await this.#connPromise;\n return;\n }\n this.#connPromise = Promise.resolve().then(async () => {\n const conn = await this.#client.acquireRawConnection();\n\n try {\n await conn.query(`LISTEN ${TOPIC_PUBLISH}`);\n\n // Set up a keepalive interval to make sure the connection stays alive\n if (this.#keepaliveInterval) {\n clearInterval(this.#keepaliveInterval);\n }\n this.#keepaliveInterval = setInterval(() => {\n conn.query('select 1').catch(error => {\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n this.#handleError(new ForwardedError('Keepalive failed', error));\n });\n }, KEEPALIVE_INTERVAL_MS);\n\n conn.on('notification', event => {\n this.#handleNotify(event.payload);\n });\n conn.on('error', error => {\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n this.#handleError(error);\n });\n conn.on('end', error => {\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n this.#handleError(\n error ?? new Error('Connection ended unexpectedly'),\n );\n });\n return conn;\n } catch (error) {\n this.#destroyConnection(conn);\n throw error;\n }\n });\n try {\n await this.#connPromise;\n } catch (error) {\n this.#connPromise = undefined;\n throw error;\n }\n }\n}\n\nexport class DatabaseEventBusStore implements EventBusStore {\n static async create(options: {\n database: DatabaseService;\n logger: LoggerService;\n scheduler: SchedulerService;\n lifecycle: LifecycleService;\n window?: {\n /** Events within this range will never be deleted */\n minAge?: HumanDuration;\n /** Events outside of this age will always be deleted */\n maxAge?: HumanDuration;\n /** Events outside of this count will be deleted if they are outside the minAge window */\n maxCount?: number;\n };\n }): Promise<DatabaseEventBusStore> {\n const db = await options.database.getClient();\n\n if (db.client.config.client !== 'pg') {\n throw new Error(\n `DatabaseEventBusStore only supports PostgreSQL, got '${db.client.config.client}'`,\n );\n }\n\n if (!options.database.migrations?.skip) {\n await db.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n const listener = new DatabaseEventBusListener(db.client, options.logger);\n\n const store = new DatabaseEventBusStore(\n db,\n options.logger,\n listener,\n options.window?.maxCount ?? WINDOW_MAX_COUNT_DEFAULT,\n durationToMilliseconds(options.window?.minAge ?? WINDOW_MIN_AGE_DEFAULT),\n durationToMilliseconds(options.window?.maxAge ?? WINDOW_MAX_AGE_DEFAULT),\n );\n\n await options.scheduler.scheduleTask({\n id: 'event-bus-cleanup',\n frequency: { seconds: 10 },\n timeout: { minutes: 1 },\n initialDelay: { seconds: 10 },\n fn: () => store.#cleanup(),\n });\n\n options.lifecycle.addShutdownHook(async () => {\n await listener.shutdown();\n });\n\n return store;\n }\n\n /** @internal */\n static async forTest({\n db,\n logger,\n minAge = 0,\n maxAge = 10_000,\n }: {\n db: Knex;\n logger: LoggerService;\n minAge?: number;\n maxAge?: number;\n }) {\n await db.migrate.latest({ directory: migrationsDir });\n\n const store = new DatabaseEventBusStore(\n db,\n logger,\n new DatabaseEventBusListener(db.client, logger),\n 5,\n minAge,\n maxAge,\n );\n\n return Object.assign(store, { clean: () => store.#cleanup() });\n }\n\n readonly #db: Knex;\n readonly #logger: LoggerService;\n readonly #listener: DatabaseEventBusListener;\n readonly #windowMaxCount: number;\n readonly #windowMinAge: number;\n readonly #windowMaxAge: number;\n\n private constructor(\n db: Knex,\n logger: LoggerService,\n listener: DatabaseEventBusListener,\n windowMaxCount: number,\n windowMinAge: number,\n windowMaxAge: number,\n ) {\n this.#db = db;\n this.#logger = logger;\n this.#listener = listener;\n this.#windowMaxCount = windowMaxCount;\n this.#windowMinAge = windowMinAge;\n this.#windowMaxAge = windowMaxAge;\n }\n\n async publish(options: {\n event: EventParams;\n notifiedSubscribers?: string[];\n credentials: BackstageCredentials<BackstageServicePrincipal>;\n }): Promise<{ eventId: string } | undefined> {\n const topic = options.event.topic;\n const notifiedSubscribers = options.notifiedSubscribers ?? [];\n // This query inserts a new event into the database, but only if there are\n // subscribers to the topic that have not already been notified\n const result = await this.#db\n // There's no clean way to create a INSERT INTO .. SELECT with knex, so we end up with quite a lot of .raw(...)\n .into(\n this.#db.raw('?? (??, ??, ??, ??)', [\n TABLE_EVENTS,\n // These are the rows that we insert, and should match the SELECT below\n 'created_by',\n 'topic',\n 'data_json',\n 'notified_subscribers',\n ]),\n )\n .insert<EventsRow>(\n (q: Knex.QueryBuilder) =>\n q\n // We're not reading data to insert from anywhere else, just raw data\n .select(\n this.#db.raw('?', [creatorId(options.credentials)]),\n this.#db.raw('?', [topic]),\n this.#db.raw('?', [\n JSON.stringify({\n payload: options.event.eventPayload,\n metadata: options.event.metadata,\n }),\n ]),\n this.#db.raw('?', [notifiedSubscribers]),\n )\n // The rest of this query is to check whether there are any\n // subscribers that have not been notified yet\n .from(TABLE_SUBSCRIPTIONS)\n .whereNotIn('id', notifiedSubscribers) // Skip notified subscribers\n .andWhere(this.#db.raw('? = ANY(topics)', [topic])) // Match topic\n .having(this.#db.raw('count(*)'), '>', 0), // Check if there are any results\n )\n .returning<{ id: string }[]>('id');\n\n if (result.length === 0) {\n return undefined;\n }\n if (result.length > 1) {\n throw new Error(\n `Failed to insert event, unexpectedly updated ${result.length} rows`,\n );\n }\n\n const [{ id }] = result;\n\n // Notify other event bus instances that an event is available on the topic\n const notifyResult = await this.#db.select(\n this.#db.raw(`pg_notify(?, ?)`, [TOPIC_PUBLISH, topic]),\n );\n if (notifyResult?.length !== 1) {\n this.#logger.warn(\n `Failed to notify subscribers of event with ID '${id}' on topic '${topic}'`,\n );\n }\n\n return { eventId: id };\n }\n\n async upsertSubscription(\n id: string,\n topics: string[],\n credentials: BackstageCredentials<BackstageServicePrincipal>,\n ): Promise<void> {\n const [{ max: maxId }] = await this.#db(TABLE_EVENTS).max('id');\n const result = await this.#db<SubscriptionsRow>(TABLE_SUBSCRIPTIONS)\n .insert({\n id,\n created_by: creatorId(credentials),\n updated_at: this.#db.fn.now(),\n topics,\n read_until: maxId || 0,\n })\n .onConflict('id')\n .merge(['created_by', 'topics', 'updated_at'])\n .returning('*');\n\n if (result.length !== 1) {\n throw new Error(\n `Failed to upsert subscription, updated ${result.length} rows`,\n );\n }\n }\n\n async readSubscription(id: string): Promise<{ events: EventParams[] }> {\n // The below query selects the subscription we're reading from, locks it for\n // an update, reads events for the subscription up to the limit, and then\n // updates the pointer to the last read event.\n //\n // This is written as a plain SQL query to spare us all the horrors of\n // expressing this in knex.\n\n const { rows: result } = await this.#db.raw<{\n rows: [] | [{ events: EventsRow[] }];\n }>(\n `\n WITH subscription AS (\n SELECT topics, read_until\n FROM event_bus_subscriptions\n WHERE id = :id\n FOR UPDATE\n ),\n selected_events AS (\n SELECT event_bus_events.*\n FROM event_bus_events\n INNER JOIN subscription\n ON event_bus_events.topic = ANY(subscription.topics)\n WHERE event_bus_events.id > subscription.read_until\n AND NOT :id = ANY(event_bus_events.notified_subscribers)\n ORDER BY event_bus_events.id ASC LIMIT :limit\n ),\n last_event_id AS (\n SELECT max(id) AS last_event_id\n FROM selected_events\n ),\n events_array AS (\n SELECT json_agg(row_to_json(selected_events)) AS events\n FROM selected_events\n )\n UPDATE event_bus_subscriptions\n SET read_until = COALESCE(last_event_id, (SELECT MAX(id) FROM event_bus_events), 0)\n FROM events_array, last_event_id\n WHERE event_bus_subscriptions.id = :id\n RETURNING events_array.events\n `,\n { id, limit: MAX_BATCH_SIZE },\n );\n\n if (result.length === 0) {\n throw new NotFoundError(`Subscription with ID '${id}' not found`);\n } else if (result.length > 1) {\n throw new Error(\n `Failed to read subscription, unexpectedly updated ${result.length} rows`,\n );\n }\n\n const rows = result[0].events;\n if (!rows || rows.length === 0) {\n return { events: [] };\n }\n\n return {\n events: rows.map(row => {\n const { payload, metadata } = JSON.parse(row.data_json);\n return {\n topic: row.topic,\n eventPayload: payload,\n metadata,\n };\n }),\n };\n }\n\n async setupListener(\n subscriptionId: string,\n options: {\n signal: AbortSignal;\n },\n ): Promise<{ waitForUpdate(): Promise<{ topic: string }> }> {\n const result = await this.#db<SubscriptionsRow>(TABLE_SUBSCRIPTIONS)\n .select('topics')\n .where({ id: subscriptionId })\n .first();\n\n if (!result) {\n throw new NotFoundError(\n `Subscription with ID '${subscriptionId}' not found`,\n );\n }\n\n options.signal.throwIfAborted();\n\n return this.#listener.setupListener(\n new Set(result.topics ?? []),\n options.signal,\n );\n }\n\n async #cleanup() {\n try {\n const eventCount = await this.#db(TABLE_EVENTS)\n .delete()\n // Delete any events that are outside both the min age and size window\n .orWhere(inner =>\n inner\n .whereIn(\n 'id',\n this.#db\n .select('id')\n .from(TABLE_EVENTS)\n .orderBy('id', 'desc')\n .offset(this.#windowMaxCount),\n )\n .andWhere(\n 'created_at',\n '<',\n new Date(Date.now() - this.#windowMinAge),\n ),\n )\n // If events are outside the max age they will always be deleted\n .orWhere('created_at', '<', new Date(Date.now() - this.#windowMaxAge));\n\n if (eventCount > 0) {\n this.#logger.info(\n `Event cleanup resulted in ${eventCount} old events being deleted`,\n );\n }\n } catch (error) {\n this.#logger.error('Event cleanup failed', error);\n }\n\n try {\n // Delete any subscribers that aren't keeping up with current events\n const [{ min: minId }] = await this.#db(TABLE_EVENTS).min('id');\n\n let subscriberCount;\n if (minId === null) {\n // No events left, remove all subscribers. This can happen if no events\n // are published within the max age window.\n subscriberCount = await this.#db(TABLE_SUBSCRIPTIONS)\n .where('updated_at', '<', new Date(Date.now() - this.#windowMaxAge))\n .delete();\n } else {\n subscriberCount = await this.#db(TABLE_SUBSCRIPTIONS)\n .delete()\n // Read pointer points to the ID that has been read, so we need an additional offset\n .where('read_until', '<', minId - 1);\n }\n\n if (subscriberCount > 0) {\n this.#logger.info(\n `Subscription cleanup resulted in ${subscriberCount} stale subscribers being deleted`,\n );\n }\n } catch (error) {\n this.#logger.error('Subscription cleanup failed', error);\n }\n }\n}\n"],"names":["resolvePackagePath","ForwardedError","durationToMilliseconds","NotFoundError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DatabaseEventBusStore.cjs.js","sources":["../../../src/service/hub/DatabaseEventBusStore.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 */\nimport { EventParams } from '@backstage/plugin-events-node';\nimport { EventBusStore } from './types';\nimport { Knex } from 'knex';\nimport {\n BackstageCredentials,\n BackstageServicePrincipal,\n DatabaseService,\n LifecycleService,\n LoggerService,\n SchedulerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\nimport { ForwardedError, NotFoundError } from '@backstage/errors';\nimport { HumanDuration, durationToMilliseconds } from '@backstage/types';\n\nconst WINDOW_MAX_COUNT_DEFAULT = 10_000;\nconst WINDOW_MIN_AGE_DEFAULT = { minutes: 10 };\nconst WINDOW_MAX_AGE_DEFAULT = { days: 1 };\n\nconst MAX_BATCH_SIZE = 10;\nconst LISTENER_CONNECTION_TIMEOUT_MS = 60_000;\nconst KEEPALIVE_INTERVAL_MS = 60_000;\n\nconst TABLE_EVENTS = 'event_bus_events';\nconst TABLE_SUBSCRIPTIONS = 'event_bus_subscriptions';\nconst TOPIC_PUBLISH = 'event_bus_publish';\n\ntype EventsRow = {\n id: string;\n created_by: string;\n created_at: Date;\n topic: string;\n data_json: string;\n notified_subscribers: string[];\n};\n\ntype SubscriptionsRow = {\n id: string;\n created_by: string;\n created_at: Date;\n updated_at: Date;\n read_until: string;\n topics: string[];\n};\n\nfunction creatorId(\n credentials: BackstageCredentials<BackstageServicePrincipal>,\n) {\n return `service=${credentials.principal.subject}`;\n}\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-events-backend',\n 'migrations',\n);\n\ninterface InternalDbClient {\n acquireRawConnection(): Promise<InternalDbConnection>;\n destroyRawConnection(conn: InternalDbConnection): Promise<void>;\n}\n\ninterface InternalDbConnection {\n query(sql: string): Promise<void>;\n end(): Promise<void>;\n on(\n event: 'notification',\n listener: (event: { channel: string; payload: string }) => void,\n ): void;\n on(event: 'error', listener: (error: Error) => void): void;\n on(event: 'end', listener: (error?: Error) => void): void;\n removeAllListeners(): void;\n}\n\n// This internal class manages a single connection to the database that all listeners share\nclass DatabaseEventBusListener {\n readonly #client: InternalDbClient;\n readonly #logger: LoggerService;\n\n readonly #listeners = new Set<{\n topics: Set<string>;\n resolve: (result: { topic: string }) => void;\n reject: (error: Error) => void;\n }>();\n\n #isShuttingDown = false;\n #connPromise?: Promise<InternalDbConnection>;\n #connTimeout?: NodeJS.Timeout;\n #keepaliveInterval?: NodeJS.Timeout;\n\n constructor(client: InternalDbClient, logger: LoggerService) {\n this.#client = client;\n this.#logger = logger.child({ type: 'DatabaseEventBusListener' });\n }\n\n async setupListener(\n topics: Set<string>,\n signal: AbortSignal,\n ): Promise<{ waitForUpdate(): Promise<{ topic: string }> }> {\n if (this.#connTimeout) {\n clearTimeout(this.#connTimeout);\n this.#connTimeout = undefined;\n }\n\n await this.#ensureConnection();\n\n const updatePromise = new Promise<{ topic: string }>((resolve, reject) => {\n const listener = {\n topics,\n resolve(result: { topic: string }) {\n resolve(result);\n cleanup();\n },\n reject(err: Error) {\n reject(err);\n cleanup();\n },\n };\n this.#listeners.add(listener);\n\n const onAbort = () => {\n this.#listeners.delete(listener);\n this.#maybeTimeoutConnection();\n reject(signal.reason);\n cleanup();\n };\n\n function cleanup() {\n signal.removeEventListener('abort', onAbort);\n }\n\n signal.addEventListener('abort', onAbort);\n });\n\n // Ignore unhandled rejections\n updatePromise.catch(() => {});\n\n return { waitForUpdate: () => updatePromise };\n }\n\n async shutdown() {\n if (this.#isShuttingDown) {\n return;\n }\n this.#isShuttingDown = true;\n const conn = await this.#connPromise?.catch(() => undefined);\n if (conn) {\n this.#destroyConnection(conn);\n }\n }\n\n #handleNotify(topic: string) {\n this.#logger.debug(`Listener received notification for topic '${topic}'`);\n for (const l of this.#listeners) {\n if (l.topics.has(topic)) {\n l.resolve({ topic });\n this.#listeners.delete(l);\n }\n }\n this.#maybeTimeoutConnection();\n }\n\n // We don't try to reconnect on error, instead we notify all listeners and let\n // them try to establish a new connection\n #handleError(error: Error) {\n this.#logger.error(\n `Listener connection failed, notifying all listeners`,\n error,\n );\n for (const l of this.#listeners) {\n l.reject(new Error('Listener connection failed'));\n }\n this.#listeners.clear();\n this.#maybeTimeoutConnection();\n }\n\n #maybeTimeoutConnection() {\n // If we don't have any listeners, destroy the connection after a timeout\n if (this.#listeners.size === 0 && !this.#connTimeout) {\n this.#connTimeout = setTimeout(() => {\n this.#connTimeout = undefined;\n this.#connPromise?.then(conn => {\n this.#logger.info('Listener connection timed out, destroying');\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n });\n }, LISTENER_CONNECTION_TIMEOUT_MS);\n }\n }\n\n #destroyConnection(conn: InternalDbConnection) {\n if (this.#keepaliveInterval) {\n clearInterval(this.#keepaliveInterval);\n this.#keepaliveInterval = undefined;\n }\n this.#client.destroyRawConnection(conn).catch(error => {\n this.#logger.error(`Listener failed to destroy connection`, error);\n });\n conn.removeAllListeners();\n }\n\n async #ensureConnection() {\n if (this.#isShuttingDown) {\n throw new Error('Listener is shutting down');\n }\n if (this.#connPromise) {\n await this.#connPromise;\n return;\n }\n this.#connPromise = Promise.resolve().then(async () => {\n const conn = await this.#client.acquireRawConnection();\n\n try {\n await conn.query(`LISTEN ${TOPIC_PUBLISH}`);\n\n // Set up a keepalive interval to make sure the connection stays alive\n if (this.#keepaliveInterval) {\n clearInterval(this.#keepaliveInterval);\n }\n this.#keepaliveInterval = setInterval(() => {\n conn.query('select 1').catch(error => {\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n this.#handleError(new ForwardedError('Keepalive failed', error));\n });\n }, KEEPALIVE_INTERVAL_MS);\n\n conn.on('notification', event => {\n this.#handleNotify(event.payload);\n });\n conn.on('error', error => {\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n this.#handleError(error);\n });\n conn.on('end', error => {\n this.#connPromise = undefined;\n this.#destroyConnection(conn);\n this.#handleError(\n error ?? new Error('Connection ended unexpectedly'),\n );\n });\n return conn;\n } catch (error) {\n this.#destroyConnection(conn);\n throw error;\n }\n });\n try {\n await this.#connPromise;\n } catch (error) {\n this.#connPromise = undefined;\n throw error;\n }\n }\n}\n\nexport class DatabaseEventBusStore implements EventBusStore {\n static async create(options: {\n database: DatabaseService;\n logger: LoggerService;\n scheduler: SchedulerService;\n lifecycle: LifecycleService;\n window?: {\n /** Events within this range will never be deleted */\n minAge?: HumanDuration;\n /** Events outside of this age will always be deleted */\n maxAge?: HumanDuration;\n /** Events outside of this count will be deleted if they are outside the minAge window */\n maxCount?: number;\n };\n }): Promise<DatabaseEventBusStore> {\n const db = await options.database.getClient();\n\n if (db.client.config.client !== 'pg') {\n throw new Error(\n `DatabaseEventBusStore only supports PostgreSQL, got '${db.client.config.client}'`,\n );\n }\n\n if (!options.database.migrations?.skip) {\n await db.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n const listener = new DatabaseEventBusListener(db.client, options.logger);\n\n const store = new DatabaseEventBusStore(\n db,\n options.logger,\n listener,\n options.window?.maxCount ?? WINDOW_MAX_COUNT_DEFAULT,\n durationToMilliseconds(options.window?.minAge ?? WINDOW_MIN_AGE_DEFAULT),\n durationToMilliseconds(options.window?.maxAge ?? WINDOW_MAX_AGE_DEFAULT),\n );\n\n await options.scheduler.scheduleTask({\n id: 'event-bus-cleanup',\n frequency: { seconds: 10 },\n timeout: { minutes: 1 },\n initialDelay: { seconds: 10 },\n fn: () => store.#cleanup(),\n });\n\n options.lifecycle.addShutdownHook(async () => {\n await listener.shutdown();\n });\n\n return store;\n }\n\n /** @internal */\n static async forTest({\n db,\n logger,\n minAge = 0,\n maxAge = 10_000,\n }: {\n db: Knex;\n logger: LoggerService;\n minAge?: number;\n maxAge?: number;\n }) {\n await db.migrate.latest({ directory: migrationsDir });\n\n const store = new DatabaseEventBusStore(\n db,\n logger,\n new DatabaseEventBusListener(db.client, logger),\n 5,\n minAge,\n maxAge,\n );\n\n return Object.assign(store, { clean: () => store.#cleanup() });\n }\n\n readonly #db: Knex;\n readonly #logger: LoggerService;\n readonly #listener: DatabaseEventBusListener;\n readonly #windowMaxCount: number;\n readonly #windowMinAge: number;\n readonly #windowMaxAge: number;\n\n private constructor(\n db: Knex,\n logger: LoggerService,\n listener: DatabaseEventBusListener,\n windowMaxCount: number,\n windowMinAge: number,\n windowMaxAge: number,\n ) {\n this.#db = db;\n this.#logger = logger;\n this.#listener = listener;\n this.#windowMaxCount = windowMaxCount;\n this.#windowMinAge = windowMinAge;\n this.#windowMaxAge = windowMaxAge;\n }\n\n async publish(options: {\n event: EventParams;\n notifiedSubscribers?: string[];\n credentials: BackstageCredentials<BackstageServicePrincipal>;\n }): Promise<{ eventId: string } | undefined> {\n const topic = options.event.topic;\n const notifiedSubscribers = options.notifiedSubscribers ?? [];\n // This query inserts a new event into the database, but only if there are\n // subscribers to the topic that have not already been notified\n const result = await this.#db\n // There's no clean way to create a INSERT INTO .. SELECT with knex, so we end up with quite a lot of .raw(...)\n .into(\n this.#db.raw('?? (??, ??, ??, ??)', [\n TABLE_EVENTS,\n // These are the rows that we insert, and should match the SELECT below\n 'created_by',\n 'topic',\n 'data_json',\n 'notified_subscribers',\n ]),\n )\n .insert<EventsRow>(\n (q: Knex.QueryBuilder) =>\n q\n // We're not reading data to insert from anywhere else, just raw data\n .select(\n this.#db.raw('?', [creatorId(options.credentials)]),\n this.#db.raw('?', [topic]),\n this.#db.raw('?', [\n JSON.stringify({\n payload: options.event.eventPayload,\n metadata: options.event.metadata,\n }),\n ]),\n this.#db.raw('?', [notifiedSubscribers]),\n )\n // The rest of this query is to check whether there are any\n // subscribers that have not been notified yet\n .from(TABLE_SUBSCRIPTIONS)\n .whereNotIn('id', notifiedSubscribers) // Skip notified subscribers\n .andWhere(this.#db.raw('? = ANY(topics)', [topic])) // Match topic\n .having(this.#db.raw('count(*)'), '>', 0), // Check if there are any results\n )\n .returning<{ id: string }[]>('id');\n\n if (result.length === 0) {\n return undefined;\n }\n if (result.length > 1) {\n throw new Error(\n `Failed to insert event, unexpectedly updated ${result.length} rows`,\n );\n }\n\n const [{ id }] = result;\n\n // Notify other event bus instances that an event is available on the topic\n const notifyResult = await this.#db.select(\n this.#db.raw(`pg_notify(?, ?)`, [TOPIC_PUBLISH, topic]),\n );\n if (notifyResult?.length !== 1) {\n this.#logger.warn(\n `Failed to notify subscribers of event with ID '${id}' on topic '${topic}'`,\n );\n }\n\n return { eventId: id };\n }\n\n async upsertSubscription(\n id: string,\n topics: string[],\n credentials: BackstageCredentials<BackstageServicePrincipal>,\n ): Promise<void> {\n const [{ max: maxId }] = await this.#db(TABLE_EVENTS).max('id');\n const result = await this.#db<SubscriptionsRow>(TABLE_SUBSCRIPTIONS)\n .insert({\n id,\n created_by: creatorId(credentials),\n updated_at: this.#db.fn.now(),\n topics,\n read_until: maxId || 0,\n })\n .onConflict('id')\n .merge(['created_by', 'topics', 'updated_at'])\n .returning('*');\n\n if (result.length !== 1) {\n throw new Error(\n `Failed to upsert subscription, updated ${result.length} rows`,\n );\n }\n }\n\n async readSubscription(id: string): Promise<{ events: EventParams[] }> {\n // The below query selects the subscription we're reading from, locks it for\n // an update, reads events for the subscription up to the limit, and then\n // updates the pointer to the last read event.\n //\n // This is written as a plain SQL query to spare us all the horrors of\n // expressing this in knex.\n\n const { rows: result } = await this.#db.raw<{\n rows: [] | [{ events: EventsRow[] }];\n }>(\n `\n WITH subscription AS (\n SELECT topics, read_until\n FROM event_bus_subscriptions\n WHERE id = :id\n FOR UPDATE\n ),\n selected_events AS (\n SELECT event_bus_events.*\n FROM event_bus_events\n INNER JOIN subscription\n ON event_bus_events.topic = ANY(subscription.topics)\n WHERE event_bus_events.id > subscription.read_until\n AND NOT :id = ANY(event_bus_events.notified_subscribers)\n ORDER BY event_bus_events.id ASC LIMIT :limit\n ),\n last_event_id AS (\n SELECT max(id) AS last_event_id\n FROM selected_events\n ),\n events_array AS (\n SELECT json_agg(row_to_json(selected_events)) AS events\n FROM selected_events\n )\n UPDATE event_bus_subscriptions\n SET read_until = COALESCE(last_event_id, (SELECT MAX(id) FROM event_bus_events), 0)\n FROM events_array, last_event_id\n WHERE event_bus_subscriptions.id = :id\n RETURNING events_array.events\n `,\n { id, limit: MAX_BATCH_SIZE },\n );\n\n if (result.length === 0) {\n throw new NotFoundError(`Subscription with ID '${id}' not found`);\n } else if (result.length > 1) {\n throw new Error(\n `Failed to read subscription, unexpectedly updated ${result.length} rows`,\n );\n }\n\n const rows = result[0].events;\n if (!rows || rows.length === 0) {\n return { events: [] };\n }\n\n return {\n events: rows.map(row => {\n const { payload, metadata } = JSON.parse(row.data_json);\n return {\n topic: row.topic,\n eventPayload: payload,\n metadata,\n };\n }),\n };\n }\n\n async setupListener(\n subscriptionId: string,\n options: {\n signal: AbortSignal;\n },\n ): Promise<{ waitForUpdate(): Promise<{ topic: string }> }> {\n const result = await this.#db<SubscriptionsRow>(TABLE_SUBSCRIPTIONS)\n .select('topics')\n .where({ id: subscriptionId })\n .first();\n\n if (!result) {\n throw new NotFoundError(\n `Subscription with ID '${subscriptionId}' not found`,\n );\n }\n\n options.signal.throwIfAborted();\n\n return this.#listener.setupListener(\n new Set(result.topics ?? []),\n options.signal,\n );\n }\n\n async #cleanup() {\n try {\n const eventCount = await this.#db(TABLE_EVENTS)\n .delete()\n // Delete any events that are outside both the min age and size window\n .orWhere(inner =>\n inner\n .whereIn(\n 'id',\n this.#db\n .select('id')\n .from(TABLE_EVENTS)\n .orderBy('id', 'desc')\n .offset(this.#windowMaxCount),\n )\n .andWhere(\n 'created_at',\n '<',\n new Date(Date.now() - this.#windowMinAge),\n ),\n )\n // If events are outside the max age they will always be deleted\n .orWhere('created_at', '<', new Date(Date.now() - this.#windowMaxAge));\n\n if (eventCount > 0) {\n this.#logger.info(\n `Event cleanup resulted in ${eventCount} old events being deleted`,\n );\n }\n } catch (error) {\n this.#logger.error('Event cleanup failed', error);\n }\n\n try {\n // Delete any subscribers that aren't keeping up with current events\n const [{ min: minId }] = await this.#db(TABLE_EVENTS).min('id');\n\n let subscriberCount;\n if (minId === null) {\n // No events left, remove all subscribers. This can happen if no events\n // are published within the max age window.\n subscriberCount = await this.#db(TABLE_SUBSCRIPTIONS)\n .where('updated_at', '<', new Date(Date.now() - this.#windowMaxAge))\n .delete();\n } else {\n subscriberCount = await this.#db(TABLE_SUBSCRIPTIONS)\n .delete()\n // Read pointer points to the ID that has been read, so we need an additional offset\n .where('read_until', '<', minId - 1);\n }\n\n if (subscriberCount > 0) {\n this.#logger.info(\n `Subscription cleanup resulted in ${subscriberCount} stale subscribers being deleted`,\n );\n }\n } catch (error) {\n this.#logger.error('Subscription cleanup failed', error);\n }\n }\n}\n"],"names":["resolvePackagePath","ForwardedError","durationToMilliseconds","NotFoundError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemoryEventBusStore.cjs.js","sources":["../../../src/service/hub/MemoryEventBusStore.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 */\nimport { EventParams } from '@backstage/plugin-events-node';\nimport { EventBusStore } from './types';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n BackstageCredentials,\n BackstageServicePrincipal,\n} from '@backstage/backend-plugin-api';\n\nconst MAX_BATCH_SIZE = 10;\nconst MAX_EVENTS_DEFAULT = 1_000;\n\nexport class MemoryEventBusStore implements EventBusStore {\n #maxEvents: number;\n #events = new Array<\n EventParams & { seq: number; notifiedSubscribers: Set<string> }\n >();\n #subscribers = new Map<\n string,\n { id: string; seq: number; topics: Set<string> }\n >();\n #listeners = new Set<{\n topics: Set<string>;\n resolve(result: { topic: string }): void;\n }>();\n\n constructor(options: { maxEvents?: number } = {}) {\n this.#maxEvents = options.maxEvents ?? MAX_EVENTS_DEFAULT;\n }\n\n async publish(options: {\n event: EventParams;\n notifiedSubscribers: string[];\n credentials: BackstageCredentials<BackstageServicePrincipal>;\n }): Promise<{ eventId: string } | undefined> {\n const topic = options.event.topic;\n const notifiedSubscribers = new Set(options.notifiedSubscribers);\n\n let hasOtherSubscribers = false;\n for (const sub of this.#subscribers.values()) {\n if (sub.topics.has(topic) && !notifiedSubscribers.has(sub.id)) {\n hasOtherSubscribers = true;\n break;\n }\n }\n if (!hasOtherSubscribers) {\n return undefined;\n }\n\n const nextSeq = this.#getMaxSeq() + 1;\n this.#events.push({ ...options.event, notifiedSubscribers, seq: nextSeq });\n\n for (const listener of this.#listeners) {\n if (listener.topics.has(topic)) {\n listener.resolve({ topic });\n this.#listeners.delete(listener);\n }\n }\n\n // Trim old events\n if (this.#events.length > this.#maxEvents) {\n this.#events.shift();\n }\n\n return { eventId: String(nextSeq) };\n }\n\n #getMaxSeq() {\n return this.#events[this.#events.length - 1]?.seq ?? 0;\n }\n\n async upsertSubscription(id: string, topics: string[]): Promise<void> {\n const existing = this.#subscribers.get(id);\n if (existing) {\n existing.topics = new Set(topics);\n return;\n }\n const sub = {\n id: id,\n seq: this.#getMaxSeq(),\n topics: new Set(topics),\n };\n this.#subscribers.set(id, sub);\n }\n\n async readSubscription(id: string): Promise<{ events: EventParams[] }> {\n const sub = this.#subscribers.get(id);\n if (!sub) {\n throw new NotFoundError(`Subscription not found`);\n }\n const events = this.#events\n .filter(\n event =>\n event.seq > sub.seq &&\n sub.topics.has(event.topic) &&\n !event.notifiedSubscribers.has(id),\n )\n .slice(0, MAX_BATCH_SIZE);\n\n sub.seq = events[events.length - 1]?.seq ?? sub.seq;\n\n return {\n events: events.map(({ topic, eventPayload }) => ({\n topic,\n eventPayload,\n })),\n };\n }\n\n async setupListener(\n subscriptionId: string,\n options: {\n signal: AbortSignal;\n },\n ): Promise<{ waitForUpdate(): Promise<{ topic: string }> }> {\n return {\n waitForUpdate: async () => {\n options.signal.throwIfAborted();\n\n const sub = this.#subscribers.get(subscriptionId);\n if (!sub) {\n throw new NotFoundError(`Subscription not found`);\n }\n\n return new Promise<{ topic: string }>((resolve, reject) => {\n const listener = {\n topics: sub.topics,\n resolve(result: { topic: string }) {\n resolve(result);\n cleanup();\n },\n };\n this.#listeners.add(listener);\n\n const onAbort = () => {\n this.#listeners.delete(listener);\n reject(options.signal.reason);\n cleanup();\n };\n\n function cleanup() {\n options.signal.removeEventListener('abort', onAbort);\n }\n\n options.signal.addEventListener('abort', onAbort);\n });\n },\n };\n }\n}\n"],"names":["NotFoundError"],"mappings":";;;;AAuBA,MAAM,
|
|
1
|
+
{"version":3,"file":"MemoryEventBusStore.cjs.js","sources":["../../../src/service/hub/MemoryEventBusStore.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 */\nimport { EventParams } from '@backstage/plugin-events-node';\nimport { EventBusStore } from './types';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n BackstageCredentials,\n BackstageServicePrincipal,\n} from '@backstage/backend-plugin-api';\n\nconst MAX_BATCH_SIZE = 10;\nconst MAX_EVENTS_DEFAULT = 1_000;\n\nexport class MemoryEventBusStore implements EventBusStore {\n #maxEvents: number;\n #events = new Array<\n EventParams & { seq: number; notifiedSubscribers: Set<string> }\n >();\n #subscribers = new Map<\n string,\n { id: string; seq: number; topics: Set<string> }\n >();\n #listeners = new Set<{\n topics: Set<string>;\n resolve(result: { topic: string }): void;\n }>();\n\n constructor(options: { maxEvents?: number } = {}) {\n this.#maxEvents = options.maxEvents ?? MAX_EVENTS_DEFAULT;\n }\n\n async publish(options: {\n event: EventParams;\n notifiedSubscribers: string[];\n credentials: BackstageCredentials<BackstageServicePrincipal>;\n }): Promise<{ eventId: string } | undefined> {\n const topic = options.event.topic;\n const notifiedSubscribers = new Set(options.notifiedSubscribers);\n\n let hasOtherSubscribers = false;\n for (const sub of this.#subscribers.values()) {\n if (sub.topics.has(topic) && !notifiedSubscribers.has(sub.id)) {\n hasOtherSubscribers = true;\n break;\n }\n }\n if (!hasOtherSubscribers) {\n return undefined;\n }\n\n const nextSeq = this.#getMaxSeq() + 1;\n this.#events.push({ ...options.event, notifiedSubscribers, seq: nextSeq });\n\n for (const listener of this.#listeners) {\n if (listener.topics.has(topic)) {\n listener.resolve({ topic });\n this.#listeners.delete(listener);\n }\n }\n\n // Trim old events\n if (this.#events.length > this.#maxEvents) {\n this.#events.shift();\n }\n\n return { eventId: String(nextSeq) };\n }\n\n #getMaxSeq() {\n return this.#events[this.#events.length - 1]?.seq ?? 0;\n }\n\n async upsertSubscription(id: string, topics: string[]): Promise<void> {\n const existing = this.#subscribers.get(id);\n if (existing) {\n existing.topics = new Set(topics);\n return;\n }\n const sub = {\n id: id,\n seq: this.#getMaxSeq(),\n topics: new Set(topics),\n };\n this.#subscribers.set(id, sub);\n }\n\n async readSubscription(id: string): Promise<{ events: EventParams[] }> {\n const sub = this.#subscribers.get(id);\n if (!sub) {\n throw new NotFoundError(`Subscription not found`);\n }\n const events = this.#events\n .filter(\n event =>\n event.seq > sub.seq &&\n sub.topics.has(event.topic) &&\n !event.notifiedSubscribers.has(id),\n )\n .slice(0, MAX_BATCH_SIZE);\n\n sub.seq = events[events.length - 1]?.seq ?? sub.seq;\n\n return {\n events: events.map(({ topic, eventPayload }) => ({\n topic,\n eventPayload,\n })),\n };\n }\n\n async setupListener(\n subscriptionId: string,\n options: {\n signal: AbortSignal;\n },\n ): Promise<{ waitForUpdate(): Promise<{ topic: string }> }> {\n return {\n waitForUpdate: async () => {\n options.signal.throwIfAborted();\n\n const sub = this.#subscribers.get(subscriptionId);\n if (!sub) {\n throw new NotFoundError(`Subscription not found`);\n }\n\n return new Promise<{ topic: string }>((resolve, reject) => {\n const listener = {\n topics: sub.topics,\n resolve(result: { topic: string }) {\n resolve(result);\n cleanup();\n },\n };\n this.#listeners.add(listener);\n\n const onAbort = () => {\n this.#listeners.delete(listener);\n reject(options.signal.reason);\n cleanup();\n };\n\n function cleanup() {\n options.signal.removeEventListener('abort', onAbort);\n }\n\n options.signal.addEventListener('abort', onAbort);\n });\n },\n };\n }\n}\n"],"names":["NotFoundError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createEventBusRouter.cjs.js","sources":["../../../src/service/hub/createEventBusRouter.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 DatabaseService,\n HttpAuthService,\n LifecycleService,\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { Handler } from 'express';\nimport { createOpenApiRouter } from '../../schema/openapi';\nimport { MemoryEventBusStore } from './MemoryEventBusStore';\nimport { DatabaseEventBusStore } from './DatabaseEventBusStore';\nimport { EventBusStore } from './types';\nimport {\n EVENTS_NOTIFY_TIMEOUT_HEADER,\n EventParams,\n} from '@backstage/plugin-events-node';\n\nconst DEFAULT_NOTIFY_TIMEOUT_MS = 55_000; // Just below 60s, which is a common HTTP timeout\n\n/*\n\n# Event Bus\n\nThis comment describes the event bus that is implemented here in the events\nbackend, and by default used by the events service.\n\n## Overview\n\nThe events bus implements a subscription mechanism where subscribers must exist\nupfront for events to be stored. It uses a single inbox for all events, with\neach subscriber having its own pointer for how far into the inbox it has read.\n\nIn order to avoid busy polling, the API uses a long-polling mechanism where a\nrequest is left hanging until the client should try to read again.\n\nThe event bus is not implemented with any guarantees of events being consumed,\nbut it does aim to make it unlikely that events are dropped\n\n## API\n\n### POST /bus/v1/events\n\nThis endpoint is used to publish new events to the event bus on a specific\ntopic. It can optionally include a set of subscription IDs for subscribers that\nhave already been notified of the event. This is to enable an optimization where\nwe notify subscribers locally if possible, and avoid the need for events to be\nrelayed through the events bus at all of possible.\n\nFor an event to be published and stored there must already exist a subscriber\nthat is subscribed to the event's topic, and that hasn't already been notified\nof the event. If no such subscriber is found, the event will be discarded.\n\n### PUT /bus/v1/subscriptions/:subscriptionId\n\nThis endpoint is used to create or update a subscriptions. Subscriptions are\nshared across the entire bus and divided by subscription ID. Multiple clients\ncan be reading events from the same subscription at the same time, but only one\nof those clients will receive each event. This enables division of work by using\nthe same subscriber ID across multiple instances, as well as broadcasting by\nensuring separate subscribers IDs.\n\n### GET /bus/v1/subscriptions/:subscriptionId/events\n\nThis endpoint is used to read events from a subscription. It will return a batch\nof events for the subscribed topics that have not yet been read by the\nsubscription. If no such events are available, the endpoint will return a 202\nresponse and then hang end response until an event is available or a timeout is\nreached. This allows clients to call this endpoint in a loop but will keep\ntraffic overhead fairly low.\n\n## Delivery guarantees\n\nWhen reading events from the event bus, clients should always implement a\ngraceful shutdown where they process any events that are received from the\nevents endpoint before shutting down. This is also the reason that the events\nendpoint does not return any events when responding with a 202 blocking the\nresponse, because there would otherwise be a race condition where the events\nmight be lost in transit if the client shuts down. By always sending an empty\nresponse and requiring the client to send another request, we ensure that the\nclient is prepared to halt shutdown until the request had been fully processed.\n\n## Local processing optimization\n\nWhen possible, events will be processed locally before sent to the event bus.\nThe client will also inform the bus of which subscriptions have already been\nnotified of the event, so that the bus can completely avoid storing an event if\nit has already been fully consumed by all subscribers.\n\n## Automated cleanup & event window\n\nEvents are deleted once they are outside the guaranteed storage window. By\ndefault the window 10 minutes for all events, and 24 hours for the last 10000\nevents. This ensures that the event log doesn't grow indefinitely, while still\nallowing subscribers with restarts or outages to catch up to past events,\nensuring that events are likely not lost.\n\nSubscriptions are also cleaned up if their read pointer falls outside of the\ncurrent event window. This ensures that stale subscribers don't accumulate and\ncause unnecessary storage of events.\n\n*/\n\nasync function createEventBusStore(deps: {\n logger: LoggerService;\n database: DatabaseService;\n scheduler: SchedulerService;\n lifecycle: LifecycleService;\n httpAuth: HttpAuthService;\n}): Promise<EventBusStore> {\n const db = await deps.database.getClient();\n if (db.client.config.client === 'pg') {\n deps.logger.info('Database is PostgreSQL, using database store');\n return await DatabaseEventBusStore.create(deps);\n }\n\n deps.logger.info('Database is not PostgreSQL, using memory store');\n return new MemoryEventBusStore();\n}\n\n/**\n * Creates a new event bus router\n * @internal\n */\nexport async function createEventBusRouter(options: {\n logger: LoggerService;\n database: DatabaseService;\n scheduler: SchedulerService;\n lifecycle: LifecycleService;\n httpAuth: HttpAuthService;\n notifyTimeoutMs?: number; // for testing\n}): Promise<Handler> {\n const { httpAuth, notifyTimeoutMs = DEFAULT_NOTIFY_TIMEOUT_MS } = options;\n const logger = options.logger.child({ type: 'EventBus' });\n\n const store = await createEventBusStore(options);\n\n const apiRouter = await createOpenApiRouter();\n\n apiRouter.post('/bus/v1/events', async (req, res) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['service'],\n });\n const topic = req.body.event.topic;\n const notifiedSubscribers = req.body.notifiedSubscribers;\n const result = await store.publish({\n event: {\n topic,\n eventPayload: req.body.event.payload,\n } as EventParams,\n notifiedSubscribers,\n credentials,\n });\n if (result) {\n logger.debug(\n `Published event to '${topic}' with ID '${result.eventId}'`,\n {\n subject: credentials.principal.subject,\n },\n );\n res.status(201).end();\n } else {\n if (notifiedSubscribers) {\n const notified = `'${notifiedSubscribers.join(\"', '\")}'`;\n logger.debug(\n `Skipped publishing of event to '${topic}', subscribers have already been notified: ${notified}`,\n { subject: credentials.principal.subject },\n );\n } else {\n logger.debug(\n `Skipped publishing of event to '${topic}', no subscribers present`,\n { subject: credentials.principal.subject },\n );\n }\n res.status(204).end();\n }\n });\n\n apiRouter.get(\n '/bus/v1/subscriptions/:subscriptionId/events',\n async (req, res) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['service'],\n });\n const id = req.params.subscriptionId;\n\n const controller = new AbortController();\n req.on('end', () => controller.abort());\n\n // By setting up the listener first we make sure we don't miss any events\n // that are published while reading. If an event is published we'll receive\n // a notification, which we may ignore depending on the outcome of the read\n const listener = await store.setupListener(id, {\n signal: controller.signal,\n });\n\n // By timing out requests we make sure they don't stall or that events get stuck.\n // For the caller there's no difference between a timeout and a\n // notification, either way they should try reading again.\n const timeout = setTimeout(() => {\n controller.abort();\n }, notifyTimeoutMs);\n\n try {\n const { events } = await store.readSubscription(id);\n\n logger.debug(\n `Reading subscription '${id}' resulted in ${events.length} events`,\n { subject: credentials.principal.subject },\n );\n\n if (events.length > 0) {\n res.json({\n events: events.map(event => ({\n topic: event.topic,\n payload: event.eventPayload,\n })),\n });\n } else {\n res.setHeader(\n EVENTS_NOTIFY_TIMEOUT_HEADER,\n notifyTimeoutMs.toString(),\n );\n res.status(202);\n res.flushHeaders();\n\n try {\n const { topic } = await listener.waitForUpdate();\n logger.debug(\n `Received notification for subscription '${id}' for topic '${topic}'`,\n { subject: credentials.principal.subject },\n );\n } catch (error) {\n if (error !== controller.signal.reason) {\n logger.error(`Error listening for subscription '${id}'`, error);\n }\n } finally {\n // A small extra delay ensures a more even spread of events across\n // consumers in case some consumers are faster than others\n await new Promise(resolve =>\n setTimeout(resolve, 1 + Math.random() * 9),\n );\n res.end();\n }\n }\n } finally {\n controller.abort();\n clearTimeout(timeout);\n }\n },\n );\n\n apiRouter.put('/bus/v1/subscriptions/:subscriptionId', async (req, res) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['service'],\n });\n const id = req.params.subscriptionId;\n\n await store.upsertSubscription(id, req.body.topics, credentials);\n\n logger.debug(\n `New subscription '${id}' for topics '${req.body.topics.join(\"', '\")}'`,\n { subject: credentials.principal.subject },\n );\n\n res.status(201).end();\n });\n\n return apiRouter;\n}\n"],"names":["DatabaseEventBusStore","MemoryEventBusStore","createOpenApiRouter","EVENTS_NOTIFY_TIMEOUT_HEADER"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createEventBusRouter.cjs.js","sources":["../../../src/service/hub/createEventBusRouter.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 DatabaseService,\n HttpAuthService,\n LifecycleService,\n LoggerService,\n SchedulerService,\n} from '@backstage/backend-plugin-api';\nimport { Handler } from 'express';\nimport { createOpenApiRouter } from '../../schema/openapi';\nimport { MemoryEventBusStore } from './MemoryEventBusStore';\nimport { DatabaseEventBusStore } from './DatabaseEventBusStore';\nimport { EventBusStore } from './types';\nimport {\n EVENTS_NOTIFY_TIMEOUT_HEADER,\n EventParams,\n} from '@backstage/plugin-events-node';\n\nconst DEFAULT_NOTIFY_TIMEOUT_MS = 55_000; // Just below 60s, which is a common HTTP timeout\n\n/*\n\n# Event Bus\n\nThis comment describes the event bus that is implemented here in the events\nbackend, and by default used by the events service.\n\n## Overview\n\nThe events bus implements a subscription mechanism where subscribers must exist\nupfront for events to be stored. It uses a single inbox for all events, with\neach subscriber having its own pointer for how far into the inbox it has read.\n\nIn order to avoid busy polling, the API uses a long-polling mechanism where a\nrequest is left hanging until the client should try to read again.\n\nThe event bus is not implemented with any guarantees of events being consumed,\nbut it does aim to make it unlikely that events are dropped\n\n## API\n\n### POST /bus/v1/events\n\nThis endpoint is used to publish new events to the event bus on a specific\ntopic. It can optionally include a set of subscription IDs for subscribers that\nhave already been notified of the event. This is to enable an optimization where\nwe notify subscribers locally if possible, and avoid the need for events to be\nrelayed through the events bus at all of possible.\n\nFor an event to be published and stored there must already exist a subscriber\nthat is subscribed to the event's topic, and that hasn't already been notified\nof the event. If no such subscriber is found, the event will be discarded.\n\n### PUT /bus/v1/subscriptions/:subscriptionId\n\nThis endpoint is used to create or update a subscriptions. Subscriptions are\nshared across the entire bus and divided by subscription ID. Multiple clients\ncan be reading events from the same subscription at the same time, but only one\nof those clients will receive each event. This enables division of work by using\nthe same subscriber ID across multiple instances, as well as broadcasting by\nensuring separate subscribers IDs.\n\n### GET /bus/v1/subscriptions/:subscriptionId/events\n\nThis endpoint is used to read events from a subscription. It will return a batch\nof events for the subscribed topics that have not yet been read by the\nsubscription. If no such events are available, the endpoint will return a 202\nresponse and then hang end response until an event is available or a timeout is\nreached. This allows clients to call this endpoint in a loop but will keep\ntraffic overhead fairly low.\n\n## Delivery guarantees\n\nWhen reading events from the event bus, clients should always implement a\ngraceful shutdown where they process any events that are received from the\nevents endpoint before shutting down. This is also the reason that the events\nendpoint does not return any events when responding with a 202 blocking the\nresponse, because there would otherwise be a race condition where the events\nmight be lost in transit if the client shuts down. By always sending an empty\nresponse and requiring the client to send another request, we ensure that the\nclient is prepared to halt shutdown until the request had been fully processed.\n\n## Local processing optimization\n\nWhen possible, events will be processed locally before sent to the event bus.\nThe client will also inform the bus of which subscriptions have already been\nnotified of the event, so that the bus can completely avoid storing an event if\nit has already been fully consumed by all subscribers.\n\n## Automated cleanup & event window\n\nEvents are deleted once they are outside the guaranteed storage window. By\ndefault the window 10 minutes for all events, and 24 hours for the last 10000\nevents. This ensures that the event log doesn't grow indefinitely, while still\nallowing subscribers with restarts or outages to catch up to past events,\nensuring that events are likely not lost.\n\nSubscriptions are also cleaned up if their read pointer falls outside of the\ncurrent event window. This ensures that stale subscribers don't accumulate and\ncause unnecessary storage of events.\n\n*/\n\nasync function createEventBusStore(deps: {\n logger: LoggerService;\n database: DatabaseService;\n scheduler: SchedulerService;\n lifecycle: LifecycleService;\n httpAuth: HttpAuthService;\n}): Promise<EventBusStore> {\n const db = await deps.database.getClient();\n if (db.client.config.client === 'pg') {\n deps.logger.info('Database is PostgreSQL, using database store');\n return await DatabaseEventBusStore.create(deps);\n }\n\n deps.logger.info('Database is not PostgreSQL, using memory store');\n return new MemoryEventBusStore();\n}\n\n/**\n * Creates a new event bus router\n * @internal\n */\nexport async function createEventBusRouter(options: {\n logger: LoggerService;\n database: DatabaseService;\n scheduler: SchedulerService;\n lifecycle: LifecycleService;\n httpAuth: HttpAuthService;\n notifyTimeoutMs?: number; // for testing\n}): Promise<Handler> {\n const { httpAuth, notifyTimeoutMs = DEFAULT_NOTIFY_TIMEOUT_MS } = options;\n const logger = options.logger.child({ type: 'EventBus' });\n\n const store = await createEventBusStore(options);\n\n const apiRouter = await createOpenApiRouter();\n\n apiRouter.post('/bus/v1/events', async (req, res) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['service'],\n });\n const topic = req.body.event.topic;\n const notifiedSubscribers = req.body.notifiedSubscribers;\n const result = await store.publish({\n event: {\n topic,\n eventPayload: req.body.event.payload,\n } as EventParams,\n notifiedSubscribers,\n credentials,\n });\n if (result) {\n logger.debug(\n `Published event to '${topic}' with ID '${result.eventId}'`,\n {\n subject: credentials.principal.subject,\n },\n );\n res.status(201).end();\n } else {\n if (notifiedSubscribers) {\n const notified = `'${notifiedSubscribers.join(\"', '\")}'`;\n logger.debug(\n `Skipped publishing of event to '${topic}', subscribers have already been notified: ${notified}`,\n { subject: credentials.principal.subject },\n );\n } else {\n logger.debug(\n `Skipped publishing of event to '${topic}', no subscribers present`,\n { subject: credentials.principal.subject },\n );\n }\n res.status(204).end();\n }\n });\n\n apiRouter.get(\n '/bus/v1/subscriptions/:subscriptionId/events',\n async (req, res) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['service'],\n });\n const id = req.params.subscriptionId;\n\n const controller = new AbortController();\n req.on('end', () => controller.abort());\n\n // By setting up the listener first we make sure we don't miss any events\n // that are published while reading. If an event is published we'll receive\n // a notification, which we may ignore depending on the outcome of the read\n const listener = await store.setupListener(id, {\n signal: controller.signal,\n });\n\n // By timing out requests we make sure they don't stall or that events get stuck.\n // For the caller there's no difference between a timeout and a\n // notification, either way they should try reading again.\n const timeout = setTimeout(() => {\n controller.abort();\n }, notifyTimeoutMs);\n\n try {\n const { events } = await store.readSubscription(id);\n\n logger.debug(\n `Reading subscription '${id}' resulted in ${events.length} events`,\n { subject: credentials.principal.subject },\n );\n\n if (events.length > 0) {\n res.json({\n events: events.map(event => ({\n topic: event.topic,\n payload: event.eventPayload,\n })),\n });\n } else {\n res.setHeader(\n EVENTS_NOTIFY_TIMEOUT_HEADER,\n notifyTimeoutMs.toString(),\n );\n res.status(202);\n res.flushHeaders();\n\n try {\n const { topic } = await listener.waitForUpdate();\n logger.debug(\n `Received notification for subscription '${id}' for topic '${topic}'`,\n { subject: credentials.principal.subject },\n );\n } catch (error) {\n if (error !== controller.signal.reason) {\n logger.error(`Error listening for subscription '${id}'`, error);\n }\n } finally {\n // A small extra delay ensures a more even spread of events across\n // consumers in case some consumers are faster than others\n await new Promise(resolve =>\n setTimeout(resolve, 1 + Math.random() * 9),\n );\n res.end();\n }\n }\n } finally {\n controller.abort();\n clearTimeout(timeout);\n }\n },\n );\n\n apiRouter.put('/bus/v1/subscriptions/:subscriptionId', async (req, res) => {\n const credentials = await httpAuth.credentials(req, {\n allow: ['service'],\n });\n const id = req.params.subscriptionId;\n\n await store.upsertSubscription(id, req.body.topics, credentials);\n\n logger.debug(\n `New subscription '${id}' for topics '${req.body.topics.join(\"', '\")}'`,\n { subject: credentials.principal.subject },\n );\n\n res.status(201).end();\n });\n\n return apiRouter;\n}\n"],"names":["DatabaseEventBusStore","MemoryEventBusStore","createOpenApiRouter","EVENTS_NOTIFY_TIMEOUT_HEADER"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-events-backend",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "backend-plugin",
|
|
6
6
|
"pluginId": "events",
|
|
@@ -67,11 +67,11 @@
|
|
|
67
67
|
"test": "backstage-cli package test"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@backstage/backend-openapi-utils": "^0.
|
|
71
|
-
"@backstage/backend-plugin-api": "^1.4.
|
|
70
|
+
"@backstage/backend-openapi-utils": "^0.6.0",
|
|
71
|
+
"@backstage/backend-plugin-api": "^1.4.2",
|
|
72
72
|
"@backstage/config": "^1.3.3",
|
|
73
73
|
"@backstage/errors": "^1.2.7",
|
|
74
|
-
"@backstage/plugin-events-node": "^0.4.
|
|
74
|
+
"@backstage/plugin-events-node": "^0.4.14",
|
|
75
75
|
"@backstage/types": "^1.2.1",
|
|
76
76
|
"@types/express": "^4.17.6",
|
|
77
77
|
"content-type": "^1.0.5",
|
|
@@ -80,12 +80,12 @@
|
|
|
80
80
|
"knex": "^3.0.0"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@backstage/backend-app-api": "^1.2.
|
|
84
|
-
"@backstage/backend-defaults": "^0.
|
|
85
|
-
"@backstage/backend-test-utils": "^1.
|
|
86
|
-
"@backstage/cli": "^0.
|
|
87
|
-
"@backstage/plugin-events-backend-test-utils": "^0.1.
|
|
88
|
-
"@backstage/repo-tools": "^0.15.
|
|
83
|
+
"@backstage/backend-app-api": "^1.2.6",
|
|
84
|
+
"@backstage/backend-defaults": "^0.12.0",
|
|
85
|
+
"@backstage/backend-test-utils": "^1.8.0",
|
|
86
|
+
"@backstage/cli": "^0.34.0",
|
|
87
|
+
"@backstage/plugin-events-backend-test-utils": "^0.1.47",
|
|
88
|
+
"@backstage/repo-tools": "^0.15.1",
|
|
89
89
|
"@types/content-type": "^1.1.8",
|
|
90
90
|
"supertest": "^7.0.0"
|
|
91
91
|
},
|