@ddd-ts-kit/http 0.2.0 → 0.4.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 (133) hide show
  1. package/README.md +81 -0
  2. package/dist/controller/controller-class.d.ts +7 -0
  3. package/dist/controller/controller-class.d.ts.map +1 -0
  4. package/dist/controller/controller-class.js +2 -0
  5. package/dist/controller/controller-class.js.map +1 -0
  6. package/dist/controller/controller-request.d.ts +11 -0
  7. package/dist/controller/controller-request.d.ts.map +1 -0
  8. package/dist/controller/controller-request.js +2 -0
  9. package/dist/controller/controller-request.js.map +1 -0
  10. package/dist/controller/controller-response.d.ts +10 -0
  11. package/dist/controller/controller-response.d.ts.map +1 -0
  12. package/dist/controller/controller-response.js +2 -0
  13. package/dist/controller/controller-response.js.map +1 -0
  14. package/dist/controller/controller-result.d.ts +7 -0
  15. package/dist/controller/controller-result.d.ts.map +1 -0
  16. package/dist/controller/controller-result.js +2 -0
  17. package/dist/controller/controller-result.js.map +1 -0
  18. package/dist/controller/controller-route.d.ts +7 -0
  19. package/dist/controller/controller-route.d.ts.map +1 -0
  20. package/dist/controller/controller-route.js +10 -0
  21. package/dist/controller/controller-route.js.map +1 -0
  22. package/dist/controller/controller-routes-options.d.ts +19 -0
  23. package/dist/controller/controller-routes-options.d.ts.map +1 -0
  24. package/dist/controller/controller-routes-options.js +2 -0
  25. package/dist/controller/controller-routes-options.js.map +1 -0
  26. package/dist/controller/controller-routes.d.ts +11 -0
  27. package/dist/controller/controller-routes.d.ts.map +1 -0
  28. package/dist/controller/controller-routes.js +72 -0
  29. package/dist/controller/controller-routes.js.map +1 -0
  30. package/dist/controller/controller-spec.d.ts +30 -0
  31. package/dist/controller/controller-spec.d.ts.map +1 -0
  32. package/dist/controller/controller-spec.js +2 -0
  33. package/dist/controller/controller-spec.js.map +1 -0
  34. package/dist/controller/controller.d.ts +20 -0
  35. package/dist/controller/controller.d.ts.map +1 -0
  36. package/dist/controller/controller.js +30 -0
  37. package/dist/controller/controller.js.map +1 -0
  38. package/dist/controller/declared-route.d.ts +13 -0
  39. package/dist/controller/declared-route.d.ts.map +1 -0
  40. package/dist/controller/declared-route.js +54 -0
  41. package/dist/controller/declared-route.js.map +1 -0
  42. package/dist/controller/erased-controller.d.ts +9 -0
  43. package/dist/controller/erased-controller.d.ts.map +1 -0
  44. package/dist/controller/erased-controller.js +2 -0
  45. package/dist/controller/erased-controller.js.map +1 -0
  46. package/dist/controller/guard-documentation.d.ts +10 -0
  47. package/dist/controller/guard-documentation.d.ts.map +1 -0
  48. package/dist/controller/guard-documentation.js +2 -0
  49. package/dist/controller/guard-documentation.js.map +1 -0
  50. package/dist/controller/guard-reference.d.ts +4 -0
  51. package/dist/controller/guard-reference.d.ts.map +1 -0
  52. package/dist/controller/guard-reference.js +2 -0
  53. package/dist/controller/guard-reference.js.map +1 -0
  54. package/dist/controller/guard-request.d.ts +20 -0
  55. package/dist/controller/guard-request.d.ts.map +1 -0
  56. package/dist/controller/guard-request.js +2 -0
  57. package/dist/controller/guard-request.js.map +1 -0
  58. package/dist/controller/guard.d.ts +21 -0
  59. package/dist/controller/guard.d.ts.map +1 -0
  60. package/dist/controller/guard.js +10 -0
  61. package/dist/controller/guard.js.map +1 -0
  62. package/dist/controller/guards/authenticated.d.ts +4 -0
  63. package/dist/controller/guards/authenticated.d.ts.map +1 -0
  64. package/dist/controller/guards/authenticated.js +12 -0
  65. package/dist/controller/guards/authenticated.js.map +1 -0
  66. package/dist/controller/guards/has-role.d.ts +8 -0
  67. package/dist/controller/guards/has-role.d.ts.map +1 -0
  68. package/dist/controller/guards/has-role.js +26 -0
  69. package/dist/controller/guards/has-role.js.map +1 -0
  70. package/dist/controller/guards/public.d.ts +7 -0
  71. package/dist/controller/guards/public.d.ts.map +1 -0
  72. package/dist/controller/guards/public.js +11 -0
  73. package/dist/controller/guards/public.js.map +1 -0
  74. package/dist/controller/http-controller.d.ts +14 -0
  75. package/dist/controller/http-controller.d.ts.map +1 -0
  76. package/dist/controller/http-controller.js +2 -0
  77. package/dist/controller/http-controller.js.map +1 -0
  78. package/dist/controller/http-request.d.ts +27 -0
  79. package/dist/controller/http-request.d.ts.map +1 -0
  80. package/dist/controller/http-request.js +2 -0
  81. package/dist/controller/http-request.js.map +1 -0
  82. package/dist/controller/presentation-registry.d.ts +10 -0
  83. package/dist/controller/presentation-registry.d.ts.map +1 -0
  84. package/dist/controller/presentation-registry.js +21 -0
  85. package/dist/controller/presentation-registry.js.map +1 -0
  86. package/dist/controller/request-mapper.d.ts +12 -0
  87. package/dist/controller/request-mapper.d.ts.map +1 -0
  88. package/dist/controller/request-mapper.js +6 -0
  89. package/dist/controller/request-mapper.js.map +1 -0
  90. package/dist/controller/resolved-controller.d.ts +18 -0
  91. package/dist/controller/resolved-controller.d.ts.map +1 -0
  92. package/dist/controller/resolved-controller.js +47 -0
  93. package/dist/controller/resolved-controller.js.map +1 -0
  94. package/dist/controller/route-group-class.d.ts +3 -0
  95. package/dist/controller/route-group-class.d.ts.map +1 -0
  96. package/dist/controller/route-group-class.js +2 -0
  97. package/dist/controller/route-group-class.js.map +1 -0
  98. package/dist/controller/route-group-options.d.ts +14 -0
  99. package/dist/controller/route-group-options.d.ts.map +1 -0
  100. package/dist/controller/route-group-options.js +2 -0
  101. package/dist/controller/route-group-options.js.map +1 -0
  102. package/dist/controller/route-group.d.ts +10 -0
  103. package/dist/controller/route-group.d.ts.map +1 -0
  104. package/dist/controller/route-group.js +25 -0
  105. package/dist/controller/route-group.js.map +1 -0
  106. package/dist/controller/serializer.d.ts +11 -0
  107. package/dist/controller/serializer.d.ts.map +1 -0
  108. package/dist/controller/serializer.js +6 -0
  109. package/dist/controller/serializer.js.map +1 -0
  110. package/dist/error/problem.d.ts +3 -0
  111. package/dist/error/problem.d.ts.map +1 -1
  112. package/dist/error/to-problem.d.ts.map +1 -1
  113. package/dist/error/to-problem.js +43 -2
  114. package/dist/error/to-problem.js.map +1 -1
  115. package/dist/index.d.ts +24 -0
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +10 -0
  118. package/dist/index.js.map +1 -1
  119. package/dist/openapi/problem-schema.d.ts.map +1 -1
  120. package/dist/openapi/problem-schema.js +5 -0
  121. package/dist/openapi/problem-schema.js.map +1 -1
  122. package/dist/pipeline/execute-route.js +1 -0
  123. package/dist/pipeline/execute-route.js.map +1 -1
  124. package/dist/route/define-route.d.ts.map +1 -1
  125. package/dist/route/define-route.js +2 -2
  126. package/dist/route/define-route.js.map +1 -1
  127. package/dist/route/request-context.d.ts +3 -0
  128. package/dist/route/request-context.d.ts.map +1 -1
  129. package/dist/route/route-path-pattern.d.ts +3 -0
  130. package/dist/route/route-path-pattern.d.ts.map +1 -0
  131. package/dist/route/route-path-pattern.js +3 -0
  132. package/dist/route/route-path-pattern.js.map +1 -0
  133. package/package.json +3 -3
