@thisisagile/easy-express
Version:
Straightforward library for building domain-driven microservice architectures
238 lines (228 loc) • 10.6 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
AuthError: () => AuthError,
ExpressProvider: () => ExpressProvider,
NamespaceContext: () => NamespaceContext,
authError: () => authError,
checkLabCoat: () => checkLabCoat,
checkScope: () => checkScope,
checkToken: () => checkToken,
checkUseCase: () => checkUseCase,
correlation: () => correlation,
error: () => error,
isAuthError: () => isAuthError,
notFound: () => notFound,
requestContext: () => requestContext,
security: () => security,
service: () => service
});
module.exports = __toCommonJS(src_exports);
// src/express/AuthError.ts
var import_easy = require("@thisisagile/easy");
var AuthError = class extends Error {
status;
constructor({ name, status }) {
super(name);
this.name = "AuthenticationError";
this.status = status;
}
};
var authError = (status) => new AuthError(status);
var isAuthError = (e) => (0, import_easy.isError)(e) && e.name === "AuthenticationError";
// src/express/CorrelationHandler.ts
var import_easy2 = require("@thisisagile/easy");
var correlation = (req, res, next) => {
res.setHeader(import_easy2.HttpHeader.Correlation, import_easy2.ctx.request.correlationId = req?.header(import_easy2.HttpHeader.Correlation) ?? (0, import_easy2.toUuid)());
next();
};
// src/express/ErrorHandler.ts
var import_easy3 = require("@thisisagile/easy");
var import_easy_service = require("@thisisagile/easy-service");
var toResponse = (status, errors = []) => ({
status,
body: import_easy3.rest.toError(status, errors)
});
var toBody = ({ origin, options }) => {
return (0, import_easy3.choose)(origin).type(isAuthError, (ae) => toResponse((0, import_easy3.toHttpStatus)(ae.status), [(0, import_easy3.toResult)(ae.message)])).type(import_easy3.isDoesNotExist, (e) => toResponse(options?.onNotFound ?? import_easy3.HttpStatus.NotFound, [(0, import_easy3.toResult)(e.reason ?? e.message)])).type(import_easy3.isError, (e) => toResponse(import_easy3.HttpStatus.InternalServerError, [(0, import_easy3.toResult)(e.message)])).type(import_easy3.isResults, (r) => toResponse(options?.onError ?? import_easy3.HttpStatus.BadRequest, r.results)).type(import_easy3.isResponse, (r) => toResponse(import_easy3.HttpStatus.InternalServerError, r.body.error?.errors)).type(import_easy3.isException, (e) => toResponse(options?.onError ?? import_easy3.HttpStatus.BadRequest, [(0, import_easy3.toResult)(e.reason ?? e.message)])).type(import_easy3.isText, (t) => toResponse(options?.onError ?? import_easy3.HttpStatus.BadRequest, [(0, import_easy3.toResult)((0, import_easy3.asString)(t))])).else(() => toResponse(import_easy3.HttpStatus.InternalServerError, [(0, import_easy3.toResult)("Unknown error")]));
};
var error = (e, req, res, _next) => {
let response;
(0, import_easy3.tryTo)(() => (0, import_easy_service.toOriginatedError)(e)).map((oe) => toBody(oe)).accept((r) => response = r).accept((r) => import_easy3.ctx.request.lastError = r.status.isServerError ? r.body.error?.errors[0]?.message : void 0).accept((r) => import_easy3.ctx.request.lastErrorStack = r.status.isServerError ? e.stack : void 0).recover(() => response).accept((r) => res.status(r.status.status).json(r.body));
};
// src/express/ExpressProvider.ts
var import_express = __toESM(require("express"));
// src/express/SecurityHandler.ts
var import_passport = __toESM(require("passport"));
var import_passport_jwt = require("passport-jwt");
var import_easy4 = require("@thisisagile/easy");
var checkLabCoat = () => (req, res, next) => next((0, import_easy4.ifFalse)(import_easy4.Environment.Dev.equals(import_easy4.ctx.env.name), authError(import_easy4.HttpStatus.Forbidden)));
var checkToken = () => import_passport.default.authenticate("jwt", { session: false, failWithError: true });
var checkScope = (scope) => (req, res, next) => next((0, import_easy4.ifFalse)(req.user?.scopes?.includes(scope.id), authError(import_easy4.HttpStatus.Forbidden)));
var checkUseCase = (uc) => (req, res, next) => next((0, import_easy4.ifFalse)(req.user?.usecases?.includes(uc.id), authError(import_easy4.HttpStatus.Forbidden)));
var wrapSecretOrKeyProvider = (p) => p ? (request, rawJwtToken, done) => p(request, rawJwtToken).then((t) => done(null, t)).catch((e) => done(e)) : void 0;
var security = ({ jwtStrategyOptions } = {}) => {
jwtStrategyOptions ??= {};
if ("secretOrKeyProvider" in jwtStrategyOptions)
jwtStrategyOptions.secretOrKeyProvider = wrapSecretOrKeyProvider(jwtStrategyOptions.secretOrKeyProvider);
else if (!("secretOrKey" in jwtStrategyOptions))
jwtStrategyOptions.secretOrKey = import_easy4.ctx.env.get("tokenPublicKey");
const strategy = new import_passport_jwt.Strategy(
{
jwtFromRequest: import_passport_jwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
...jwtStrategyOptions
},
(req, payload, done) => {
import_easy4.ctx.request.token = payload;
import_easy4.ctx.request.jwt = import_passport_jwt.ExtractJwt.fromAuthHeaderAsBearerToken()(req) ?? "";
done(null, payload);
}
);
import_passport.default.use(strategy);
return import_passport.default.initialize();
};
// src/express/ExpressProvider.ts
var import_easy5 = require("@thisisagile/easy");
var import_easy_service2 = require("@thisisagile/easy-service");
var ExpressProvider = class {
constructor(app = (0, import_express.default)()) {
this.app = app;
this.app.set("trust proxy", ["loopback", "linklocal", "uniquelocal"]);
}
use = (handler) => {
this.app.use(handler);
};
route = (service2, resource) => {
const { route, endpoints, middleware } = (0, import_easy_service2.routes)(resource);
const router = import_express.default.Router({ mergeParams: true });
if (!(0, import_easy5.isEmpty)(middleware))
router.all(route.route(service2.name), middleware);
endpoints.forEach(({ endpoint, verb, requires, middleware: middleware2 }) => {
console.log(verb.verb.code, route.route(service2.name));
router[verb.verb.toString()](
route.route(service2.name),
...this.addSecurityMiddleware(requires),
...middleware2,
this.handle(endpoint, verb.options, requires)
);
});
this.app.use(router);
};
listen = (port, message = `Service is listening on port ${port}.`) => {
this.app.listen(port, () => {
console.log(message);
});
};
addSecurityMiddleware(requires) {
const middleware = [];
if (requires.labCoat)
middleware.push(checkLabCoat());
if (requires.token)
middleware.push(checkToken());
if (requires.scope)
middleware.push(checkScope(requires.scope));
if (requires.uc)
middleware.push(checkUseCase(requires.uc));
return middleware;
}
handle = (endpoint, options, requires) => (req, res, next) => endpoint((0, import_easy5.toReq)(req)).then((r) => this.toResponse(res, r, (0, import_easy_service2.toVerbOptions)(options))).catch((error2) => next((0, import_easy_service2.toOriginatedError)(error2, options)));
toResponse(res, result, options) {
res.status(options.onOk.status);
res.type(options.type.code);
if (options.cache.enabled)
res.setHeader(options.cache.name, options.cache.value());
(this[options.type.name] ?? this.json)(res, result, options);
}
// Handling responses depending on content type
json(res, result, options) {
if (import_easy5.HttpStatus.NoContent.equals(options.onOk)) {
res.send();
} else {
res.json(import_easy5.rest.toData(options.onOk, (0, import_easy5.toList)(result), result?.total, result?.meta));
}
}
rawJson(res, result, options) {
if (import_easy5.HttpStatus.NoContent.equals(options.onOk)) {
res.send();
} else {
res.json(result);
}
}
stream(res, result) {
res.end(result);
}
text(res, data) {
res.send(data);
}
};
var service = (name) => new import_easy_service2.Service(name, new ExpressProvider());
// src/express/NotFoundHandler.ts
var import_easy6 = require("@thisisagile/easy");
var import_easy_service3 = require("@thisisagile/easy-service");
var notFound = (req, res, next) => {
next((0, import_easy_service3.toOriginatedError)(import_easy6.Exception.DoesNotExist));
};
// src/express/RequestContextHandler.ts
var import_easy7 = require("@thisisagile/easy");
var requestContext = (req, res, next) => import_easy7.ctx.request.create(() => next());
// src/types/NamespaceContext.ts
var import_cls_hooked = require("cls-hooked");
var import_easy8 = require("@thisisagile/easy");
var NamespaceContext = class extends import_easy8.BaseRequestContext {
namespace = (0, import_cls_hooked.createNamespace)("context");
get(key) {
return this.namespace.get(key);
}
set(key, value) {
return this.namespace.set(key, value);
}
create = (f) => this.namespace.run(f);
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AuthError,
ExpressProvider,
NamespaceContext,
authError,
checkLabCoat,
checkScope,
checkToken,
checkUseCase,
correlation,
error,
isAuthError,
notFound,
requestContext,
security,
service
});
//# sourceMappingURL=index.js.map