UNPKG

@backstage/backend-defaults

Version:

Backend defaults used by Backstage backend apps

117 lines (111 loc) 4.28 kB
'use strict'; var Router = require('express-promise-router'); var express = require('express'); var zod = require('zod'); var zodToJsonSchema = require('zod-to-json-schema'); var errors = require('@backstage/errors'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var Router__default = /*#__PURE__*/_interopDefaultCompat(Router); var zodToJsonSchema__default = /*#__PURE__*/_interopDefaultCompat(zodToJsonSchema); class DefaultActionsRegistryService { actions = /* @__PURE__ */ new Map(); logger; httpAuth; auth; metadata; constructor(logger, httpAuth, auth, metadata) { this.logger = logger; this.httpAuth = httpAuth; this.auth = auth; this.metadata = metadata; } static create({ httpAuth, logger, auth, metadata }) { return new DefaultActionsRegistryService(logger, httpAuth, auth, metadata); } createRouter() { const router = Router__default.default(); router.use(express.json()); router.get("/.backstage/actions/v1/actions", (_, res) => { return res.json({ actions: Array.from(this.actions.entries()).map(([id, action]) => ({ id, ...action, attributes: { // Inspired by the @modelcontextprotocol/sdk defaults for the hints. // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812 destructive: action.attributes?.destructive ?? true, idempotent: action.attributes?.idempotent ?? false, readOnly: action.attributes?.readOnly ?? false }, schema: { input: action.schema?.input ? zodToJsonSchema__default.default(action.schema.input(zod.z)) : zodToJsonSchema__default.default(zod.z.object({})), output: action.schema?.output ? zodToJsonSchema__default.default(action.schema.output(zod.z)) : zodToJsonSchema__default.default(zod.z.object({})) } })) }); }); router.post( "/.backstage/actions/v1/actions/:actionId/invoke", async (req, res) => { const credentials = await this.httpAuth.credentials(req); if (this.auth.isPrincipal(credentials, "user")) { if (!credentials.principal.actor) { throw new errors.NotAllowedError( `Actions must be invoked by a service, not a user` ); } } else if (this.auth.isPrincipal(credentials, "none")) { throw new errors.NotAllowedError( `Actions must be invoked by a service, not an anonymous request` ); } const action = this.actions.get(req.params.actionId); if (!action) { throw new errors.NotFoundError(`Action "${req.params.actionId}" not found`); } const input = action.schema?.input ? action.schema.input(zod.z).safeParse(req.body) : { success: true, data: void 0 }; if (!input.success) { throw new errors.InputError( `Invalid input to action "${req.params.actionId}"`, input.error ); } try { const result = await action.action({ input: input.data, credentials, logger: this.logger }); const output = action.schema?.output ? action.schema.output(zod.z).safeParse(result?.output) : { success: true, data: result?.output }; if (!output.success) { throw new errors.InputError( `Invalid output from action "${req.params.actionId}"`, output.error ); } res.json({ output: output.data }); } catch (error) { throw new errors.ForwardedError( `Failed execution of action "${req.params.actionId}"`, error ); } } ); return router; } register(options) { const id = `${this.metadata.getId()}:${options.name}`; if (this.actions.has(id)) { throw new Error(`Action with id "${id}" is already registered`); } this.actions.set(id, options); } } exports.DefaultActionsRegistryService = DefaultActionsRegistryService; //# sourceMappingURL=DefaultActionsRegistryService.cjs.js.map