@@ -0,0 +1,20 @@
1
+ import type { Actor, Session } from '@ddd-ts-kit/kernel';
2
+ import type { HttpMethod } from '../route/http-method.js';
3
+ import type { RawHeaders } from '../route/raw-headers.js';
4
+ import type { RequestContext } from '../route/request-context.js';
5
+ /** What a guard reads to let a request through or refuse it: who calls, and which route. */
6
+ export interface GuardRequest {
7
+ readonly method: HttpMethod;
8
+ /** The template of the route, the prefixes of its groups included: `/practices/:practiceId/agenda`. */
9
+ readonly path: string;
10
+ readonly operationId: string;
11
+ /** The path parameters, through the schema of the controller when it declares one. */
12
+ readonly params: Readonly<Record<string, unknown>>;
13
+ /** The headers as they arrived, before any schema. */
14
+ readonly headers: RawHeaders;
15
+ readonly actor: Actor;
16
+ /** Absent when the application resolves no session. */
17
+ readonly session?: Session;
18
+ readonly context: RequestContext;
19
+ }
20
+ //# sourceMappingURL=guard-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-request.d.ts","sourceRoot":"","sources":["../../src/controller/guard-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;CAClC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=guard-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-request.js","sourceRoot":"","sources":["../../src/controller/guard-request.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import { type ClassDeclarator, type DeclaredDependencies, type DependenciesFromSignature } from '@ddd-ts-kit/kernel/declarations';
2
+ import type { GuardDocumentation } from './guard-documentation.js';
3
+ import type { GuardRequest } from './guard-request.js';
4
+ /**
5
+ * A control at the edge, run in order before the controller: is the caller authenticated, does
6
+ * it hold a role, does the key of the request exist. It lets the request through by returning
7
+ * and refuses it by throwing `UnauthorizedError` or `ForbiddenError`, answered as a problem like
8
+ * any refusal. It knows the caller and the route, never an aggregate: a right that depends on
9
+ * the element is a permission of the use case, held for every caller of the use case.
10
+ */
11
+ export interface Guard {
12
+ readonly documents?: GuardDocumentation;
13
+ check(request: GuardRequest): void | Promise<void>;
14
+ }
15
+ /**
16
+ * Declares a guard that takes dependencies, so the composition root builds it from its
17
+ * constructor: `@Guard() export class ApiKeyGuard implements Guard`. A guard that needs nothing
18
+ * is a plain value (`Authenticated`, `HasRole('auditor')`) and carries no decorator.
19
+ */
20
+ export declare function Guard<const D extends DeclaredDependencies = DependenciesFromSignature>(dependencies?: D): ClassDeclarator<Guard, D>;
21
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/controller/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC/B,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAExC,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,oBAAoB,GAAG,yBAAyB,EACpF,YAAY,CAAC,EAAE,CAAC,GACf,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,CAE3B"}
@@ -0,0 +1,10 @@
1
+ import { Edge, } from '@ddd-ts-kit/kernel/declarations';
2
+ /**
3
+ * Declares a guard that takes dependencies, so the composition root builds it from its
4
+ * constructor: `@Guard() export class ApiKeyGuard implements Guard`. A guard that needs nothing
5
+ * is a plain value (`Authenticated`, `HasRole('auditor')`) and carries no decorator.
6
+ */
7
+ export function Guard(dependencies) {
8
+ return Edge('guard', dependencies);
9
+ }
10
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/controller/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,GAIL,MAAM,iCAAiC,CAAC;AAiBzC;;;;GAIG;AACH,MAAM,UAAU,KAAK,CACnB,YAAgB;IAEhB,OAAO,IAAI,CAAW,OAAO,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Guard } from '../guard.js';
2
+ /** Lets through every caller the application authenticated, whoever it is; answers 401 to the anonymous one. */
3
+ export declare const Authenticated: Guard;
4
+ //# sourceMappingURL=authenticated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticated.d.ts","sourceRoot":"","sources":["../../../src/controller/guards/authenticated.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,gHAAgH;AAChH,eAAO,MAAM,aAAa,EAAE,KAO3B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { isAnonymous } from '@ddd-ts-kit/kernel';
2
+ import { UnauthorizedError } from '../../error/unauthorized-error.js';
3
+ /** Lets through every caller the application authenticated, whoever it is; answers 401 to the anonymous one. */
4
+ export const Authenticated = {
5
+ documents: { errors: ['UNAUTHORIZED'] },
6
+ check({ actor }) {
7
+ if (isAnonymous(actor)) {
8
+ throw new UnauthorizedError('Authentication is required');
9
+ }
10
+ },
11
+ };
12
+ //# sourceMappingURL=authenticated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticated.js","sourceRoot":"","sources":["../../../src/controller/guards/authenticated.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,gHAAgH;AAChH,MAAM,CAAC,MAAM,aAAa,GAAU;IAClC,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,cAAc,CAAC,EAAE;IACvC,KAAK,CAAC,EAAE,KAAK,EAAE;QACb,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,iBAAiB,CAAC,4BAA4B,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Guard } from '../guard.js';
2
+ /**
3
+ * Lets through a caller that holds one of the roles, as its `Actor` carries them: 401 to the
4
+ * anonymous caller, 403 to one that holds none. A coarse control over a set of routes; what a
5
+ * role may do to one element stays a permission of the use case.
6
+ */
7
+ export declare function HasRole(...roles: readonly string[]): Guard;
8
+ //# sourceMappingURL=has-role.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"has-role.d.ts","sourceRoot":"","sources":["../../../src/controller/guards/has-role.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAiB1D"}
@@ -0,0 +1,26 @@
1
+ import { isAnonymous } from '@ddd-ts-kit/kernel';
2
+ import { DeclarationError } from '@ddd-ts-kit/kernel/declarations';
3
+ import { ForbiddenError } from '../../error/forbidden-error.js';
4
+ import { UnauthorizedError } from '../../error/unauthorized-error.js';
5
+ /**
6
+ * Lets through a caller that holds one of the roles, as its `Actor` carries them: 401 to the
7
+ * anonymous caller, 403 to one that holds none. A coarse control over a set of routes; what a
8
+ * role may do to one element stays a permission of the use case.
9
+ */
10
+ export function HasRole(...roles) {
11
+ if (roles.length === 0) {
12
+ throw new DeclarationError('HasRole() names no role: name at least one, or use Authenticated.');
13
+ }
14
+ return {
15
+ documents: { errors: ['UNAUTHORIZED', 'FORBIDDEN'] },
16
+ check({ actor }) {
17
+ if (isAnonymous(actor)) {
18
+ throw new UnauthorizedError('Authentication is required');
19
+ }
20
+ if (!roles.some((role) => actor.roles.includes(role))) {
21
+ throw new ForbiddenError('A role this route requires is missing');
22
+ }
23
+ },
24
+ };
25
+ }
26
+ //# sourceMappingURL=has-role.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"has-role.js","sourceRoot":"","sources":["../../../src/controller/guards/has-role.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,GAAG,KAAwB;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,gBAAgB,CAAC,mEAAmE,CAAC,CAAC;IAClG,CAAC;IAED,OAAO;QACL,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC,EAAE;QACpD,KAAK,CAAC,EAAE,KAAK,EAAE;YACb,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,iBAAiB,CAAC,4BAA4B,CAAC,CAAC;YAC5D,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBACtD,MAAM,IAAI,cAAc,CAAC,uCAAuC,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Guard } from '../guard.js';
2
+ /**
3
+ * Lets everybody through, the anonymous caller included. Said, never implied: a controller
4
+ * with no group and no guard is refused, and this guard stands alone, never next to another.
5
+ */
6
+ export declare const Public: Guard;
7
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/controller/guards/public.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,KAKpB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Lets everybody through, the anonymous caller included. Said, never implied: a controller
3
+ * with no group and no guard is refused, and this guard stands alone, never next to another.
4
+ */
5
+ export const Public = {
6
+ documents: { public: true },
7
+ check() {
8
+ return undefined;
9
+ },
10
+ };
11
+ //# sourceMappingURL=public.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/controller/guards/public.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAU;IAC3B,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3B,KAAK;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;CACF,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Schema } from '@ddd-ts-kit/validation';
2
+ import type { ControllerRequest } from './controller-request.js';
3
+ import type { ControllerSpec } from './controller-spec.js';
4
+ /**
5
+ * The shape `@Controller(spec)` requires of the class it decorates. `handle` is a function
6
+ * property on purpose: its parameter is then compared strictly, so a controller that reads a
7
+ * part its declaration does not produce, or returns another body, does not compile.
8
+ */
9
+ export interface HttpController<Spec extends ControllerSpec = ControllerSpec> {
10
+ readonly handle: Spec['response'] extends {
11
+ readonly body: Schema<infer Body>;
12
+ } ? (request: ControllerRequest<Spec>) => Body | Promise<Body> : (request: ControllerRequest<Spec>) => void | Promise<void>;
13
+ }
14
+ //# sourceMappingURL=http-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-controller.d.ts","sourceRoot":"","sources":["../../src/controller/http-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,cAAc,CAAC,IAAI,SAAS,cAAc,GAAG,cAAc;IAC1E,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAA;KAAE,GAC3E,CAAC,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC1D,CAAC,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-controller.js","sourceRoot":"","sources":["../../src/controller/http-controller.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import type { Actor } from '@ddd-ts-kit/kernel';
2
+ import type { RawHeaders } from '../route/raw-headers.js';
3
+ import type { RequestContext } from '../route/request-context.js';
4
+ interface Parts {
5
+ readonly params?: unknown;
6
+ readonly query?: unknown;
7
+ readonly headers?: unknown;
8
+ readonly body?: unknown;
9
+ }
10
+ type Part<Given, Name extends keyof Parts, Default> = Given extends {
11
+ readonly [K in Name]: infer T;
12
+ } ? T : Default;
13
+ /**
14
+ * The request as a controller, a mapper or a guard of the project names it: the parts it reads,
15
+ * typed by hand, `HttpRequest<{ params: CancelAppointmentParams; body: CancelAppointmentBody }>`.
16
+ * The decorator of the controller checks that its schemas produce those types.
17
+ */
18
+ export interface HttpRequest<Given extends Parts = Parts> {
19
+ readonly params: Part<Given, 'params', Record<string, string>>;
20
+ readonly query: Part<Given, 'query', Record<string, unknown>>;
21
+ readonly headers: Part<Given, 'headers', RawHeaders>;
22
+ readonly body: Part<Given, 'body', unknown>;
23
+ readonly actor: Actor;
24
+ readonly context: RequestContext;
25
+ }
26
+ export {};
27
+ //# sourceMappingURL=http-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-request.d.ts","sourceRoot":"","sources":["../../src/controller/http-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,UAAU,KAAK;IACb,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI,SAAS,MAAM,KAAK,EAAE,OAAO,IAAI,KAAK,SAAS;IAAE,QAAQ,EAAE,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC;CAAE,GAAG,CAAC,GAAG,OAAO,CAAC;AAEpH;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,KAAK,GAAG,KAAK;IACtD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;CAClC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-request.js","sourceRoot":"","sources":["../../src/controller/http-request.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { ControllerClass } from './controller-class.js';
2
+ import type { ControllerSpec } from './controller-spec.js';
3
+ import type { RouteGroupClass } from './route-group-class.js';
4
+ import type { RouteGroupOptions } from './route-group-options.js';
5
+ export interface PresentationRegistry {
6
+ readonly controllers: WeakMap<ControllerClass, ControllerSpec>;
7
+ readonly groups: WeakMap<RouteGroupClass, RouteGroupOptions>;
8
+ }
9
+ export declare function presentationRegistry(): PresentationRegistry;
10
+ //# sourceMappingURL=presentation-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation-registry.d.ts","sourceRoot":"","sources":["../../src/controller/presentation-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IAC/D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;CAC9D;AAQD,wBAAgB,oBAAoB,IAAI,oBAAoB,CAY3D"}
@@ -0,0 +1,21 @@
1
+ /*
2
+ * One registry per process, whatever the number of copies of this package it loaded, as the
3
+ * registry of declarations of the kernel: the global symbol registry is what they all share.
4
+ */
5
+ const REGISTRY = Symbol.for('@ddd-ts-kit/http/presentation/v1');
6
+ export function presentationRegistry() {
7
+ const existing = Reflect.get(globalThis, REGISTRY);
8
+ if (isRegistry(existing)) {
9
+ return existing;
10
+ }
11
+ const created = { controllers: new WeakMap(), groups: new WeakMap() };
12
+ Reflect.set(globalThis, REGISTRY, created);
13
+ return created;
14
+ }
15
+ function isRegistry(value) {
16
+ if (typeof value !== 'object' || value === null || !('controllers' in value) || !('groups' in value)) {
17
+ return false;
18
+ }
19
+ return value.controllers instanceof WeakMap && value.groups instanceof WeakMap;
20
+ }
21
+ //# sourceMappingURL=presentation-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation-registry.js","sourceRoot":"","sources":["../../src/controller/presentation-registry.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAEhE,MAAM,UAAU,oBAAoB;IAClC,MAAM,QAAQ,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE5D,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAyB,EAAE,WAAW,EAAE,IAAI,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,CAAC;IAE5F,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE3C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC;QACrG,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,KAAK,CAAC,WAAW,YAAY,OAAO,IAAI,KAAK,CAAC,MAAM,YAAY,OAAO,CAAC;AACjF,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type ClassDeclarator, type DeclaredDependencies, type DependenciesFromSignature } from '@ddd-ts-kit/kernel/declarations';
2
+ /**
3
+ * The one place that turns what arrived (the validated request and the actor of its execution
4
+ * context) into the command or the query of a use case. Nothing of HTTP is in its signature
5
+ * but the type of the request: another transport maps its own request to the same message.
6
+ */
7
+ export interface RequestMapper<Request, Message> {
8
+ map(request: Request): Message;
9
+ }
10
+ /** Declares a request mapper, so the composition root builds it and hands it to its controller. */
11
+ export declare function RequestMapper<const D extends DeclaredDependencies = DependenciesFromSignature>(dependencies?: D): ClassDeclarator<RequestMapper<never, unknown>, D>;
12
+ //# sourceMappingURL=request-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-mapper.d.ts","sourceRoot":"","sources":["../../src/controller/request-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC/B,MAAM,iCAAiC,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,OAAO,EAAE,OAAO;IAC7C,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;CAChC;AAED,mGAAmG;AACnG,wBAAgB,aAAa,CAAC,KAAK,CAAC,CAAC,SAAS,oBAAoB,GAAG,yBAAyB,EAC5F,YAAY,CAAC,EAAE,CAAC,GACf,eAAe,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAEnD"}
@@ -0,0 +1,6 @@
1
+ import { Edge, } from '@ddd-ts-kit/kernel/declarations';
2
+ /** Declares a request mapper, so the composition root builds it and hands it to its controller. */
3
+ export function RequestMapper(dependencies) {
4
+ return Edge('mapper', dependencies);
5
+ }
6
+ //# sourceMappingURL=request-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-mapper.js","sourceRoot":"","sources":["../../src/controller/request-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,GAIL,MAAM,iCAAiC,CAAC;AAWzC,mGAAmG;AACnG,MAAM,UAAU,aAAa,CAC3B,YAAgB;IAEhB,OAAO,IAAI,CAAmC,QAAQ,EAAE,YAAY,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ControllerClass } from './controller-class.js';
2
+ import type { ErasedController } from './erased-controller.js';
3
+ import type { ControllerRoutesOptions } from './controller-routes-options.js';
4
+ import type { ControllerSpec } from './controller-spec.js';
5
+ import { type DeclaredRoute } from './declared-route.js';
6
+ import type { Guard } from './guard.js';
7
+ /** A controller ready to answer: its instance, its declaration and the guards before it, built. */
8
+ export interface ResolvedController {
9
+ readonly controller: ErasedController;
10
+ readonly spec: ControllerSpec;
11
+ readonly declared: DeclaredRoute;
12
+ readonly guards: readonly Guard[];
13
+ }
14
+ type Resolve = NonNullable<ControllerRoutesOptions['resolve']>;
15
+ /** Reads the declaration of a controller given as its class or as an instance, and builds what must be built. */
16
+ export declare function resolvedController(entry: ControllerClass | ErasedController, resolve: Resolve | undefined): ResolvedController;
17
+ export {};
18
+ //# sourceMappingURL=resolved-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolved-controller.d.ts","sourceRoot":"","sources":["../../src/controller/resolved-controller.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,mGAAmG;AACnG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;CACnC;AAED,KAAK,OAAO,GAAG,WAAW,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC;AAE/D,iHAAiH;AACjH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,GAAG,gBAAgB,EACzC,OAAO,EAAE,OAAO,GAAG,SAAS,GAC3B,kBAAkB,CAgBpB"}
@@ -0,0 +1,47 @@
1
+ import { DeclarationError } from '@ddd-ts-kit/kernel/declarations';
2
+ import { declaredRoute } from './declared-route.js';
3
+ import { presentationRegistry } from './presentation-registry.js';
4
+ /** Reads the declaration of a controller given as its class or as an instance, and builds what must be built. */
5
+ export function resolvedController(entry, resolve) {
6
+ const target = isClass(entry) ? entry : classOf(entry);
7
+ const spec = presentationRegistry().controllers.get(target);
8
+ if (spec === undefined) {
9
+ throw new DeclarationError(`${target.name} is mounted as a controller and carries no @Controller.`);
10
+ }
11
+ const declared = declaredRoute(target.name, spec);
12
+ return {
13
+ controller: isClass(entry) ? controllerOf(built(entry, resolve), target.name) : entry,
14
+ spec,
15
+ declared,
16
+ guards: declared.guards.map((guard) => guardOf(guard, resolve)),
17
+ };
18
+ }
19
+ function classOf(instance) {
20
+ const constructor = instance.constructor;
21
+ if (!isClass(constructor)) {
22
+ throw new DeclarationError('A controller is mounted that is the instance of no class.');
23
+ }
24
+ return constructor;
25
+ }
26
+ function isClass(value) {
27
+ return typeof value === 'function';
28
+ }
29
+ function controllerOf(instance, className) {
30
+ if (typeof instance === 'object' && instance !== null && 'handle' in instance && isHandler(instance)) {
31
+ return instance;
32
+ }
33
+ throw new DeclarationError(`What was built for ${className} has no handle method: it is no controller.`);
34
+ }
35
+ function isHandler(instance) {
36
+ return typeof instance.handle === 'function';
37
+ }
38
+ function guardOf(reference, resolve) {
39
+ return typeof reference === 'function' ? built(reference, resolve) : reference;
40
+ }
41
+ function built(target, resolve) {
42
+ if (resolve === undefined) {
43
+ throw new DeclarationError(`${target.name} is given as a class and nothing builds it: pass \`resolve\` of the application to controllerRoutes, or give an instance.`);
44
+ }
45
+ return resolve(target);
46
+ }
47
+ //# sourceMappingURL=resolved-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolved-controller.js","sourceRoot":"","sources":["../../src/controller/resolved-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAKnE,OAAO,EAAE,aAAa,EAAsB,MAAM,qBAAqB,CAAC;AAGxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAYlE,iHAAiH;AACjH,MAAM,UAAU,kBAAkB,CAChC,KAAyC,EACzC,OAA4B;IAE5B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAE5D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,gBAAgB,CAAC,GAAG,MAAM,CAAC,IAAI,yDAAyD,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAElD,OAAO;QACL,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK;QACrF,IAAI;QACJ,QAAQ;QACR,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;KAChE,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,QAA0B;IACzC,MAAM,WAAW,GAAY,QAAQ,CAAC,WAAW,CAAC;IAElD,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,gBAAgB,CAAC,2DAA2D,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;AACrC,CAAC;AAED,SAAS,YAAY,CAAC,QAAiB,EAAE,SAAiB;IACxD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,IAAI,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrG,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,gBAAgB,CAAC,sBAAsB,SAAS,6CAA6C,CAAC,CAAC;AAC3G,CAAC;AAED,SAAS,SAAS,CAAC,QAA6B;IAC9C,OAAO,OAAO,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC;AAC/C,CAAC;AAED,SAAS,OAAO,CAAC,SAAyB,EAAE,OAA4B;IACtE,OAAO,OAAO,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjF,CAAC;AAED,SAAS,KAAK,CAAI,MAA0C,EAAE,OAA4B;IACxF,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,gBAAgB,CACxB,GAAG,MAAM,CAAC,IAAI,2HAA2H,CAC1I,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** A class decorated with `@RouteGroup`: it is never built, it names a set of routes. */
2
+ export type RouteGroupClass = abstract new (...args: never) => unknown;
3
+ //# sourceMappingURL=route-group-class.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group-class.d.ts","sourceRoot":"","sources":["../../src/controller/route-group-class.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,MAAM,eAAe,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=route-group-class.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group-class.js","sourceRoot":"","sources":["../../src/controller/route-group-class.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import type { GuardReference } from './guard-reference.js';
2
+ import type { RouteGroupClass } from './route-group-class.js';
3
+ export interface RouteGroupOptions {
4
+ /** The group this one refines: its prefix comes first, its tags, guards and error codes are inherited. */
5
+ readonly parent?: RouteGroupClass;
6
+ /** Put before the path of every controller of the group: `/appointments`. Left out, nothing is. */
7
+ readonly prefix?: string;
8
+ readonly tags?: readonly string[];
9
+ /** Run in order before every controller of the group, after the guards of the parent. */
10
+ readonly guards?: readonly GuardReference[];
11
+ /** Error codes every route of the group may answer, such as `FORBIDDEN` behind protected use cases. */
12
+ readonly errors?: readonly string[];
13
+ }
14
+ //# sourceMappingURL=route-group-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group-options.d.ts","sourceRoot":"","sources":["../../src/controller/route-group-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAChC,0GAA0G;IAC1G,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAClC,mGAAmG;IACnG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,yFAAyF;IACzF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,uGAAuG;IACvG,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=route-group-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group-options.js","sourceRoot":"","sources":["../../src/controller/route-group-options.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { RouteGroupClass } from './route-group-class.js';
2
+ import type { RouteGroupOptions } from './route-group-options.js';
3
+ /**
4
+ * Names a set of routes that share a prefix, tags and guards:
5
+ * `@RouteGroup({ prefix: '/appointments', tags: ['scheduling'], guards: [Authenticated] })` on
6
+ * an empty class. A controller names the class as its `group`; a group may refine another
7
+ * through `parent`. The class is never built: it is the name of the set.
8
+ */
9
+ export declare function RouteGroup(options?: RouteGroupOptions): (target: RouteGroupClass, context?: unknown) => void;
10
+ //# sourceMappingURL=route-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group.d.ts","sourceRoot":"","sources":["../../src/controller/route-group.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAIlE;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAsBhH"}
@@ -0,0 +1,25 @@
1
+ import { DeclarationError } from '@ddd-ts-kit/kernel/declarations';
2
+ import { presentationRegistry } from './presentation-registry.js';
3
+ const PREFIX_PATTERN = /^(\/(:[A-Za-z_][A-Za-z0-9_]*|[A-Za-z0-9._~-]+))*$/;
4
+ /**
5
+ * Names a set of routes that share a prefix, tags and guards:
6
+ * `@RouteGroup({ prefix: '/appointments', tags: ['scheduling'], guards: [Authenticated] })` on
7
+ * an empty class. A controller names the class as its `group`; a group may refine another
8
+ * through `parent`. The class is never built: it is the name of the set.
9
+ */
10
+ export function RouteGroup(options = {}) {
11
+ return (target) => {
12
+ const { groups } = presentationRegistry();
13
+ if (groups.has(target)) {
14
+ throw new DeclarationError(`${target.name} is declared as a route group twice.`);
15
+ }
16
+ if (!PREFIX_PATTERN.test(options.prefix ?? '')) {
17
+ throw new DeclarationError(`Route group ${target.name}: the prefix "${options.prefix}" must be made of literal segments and ":name" parameters, with no trailing slash.`);
18
+ }
19
+ if (options.parent !== undefined && !groups.has(options.parent)) {
20
+ throw new DeclarationError(`Route group ${target.name}: its parent ${options.parent.name} carries no @RouteGroup.`);
21
+ }
22
+ groups.set(target, Object.freeze({ ...options }));
23
+ };
24
+ }
25
+ //# sourceMappingURL=route-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group.js","sourceRoot":"","sources":["../../src/controller/route-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAIlE,MAAM,cAAc,GAAG,mDAAmD,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,OAAO,GAAsB,EAAE;IACxD,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,MAAM,EAAE,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAAC;QAE1C,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,gBAAgB,CAAC,GAAG,MAAM,CAAC,IAAI,sCAAsC,CAAC,CAAC;QACnF,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,gBAAgB,CACxB,eAAe,MAAM,CAAC,IAAI,iBAAiB,OAAO,CAAC,MAAM,oFAAoF,CAC9I,CAAC;QACJ,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,gBAAgB,CACxB,eAAe,MAAM,CAAC,IAAI,gBAAgB,OAAO,CAAC,MAAM,CAAC,IAAI,0BAA0B,CACxF,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type ClassDeclarator, type DeclaredDependencies, type DependenciesFromSignature } from '@ddd-ts-kit/kernel/declarations';
2
+ /**
3
+ * The one place that turns the result of a use case into the body a client reads: dates to
4
+ * text, internal fields left out. Another format serializes the same result its own way.
5
+ */
6
+ export interface Serializer<Result, Body> {
7
+ serialize(result: Result): Body;
8
+ }
9
+ /** Declares a serializer, so the composition root builds it and hands it to its controller. */
10
+ export declare function Serializer<const D extends DeclaredDependencies = DependenciesFromSignature>(dependencies?: D): ClassDeclarator<Serializer<never, unknown>, D>;
11
+ //# sourceMappingURL=serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../src/controller/serializer.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC/B,MAAM,iCAAiC,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,EAAE,IAAI;IACtC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,KAAK,CAAC,CAAC,SAAS,oBAAoB,GAAG,yBAAyB,EACzF,YAAY,CAAC,EAAE,CAAC,GACf,eAAe,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAEhD"}
@@ -0,0 +1,6 @@
1
+ import { Edge, } from '@ddd-ts-kit/kernel/declarations';
2
+ /** Declares a serializer, so the composition root builds it and hands it to its controller. */
3
+ export function Serializer(dependencies) {
4
+ return Edge('serializer', dependencies);
5
+ }
6
+ //# sourceMappingURL=serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/controller/serializer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,GAIL,MAAM,iCAAiC,CAAC;AAUzC,+FAA+F;AAC/F,MAAM,UAAU,UAAU,CACxB,YAAgB;IAEhB,OAAO,IAAI,CAAgC,YAAY,EAAE,YAAY,CAAC,CAAC;AACzE,CAAC"}
@@ -1,3 +1,4 @@
1
+ import type { RefusalFacts } from '@ddd-ts-kit/kernel';
1
2
  import type { ValidationIssue } from '@ddd-ts-kit/validation';
