@backstage/backend-defaults
Version:
Backend defaults used by Backstage backend apps
117 lines (111 loc) • 4.28 kB
JavaScript
;
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