@thisisagile/easy-service 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +282 -0
  2. package/dist/chunk-2XMYNSTI.mjs +11 -0
  3. package/dist/chunk-2XMYNSTI.mjs.map +1 -0
  4. package/dist/chunk-4N72FQFX.mjs +16 -0
  5. package/dist/chunk-4N72FQFX.mjs.map +1 -0
  6. package/dist/chunk-FYPLZQRO.mjs +39 -0
  7. package/dist/chunk-FYPLZQRO.mjs.map +1 -0
  8. package/dist/chunk-W62PBGRF.mjs +31 -0
  9. package/dist/chunk-W62PBGRF.mjs.map +1 -0
  10. package/dist/health/HealthResource.d.ts +5 -0
  11. package/dist/health/HealthResource.mjs +28 -0
  12. package/dist/health/HealthResource.mjs.map +1 -0
  13. package/dist/health/HealthUri.d.ts +5 -0
  14. package/dist/health/HealthUri.mjs +8 -0
  15. package/dist/health/HealthUri.mjs.map +1 -0
  16. package/dist/http/OriginatedError.d.ts +9 -0
  17. package/dist/http/OriginatedError.mjs +21 -0
  18. package/dist/http/OriginatedError.mjs.map +1 -0
  19. package/dist/http/Verb.d.ts +20 -0
  20. package/dist/http/Verb.mjs +22 -0
  21. package/dist/http/Verb.mjs.map +1 -0
  22. package/dist/index.d.ts +10 -0
  23. package/dist/index.js +225 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/index.mjs +11 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/resources/AppProvider.d.ts +9 -0
  28. package/dist/resources/AppProvider.mjs +1 -0
  29. package/dist/resources/AppProvider.mjs.map +1 -0
  30. package/dist/resources/Requires.d.ts +8 -0
  31. package/dist/resources/Requires.mjs +26 -0
  32. package/dist/resources/Requires.mjs.map +1 -0
  33. package/dist/resources/Resource.d.ts +1 -0
  34. package/dist/resources/Resource.mjs +1 -0
  35. package/dist/resources/Resource.mjs.map +1 -0
  36. package/dist/resources/Route.d.ts +24 -0
  37. package/dist/resources/Route.mjs +10 -0
  38. package/dist/resources/Route.mjs.map +1 -0
  39. package/dist/resources/Service.d.ts +15 -0
  40. package/dist/resources/Service.mjs +28 -0
  41. package/dist/resources/Service.mjs.map +1 -0
  42. package/dist/security/Jwt.d.ts +13 -0
  43. package/dist/security/Jwt.mjs +23 -0
  44. package/dist/security/Jwt.mjs.map +1 -0
  45. package/package.json +44 -0
  46. package/src/health/HealthResource.ts +11 -0
  47. package/src/health/HealthUri.ts +6 -0
  48. package/src/http/OriginatedError.ts +17 -0
  49. package/src/http/Verb.ts +32 -0
  50. package/src/index.ts +10 -0
  51. package/src/resources/AppProvider.ts +11 -0
  52. package/src/resources/Requires.ts +32 -0
  53. package/src/resources/Resource.ts +1 -0
  54. package/src/resources/Route.ts +53 -0
  55. package/src/resources/Service.ts +34 -0
  56. package/src/security/Jwt.ts +32 -0