2
3
  export interface Problem {
3
4
  readonly type: string;
@@ -6,6 +7,8 @@ export interface Problem {
6
7
  readonly code: string;
7
8
  readonly detail?: string;
8
9
  readonly issues?: readonly ValidationIssue[];
10
+ /** The facts the refusal exposes, by name. Absent when its factory exposed none: facts are private by default. */
11
+ readonly facts?: RefusalFacts;
9
12
  readonly requestId: string;
10
13
  }
11
14
  //# sourceMappingURL=problem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../../src/error/problem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
1
+ {"version":3,"file":"problem.d.ts","sourceRoot":"","sources":["../../src/error/problem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,kHAAkH;IAClH,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"to-problem.d.ts","sourceRoot":"","sources":["../../src/error/to-problem.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAqBzF"}
1
+ {"version":3,"file":"to-problem.d.ts","sourceRoot":"","sources":["../../src/error/to-problem.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAsBzF"}
@@ -1,4 +1,4 @@
1
- import { DomainError } from '@ddd-ts-kit/kernel';
1
+ import { ConceptError, DomainError } from '@ddd-ts-kit/kernel';
2
2
  import { ValidationError } from '@ddd-ts-kit/validation';
3
3
  import { INTERNAL_ERROR_CODE } from './internal-error-code.js';
4
4
  import { statusTitle } from './status-title.js';
@@ -16,11 +16,52 @@ export function toProblem(error, mapper, requestId) {
16
16
  title: statusTitle(status),
17
17
  status,
18
18
  code: error.code,
19
- detail: error.message,
19
+ detail: publicDetail(error),
20
20
  ...(error instanceof ValidationError ? { issues: error.issues } : {}),
21
+ ...exposedFacts(error),
21
22
  requestId,
22
23
  };
23
24
  }
25
+ /**
26
+ * The sentence a client reads. The message of a refusal built by a factory may quote the facts
27
+ * it was given (an address, an identifier): it is answered unless it quotes one that the
28
+ * factory did not expose. Then the client reads a sentence made from the code alone, and the
29
+ * message stays where the error is logged and reported.
30
+ */
31
+ function publicDetail(error) {
32
+ if (!(error instanceof ConceptError)) {
33
+ return error.message;
34
+ }
35
+ const exposed = error.exposedFacts;
36
+ const hidden = Object.entries(error.facts).filter(([name]) => !(name in exposed));
37
+ return hidden.some(([, value]) => quotes(error.message, value)) ? sentenceOf(error.code) : error.message;
38
+ }
39
+ /** Whether the text holds the value as a factory would write it: a text, a number, an instant, or one of what a list or a record holds. */
40
+ function quotes(text, value) {
41
+ if (typeof value === 'string') {
42
+ return value !== '' && text.includes(value);
43
+ }
44
+ if (typeof value === 'number' || typeof value === 'bigint') {
45
+ return text.includes(String(value));
46
+ }
47
+ if (value instanceof Date) {
48
+ return text.includes(value.toISOString()) || text.includes(String(value));
49
+ }
50
+ return typeof value === 'object' && value !== null && Object.values(value).some((part) => quotes(text, part));
51
+ }
52
+ /** `INVALID_EMAIL` read as `Invalid email.` */
53
+ function sentenceOf(code) {
54
+ const words = code.toLowerCase().replaceAll('_', ' ');
55
+ return `${words.charAt(0).toUpperCase()}${words.slice(1)}.`;
56
+ }
57
+ /** Only what the factory of the refusal exposed by name: a fact never leaves the process by default. */
58
+ function exposedFacts(error) {
59
+ if (!(error instanceof ConceptError)) {
60
+ return {};
61
+ }
62
+ const facts = error.exposedFacts;
63
+ return Object.keys(facts).length === 0 ? {} : { facts };
64
+ }
24
65
  // The message of an unexpected error quotes data and internals: it never leaves the process.
25
66
  function internalProblem(requestId) {
26
67
  return {