@@ -0,0 +1,32 @@
1
+ import { meta } from '@thisisagile/easy';
2
+ import { Scope, UseCase } from '@thisisagile/easy';
3
+
4
+ export class Requires {
5
+ readonly labCoat =
6
+ (): PropertyDecorator =>
7
+ (subject: unknown, property: string | symbol): void => {
8
+ meta(subject).property(property).set('labCoat', true);
9
+ };
10
+
11
+ readonly token =
12
+ (): PropertyDecorator =>
13
+ (subject: unknown, property: string | symbol): void => {
14
+ meta(subject).property(property).set('token', true);
15
+ };
16
+
17
+ readonly scope =
18
+ (scope: Scope): PropertyDecorator =>
19
+ (subject: unknown, property: string | symbol): void => {
20
+ meta(subject).property(property).set('token', true);
21
+ meta(subject).property(property).set('scope', scope);
22
+ };
23
+
24
+ readonly useCase =
25
+ (uc: UseCase): PropertyDecorator =>
26
+ (subject: unknown, property: string | symbol): void => {
27
+ meta(subject).property(property).set('token', true);
28
+ meta(subject).property(property).set('uc', uc);
29
+ };
30
+ }
31
+
32
+ export const requires = new Requires();
@@ -0,0 +1 @@
1
+ export type Resource = any;
@@ -0,0 +1,53 @@
1
+ import { List, meta, Optional, tryTo, Uri, Scope, UseCase, Req } from '@thisisagile/easy';
2
+ import { Resource } from './Resource';
3
+ import { RequestHandler } from 'express';
4
+ import { Verb } from '../http/Verb';
5
+
6
+ export const route =
7
+ (uri: Uri): ClassDecorator =>
8
+ (subject: unknown): void => {
9
+ meta(subject).set('route', uri);
10
+ };
11
+
12
+ export type Endpoint<T = unknown> = (re: Req) => Promise<T | List<T>>;
13
+ export type RouteRequires = { token: boolean; labCoat: boolean; scope?: Scope; uc?: UseCase };
14
+ export type Route = { verb: Verb; endpoint: Endpoint; requires: RouteRequires; middleware: RequestHandler[] };
15
+ export type Routes = { route: Uri; middleware: RequestHandler[]; endpoints: List<Route> };
16
+
17
+ const toRoute = (endpoint: Endpoint, requires: RouteRequires, verb?: Verb, middleware?: RequestHandler[]): Optional<Route> =>
18
+ tryTo(verb)
19
+ .is.defined()
20
+ .map(verb => ({ verb, endpoint, requires, middleware: middleware ?? [] }) as Route)
21
+ .orElse();
22
+
23
+ class Router implements Routes {
24
+ constructor(readonly resource: Resource) {}
25
+
26
+ get route(): Uri {
27
+ return meta(this.resource).get('route');
28
+ }
29
+
30
+ get middleware(): RequestHandler[] {
31
+ return meta(this.resource).get<RequestHandler[]>('middleware') ?? [];
32
+ }
33
+
34
+ get endpoints(): List<Route> {
35
+ return meta(this.resource)
36
+ .properties('verb')
37
+ .mapDefined(v =>
38
+ toRoute(
39
+ this.resource[v.property],
40
+ {
41
+ labCoat: v.get<boolean>('labCoat') ?? false,
42
+ token: v.get<boolean>('token') ?? false,
43
+ scope: v.get<Scope>('scope'),
44
+ uc: v.get<UseCase>('uc'),
45
+ },
46
+ v.get<Verb>('verb'),
47
+ v.get<RequestHandler[]>('middleware')
48
+ )
49
+ );
50
+ }
51
+ }
52
+
53
+ export const routes = (resource: Resource): Routes => new Router(resource);
@@ -0,0 +1,34 @@
1
+ import { AppProvider, Handler } from './AppProvider';
2
+ import { Constructor, Enum, List, toList, tryTo } from '@thisisagile/easy';
3
+ import { Resource } from './Resource';
4
+
5
+ export class Service extends Enum {
6
+ protected port = 8080;
7
+
8
+ constructor(
9
+ readonly name: string,
10
+ protected app: AppProvider,
11
+ protected resources: List<Resource> = toList()
12
+ ) {
13
+ super(name);
14
+ }
15
+
16
+ pre = (): Handler[] => [];
17
+ post = (): Handler[] => [];
18
+
19
+ with(...resources: Constructor<Resource>[]): this {
20
+ return tryTo(this).accept(t => t.resources.add(resources.map(r => new r()))).value;
21
+ }
22
+
23
+ atPort(port: number): this {
24
+ return tryTo(this).accept(t => (t.port = port)).value;
25
+ }
26
+
27
+ start(message = `Service ${this.name} listening on port ${this.port} with ${this.resources.length} resources.`): void {
28
+ tryTo(this)
29
+ .accept(t => t.pre().forEach(h => this.app.use(h)))
30
+ .accept(t => t.resources.forEach(r => this.app.route(this, r)))
31
+ .accept(t => t.post().forEach(h => this.app.use(h)))
32
+ .accept(t => t.app.listen(this.port, message));
33
+ }
34
+ }
@@ -0,0 +1,32 @@
1
+ import { Algorithm, sign, verify } from 'jsonwebtoken';
2
+ import { ctx, Json, OneOrMore, Optional, tryTo, Validatable, Jwt as JwtBase } from '@thisisagile/easy';
3
+
4
+ interface SignOptions {
5
+ audience?: Optional<OneOrMore<string>>;
6
+ issuer?: Optional<string>;
7
+ }
8
+ export class Jwt extends JwtBase implements Validatable {
9
+ static of = (a: { jwt: string }): Jwt => new Jwt(a.jwt);
10
+
11
+ get isValid(): boolean {
12
+ return (
13
+ tryTo(() => ctx.env.get('tokenPublicKey') ?? '')
14
+ .map(key => verify(this.value, key))
15
+ .map(() => true)
16
+ .orElse() ?? false
17
+ );
18
+ }
19
+
20
+ static sign = (token: Json, options?: SignOptions): Jwt =>
21
+ tryTo(() => ctx.env.get('tokenPrivateKey') ?? '')
22
+ .is.not.empty()
23
+ .map(key =>
24
+ sign(token, key, {
25
+ ...options,
26
+ expiresIn: ctx.env.get('tokenExpiresIn') ?? '1h',
27
+ keyid: ctx.env.get('tokenKeyid') ?? 'easy',
28
+ algorithm: ctx.env.get('tokenAlgorithm', 'RS256') as Algorithm,
29
+ })
30
+ )
31
+ .map(s => new Jwt(s)).value;
32
+ }