@decohere/http 0.0.1-dev.116

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 (206) hide show
  1. package/README.md +246 -0
  2. package/commonjs/src/app/app.js +166 -0
  3. package/commonjs/src/app/index.js +19 -0
  4. package/commonjs/src/app/module.js +17 -0
  5. package/commonjs/src/auth/ability.js +54 -0
  6. package/commonjs/src/auth/auth.config.js +23 -0
  7. package/commonjs/src/auth/auth.extensions.js +106 -0
  8. package/commonjs/src/auth/auth.types.js +65 -0
  9. package/commonjs/src/auth/casl-authorization.service.js +36 -0
  10. package/commonjs/src/auth/index.js +23 -0
  11. package/commonjs/src/auth/principal-provider.js +19 -0
  12. package/commonjs/src/auth/safe-outbound-url.js +102 -0
  13. package/commonjs/src/auth/token-service.js +151 -0
  14. package/commonjs/src/builder/builder.js +95 -0
  15. package/commonjs/src/builder/index.js +18 -0
  16. package/commonjs/src/const.js +87 -0
  17. package/commonjs/src/health/health.controller.js +75 -0
  18. package/commonjs/src/health/health.extensions.js +50 -0
  19. package/commonjs/src/health/index.js +21 -0
  20. package/commonjs/src/health/readiness.service.js +67 -0
  21. package/commonjs/src/health/readiness.types.js +7 -0
  22. package/commonjs/src/hosting/hosted-service-host.js +88 -0
  23. package/commonjs/src/hosting/hosted-service.js +11 -0
  24. package/commonjs/src/hosting/index.js +19 -0
  25. package/commonjs/src/http/http.compression.js +27 -0
  26. package/commonjs/src/http/http.extensions.js +269 -0
  27. package/commonjs/src/http/http.options.js +26 -0
  28. package/commonjs/src/http/http.options.store.js +20 -0
  29. package/commonjs/src/http/http.security.js +27 -0
  30. package/commonjs/src/http/http.static.js +248 -0
  31. package/commonjs/src/http/index.js +22 -0
  32. package/commonjs/src/index.js +27 -0
  33. package/commonjs/src/openapi/index.js +45 -0
  34. package/commonjs/src/openapi/openapi.generator.js +103 -0
  35. package/commonjs/src/openapi/openapi.types.js +4 -0
  36. package/commonjs/src/openapi/request-schema.js +187 -0
  37. package/commonjs/src/openapi/request-validator.js +85 -0
  38. package/commonjs/src/providers/fastify.provider.js +391 -0
  39. package/commonjs/src/providers/index.js +18 -0
  40. package/commonjs/src/routing/anvil.types.js +99 -0
  41. package/commonjs/src/routing/decorators/argument.decorator.js +49 -0
  42. package/commonjs/src/routing/decorators/auth.decorator.js +70 -0
  43. package/commonjs/src/routing/decorators/controller.decorator.js +16 -0
  44. package/commonjs/src/routing/decorators/field.decorator.js +25 -0
  45. package/commonjs/src/routing/decorators/index.js +24 -0
  46. package/commonjs/src/routing/decorators/request.decorator.js +30 -0
  47. package/commonjs/src/routing/decorators/response.decorator.js +34 -0
  48. package/commonjs/src/routing/decorators/route.decorator.js +67 -0
  49. package/commonjs/src/routing/filters/exception-filter-host.js +145 -0
  50. package/commonjs/src/routing/filters/exception-filter.js +28 -0
  51. package/commonjs/src/routing/index.js +25 -0
  52. package/commonjs/src/routing/middleware/http-access-log.middleware.js +47 -0
  53. package/commonjs/src/routing/middleware/middleware.js +20 -0
  54. package/commonjs/src/routing/middleware/request-id.js +30 -0
  55. package/commonjs/src/routing/path.util.js +51 -0
  56. package/commonjs/src/routing/pipes/index.js +18 -0
  57. package/commonjs/src/routing/pipes/pipes.js +145 -0
  58. package/commonjs/src/routing/services/index.js +18 -0
  59. package/commonjs/src/routing/services/route-argument-resolver.js +77 -0
  60. package/commonjs/src/routing/services/route-registry.js +194 -0
  61. package/commonjs/src/routing/services/router.js +158 -0
  62. package/commonjs/src/sse/index.js +26 -0
  63. package/commonjs/src/sse/sse.decorator.js +42 -0
  64. package/commonjs/src/sse/sse.encoder.js +73 -0
  65. package/commonjs/src/sse/sse.types.js +36 -0
  66. package/commonjs/src/sse/sse.wire.js +40 -0
  67. package/commonjs/src/testing/boot-module.js +38 -0
  68. package/commonjs/src/testing/index.js +20 -0
  69. package/commonjs/src/testing/sse-reader.js +98 -0
  70. package/es6/src/app/app.js +148 -0
  71. package/es6/src/app/index.js +2 -0
  72. package/es6/src/app/module.js +7 -0
  73. package/es6/src/auth/ability.js +45 -0
  74. package/es6/src/auth/auth.config.js +19 -0
  75. package/es6/src/auth/auth.extensions.js +83 -0
  76. package/es6/src/auth/auth.types.js +35 -0
  77. package/es6/src/auth/casl-authorization.service.js +40 -0
  78. package/es6/src/auth/index.js +6 -0
  79. package/es6/src/auth/principal-provider.js +14 -0
  80. package/es6/src/auth/safe-outbound-url.js +99 -0
  81. package/es6/src/auth/token-service.js +141 -0
  82. package/es6/src/builder/builder.js +85 -0
  83. package/es6/src/builder/index.js +1 -0
  84. package/es6/src/const.js +33 -0
  85. package/es6/src/health/health.controller.js +65 -0
  86. package/es6/src/health/health.extensions.js +40 -0
  87. package/es6/src/health/index.js +4 -0
  88. package/es6/src/health/readiness.service.js +57 -0
  89. package/es6/src/health/readiness.types.js +4 -0
  90. package/es6/src/hosting/hosted-service-host.js +81 -0
  91. package/es6/src/hosting/hosted-service.js +8 -0
  92. package/es6/src/hosting/index.js +2 -0
  93. package/es6/src/http/http.compression.js +9 -0
  94. package/es6/src/http/http.extensions.js +259 -0
  95. package/es6/src/http/http.options.js +16 -0
  96. package/es6/src/http/http.options.store.js +13 -0
  97. package/es6/src/http/http.security.js +9 -0
  98. package/es6/src/http/http.static.js +224 -0
  99. package/es6/src/http/index.js +5 -0
  100. package/es6/src/index.js +10 -0
  101. package/es6/src/openapi/index.js +3 -0
  102. package/es6/src/openapi/openapi.generator.js +82 -0
  103. package/es6/src/openapi/openapi.types.js +1 -0
  104. package/es6/src/openapi/request-schema.js +160 -0
  105. package/es6/src/openapi/request-validator.js +71 -0
  106. package/es6/src/providers/fastify.provider.js +376 -0
  107. package/es6/src/providers/index.js +1 -0
  108. package/es6/src/routing/anvil.types.js +57 -0
  109. package/es6/src/routing/decorators/argument.decorator.js +28 -0
  110. package/es6/src/routing/decorators/auth.decorator.js +58 -0
  111. package/es6/src/routing/decorators/controller.decorator.js +6 -0
  112. package/es6/src/routing/decorators/field.decorator.js +15 -0
  113. package/es6/src/routing/decorators/index.js +7 -0
  114. package/es6/src/routing/decorators/request.decorator.js +24 -0
  115. package/es6/src/routing/decorators/response.decorator.js +16 -0
  116. package/es6/src/routing/decorators/route.decorator.js +37 -0
  117. package/es6/src/routing/filters/exception-filter-host.js +127 -0
  118. package/es6/src/routing/filters/exception-filter.js +10 -0
  119. package/es6/src/routing/index.js +8 -0
  120. package/es6/src/routing/middleware/http-access-log.middleware.js +37 -0
  121. package/es6/src/routing/middleware/middleware.js +10 -0
  122. package/es6/src/routing/middleware/request-id.js +14 -0
  123. package/es6/src/routing/path.util.js +33 -0
  124. package/es6/src/routing/pipes/index.js +1 -0
  125. package/es6/src/routing/pipes/pipes.js +118 -0
  126. package/es6/src/routing/services/index.js +1 -0
  127. package/es6/src/routing/services/route-argument-resolver.js +67 -0
  128. package/es6/src/routing/services/route-registry.js +184 -0
  129. package/es6/src/routing/services/router.js +148 -0
  130. package/es6/src/sse/index.js +2 -0
  131. package/es6/src/sse/sse.decorator.js +31 -0
  132. package/es6/src/sse/sse.encoder.js +67 -0
  133. package/es6/src/sse/sse.types.js +22 -0
  134. package/es6/src/sse/sse.wire.js +30 -0
  135. package/es6/src/testing/boot-module.js +35 -0
  136. package/es6/src/testing/index.js +2 -0
  137. package/es6/src/testing/sse-reader.js +91 -0
  138. package/package.json +64 -0
  139. package/types/src/app/app.d.ts +41 -0
  140. package/types/src/app/index.d.ts +2 -0
  141. package/types/src/app/module.d.ts +15 -0
  142. package/types/src/auth/ability.d.ts +19 -0
  143. package/types/src/auth/auth.config.d.ts +28 -0
  144. package/types/src/auth/auth.extensions.d.ts +53 -0
  145. package/types/src/auth/auth.types.d.ts +73 -0
  146. package/types/src/auth/casl-authorization.service.d.ts +28 -0
  147. package/types/src/auth/index.d.ts +6 -0
  148. package/types/src/auth/principal-provider.d.ts +20 -0
  149. package/types/src/auth/safe-outbound-url.d.ts +9 -0
  150. package/types/src/auth/token-service.d.ts +31 -0
  151. package/types/src/builder/builder.d.ts +36 -0
  152. package/types/src/builder/index.d.ts +1 -0
  153. package/types/src/const.d.ts +32 -0
  154. package/types/src/health/health.controller.d.ts +16 -0
  155. package/types/src/health/health.extensions.d.ts +31 -0
  156. package/types/src/health/index.d.ts +4 -0
  157. package/types/src/health/readiness.service.d.ts +9 -0
  158. package/types/src/health/readiness.types.d.ts +14 -0
  159. package/types/src/hosting/hosted-service-host.d.ts +37 -0
  160. package/types/src/hosting/hosted-service.d.ts +31 -0
  161. package/types/src/hosting/index.d.ts +2 -0
  162. package/types/src/http/http.compression.d.ts +32 -0
  163. package/types/src/http/http.extensions.d.ts +79 -0
  164. package/types/src/http/http.options.d.ts +39 -0
  165. package/types/src/http/http.options.store.d.ts +7 -0
  166. package/types/src/http/http.security.d.ts +20 -0
  167. package/types/src/http/http.static.d.ts +160 -0
  168. package/types/src/http/index.d.ts +5 -0
  169. package/types/src/index.d.ts +10 -0
  170. package/types/src/openapi/index.d.ts +6 -0
  171. package/types/src/openapi/openapi.generator.d.ts +6 -0
  172. package/types/src/openapi/openapi.types.d.ts +58 -0
  173. package/types/src/openapi/request-schema.d.ts +21 -0
  174. package/types/src/openapi/request-validator.d.ts +9 -0
  175. package/types/src/providers/fastify.provider.d.ts +56 -0
  176. package/types/src/providers/index.d.ts +1 -0
  177. package/types/src/routing/anvil.types.d.ts +119 -0
  178. package/types/src/routing/decorators/argument.decorator.d.ts +11 -0
  179. package/types/src/routing/decorators/auth.decorator.d.ts +30 -0
  180. package/types/src/routing/decorators/controller.decorator.d.ts +3 -0
  181. package/types/src/routing/decorators/field.decorator.d.ts +2 -0
  182. package/types/src/routing/decorators/index.d.ts +7 -0
  183. package/types/src/routing/decorators/request.decorator.d.ts +8 -0
  184. package/types/src/routing/decorators/response.decorator.d.ts +4 -0
  185. package/types/src/routing/decorators/route.decorator.d.ts +7 -0
  186. package/types/src/routing/filters/exception-filter-host.d.ts +19 -0
  187. package/types/src/routing/filters/exception-filter.d.ts +16 -0
  188. package/types/src/routing/index.d.ts +8 -0
  189. package/types/src/routing/middleware/http-access-log.middleware.d.ts +8 -0
  190. package/types/src/routing/middleware/middleware.d.ts +10 -0
  191. package/types/src/routing/middleware/request-id.d.ts +6 -0
  192. package/types/src/routing/path.util.d.ts +9 -0
  193. package/types/src/routing/pipes/index.d.ts +1 -0
  194. package/types/src/routing/pipes/pipes.d.ts +21 -0
  195. package/types/src/routing/services/index.d.ts +1 -0
  196. package/types/src/routing/services/route-argument-resolver.d.ts +10 -0
  197. package/types/src/routing/services/route-registry.d.ts +20 -0
  198. package/types/src/routing/services/router.d.ts +22 -0
  199. package/types/src/sse/index.d.ts +3 -0
  200. package/types/src/sse/sse.decorator.d.ts +17 -0
  201. package/types/src/sse/sse.encoder.d.ts +16 -0
  202. package/types/src/sse/sse.types.d.ts +37 -0
  203. package/types/src/sse/sse.wire.d.ts +9 -0
  204. package/types/src/testing/boot-module.d.ts +34 -0
  205. package/types/src/testing/index.d.ts +4 -0
  206. package/types/src/testing/sse-reader.d.ts +29 -0
@@ -0,0 +1,119 @@
1
+ import type { Constructable, Type } from "@decohere/core/reflection";
2
+ import type { HTTPMethod } from "../const.js";
3
+ import type { SseOptions } from "../sse/sse.types.js";
4
+ import type { Pipe } from "./pipes/pipes.js";
5
+ export type Metadata = "controller" | "route" | "parameter" | "fieldMap" | "authorize" | "allowAnonymous" | "httpCode" | "headers" | "catch";
6
+ export type CorsOptions = {
7
+ origin?: boolean | string | string[];
8
+ methods?: string[];
9
+ allowedHeaders?: string[];
10
+ exposedHeaders?: string[];
11
+ credentials?: boolean;
12
+ maxAge?: number;
13
+ };
14
+ export declare const RequestParts: {
15
+ query: "query";
16
+ params: "params";
17
+ body: "body";
18
+ principal: "principal";
19
+ signal: "signal";
20
+ lastEventId: "lastEventId";
21
+ };
22
+ export interface HttpProvider {
23
+ start<T extends StartOptions>(options: T): Promise<StartResponse>;
24
+ stop(): Promise<StopResponse>;
25
+ [Symbol.asyncDispose](): Promise<void>;
26
+ }
27
+ export type RequestPart = "query" | "params" | "body" | "principal" | "signal" | "lastEventId";
28
+ /** Argument locations filled by the HTTP package itself rather than read from the request payload. */
29
+ export declare const InjectedParts: readonly RequestPart[];
30
+ export type MappedRoute = {
31
+ key: string;
32
+ controller: Constructable;
33
+ handler: string;
34
+ routeArgs?: RouteArg[];
35
+ httpCode?: number;
36
+ headers?: Record<string, string>;
37
+ /** Set by `@Sse()`: the handler returns a stream of events instead of a single response. */
38
+ sse?: SseOptions;
39
+ } & RouteMetadata;
40
+ export type RouteArg = {
41
+ argumentType: Constructable;
42
+ fieldMap?: Map<string, FieldMeta>;
43
+ pipes?: Constructable<Pipe>[];
44
+ } & ArgumentMeta;
45
+ export declare class HttpRequest {
46
+ constructor(data?: Partial<HttpRequest>);
47
+ requestId: string;
48
+ url: string;
49
+ method: string;
50
+ params: Record<string, string>;
51
+ query: Record<string, string>;
52
+ body: unknown;
53
+ headers: Record<string, string>;
54
+ /** Aborts when the client disconnects or the app stops. Never aborts if the provider cannot tell. */
55
+ signal: AbortSignal;
56
+ /** Outbound headers set by middleware; applied by the HTTP provider. */
57
+ responseHeaders: Record<string, string>;
58
+ }
59
+ export type StartResponse = {
60
+ addresses: string[];
61
+ };
62
+ export type StopResponse = {
63
+ message: string;
64
+ };
65
+ export type StartOptions = {
66
+ port: number;
67
+ host?: string;
68
+ };
69
+ export type ArgumentMeta = {
70
+ argumentIndex: number;
71
+ mappingKey?: string;
72
+ argumentLocation?: RequestPart;
73
+ pipes: Constructable<Pipe>[];
74
+ };
75
+ export type ControllerMetadata = {
76
+ prefix: string;
77
+ /**
78
+ * `false` leaves this controller's routes out of the global prefix (`withGlobalPrefix`), for
79
+ * paths something outside the app expects at a fixed place: probes, webhooks, metrics. Default
80
+ * `true`.
81
+ */
82
+ globalPrefix?: boolean;
83
+ };
84
+ export type FieldMeta = {
85
+ fieldKey: string;
86
+ fieldType: Type;
87
+ mappingKey: string;
88
+ fieldLocation?: RequestPart;
89
+ pipes?: Constructable<Pipe>[];
90
+ };
91
+ export type RouteMetadata = {
92
+ path: string;
93
+ method: HTTPMethod;
94
+ };
95
+ export declare class RouteConfigurationError extends Error {
96
+ }
97
+ export declare class RoutingError extends Error {
98
+ }
99
+ export declare class TransformError extends Error {
100
+ }
101
+ export declare class HttpError extends Error {
102
+ readonly status: number;
103
+ constructor(message: string, status: number);
104
+ }
105
+ export declare class UnauthorizedError extends HttpError {
106
+ constructor(message?: string);
107
+ }
108
+ export declare class ForbiddenError extends HttpError {
109
+ constructor(message?: string);
110
+ }
111
+ export type ValidationIssue = {
112
+ path: string;
113
+ message: string;
114
+ keyword?: string;
115
+ };
116
+ export declare class ValidationError extends HttpError {
117
+ readonly errors: ValidationIssue[];
118
+ constructor(message?: string, errors?: ValidationIssue[]);
119
+ }
@@ -0,0 +1,11 @@
1
+ import type { Constructable } from "@decohere/core/reflection";
2
+ import { type RequestPart } from "../anvil.types.js";
3
+ import type { Pipe } from "../pipes/pipes.js";
4
+ export type ArgumentOptions = {
5
+ mappingKey?: string;
6
+ argumentLocation?: RequestPart;
7
+ pipes?: Constructable<Pipe>[];
8
+ };
9
+ export declare function Query(key: string): (target: object, functionName: string, argumentIndex: number) => void;
10
+ export declare function Param(key: string): (target: object, functionName: string, argumentIndex: number) => void;
11
+ export declare function Argument(options: ArgumentOptions): (target: object, functionName: string, argumentIndex: number) => void;
@@ -0,0 +1,30 @@
1
+ import type { Constructable } from "@decohere/core/reflection";
2
+ export type AuthorizeOptions = {
3
+ action: string;
4
+ subject: string;
5
+ };
6
+ /**
7
+ * Require the principal to be able to perform `action` on `subject`.
8
+ * Uses an IAuthorizationService (e.g. CaslAuthorizationService) registered in DI.
9
+ * When applied to a class, all methods inherit the constraint.
10
+ * When applied to a method, it takes precedence over the class-level decorator.
11
+ *
12
+ * @example
13
+ * \@Authorize("delete", "Article")
14
+ * \@Authorize("manage", "all")
15
+ */
16
+ export declare function Authorize(action: string, subject?: string): (target: object | Constructable, methodName?: string, _descriptor?: PropertyDescriptor) => void;
17
+ /**
18
+ * Opt a specific route method out of authentication, even when the controller
19
+ * class is decorated with @Authorize.
20
+ */
21
+ export declare function AllowAnonymous(): (target: object, methodName: string, _descriptor?: PropertyDescriptor) => void;
22
+ /** Read the effective @Authorize options for a handler function. */
23
+ export declare function getAuthorizeOptions(controllerPrototype: object, handlerFn: object): AuthorizeOptions | undefined;
24
+ /** Returns true only when the handler is explicitly marked @AllowAnonymous. */
25
+ export declare function isAllowAnonymous(_controllerPrototype: object, handlerFn: object): boolean;
26
+ /**
27
+ * Parameter decorator — injects the authenticated Principal into this argument.
28
+ * The argument resolver will supply the principal verified during the auth step.
29
+ */
30
+ export declare function CurrentUser(): (target: object, propertyKey: string, parameterIndex: number) => void;
@@ -0,0 +1,3 @@
1
+ import type { Constructable } from "@decohere/core/reflection";
2
+ import type { ControllerMetadata } from "../anvil.types.js";
3
+ export declare function Controller(options?: ControllerMetadata): (target: Constructable) => void;
@@ -0,0 +1,2 @@
1
+ import type { RequestPart } from "../anvil.types.js";
2
+ export declare function Field(fieldLocation?: RequestPart, mappingKey?: string): (target: object, fieldName: string) => void;
@@ -0,0 +1,7 @@
1
+ export * from "./argument.decorator.js";
2
+ export * from "./auth.decorator.js";
3
+ export * from "./controller.decorator.js";
4
+ export * from "./field.decorator.js";
5
+ export * from "./request.decorator.js";
6
+ export * from "./response.decorator.js";
7
+ export * from "./route.decorator.js";
@@ -0,0 +1,8 @@
1
+ import type { Constructable } from "@decohere/core/reflection";
2
+ import type { RequestPart } from "../anvil.types.js";
3
+ /**
4
+ * Used to map a request object to route params
5
+ * @param mappingKey
6
+ * @returns
7
+ */
8
+ export declare function Request(mappingKey?: RequestPart): (target: Constructable) => void;
@@ -0,0 +1,4 @@
1
+ /** Set the HTTP status code returned on a successful handler response. */
2
+ export declare function HttpCode(statusCode: number): (_target: object, _methodName: string, descriptor: PropertyDescriptor) => void;
3
+ /** Set a response header on a successful handler response. Stackable. */
4
+ export declare function Header(name: string, value: string): (_target: object, _methodName: string, descriptor: PropertyDescriptor) => void;
@@ -0,0 +1,7 @@
1
+ import { type HTTPMethod } from "../../const.js";
2
+ export declare function Get(path?: string): (targetClass: object, functionName: string, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => void;
3
+ export declare function Post(path?: string): (targetClass: object, functionName: string, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => void;
4
+ export declare function Put(path?: string): (targetClass: object, functionName: string, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => void;
5
+ export declare function Patch(path?: string): (targetClass: object, functionName: string, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => void;
6
+ export declare function Delete(path?: string): (targetClass: object, functionName: string, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => void;
7
+ export declare function Route(method: HTTPMethod, path?: string): (targetClass: object, functionName: string, descriptor?: TypedPropertyDescriptor<(...args: any[]) => any>) => void;
@@ -0,0 +1,19 @@
1
+ import { type LifetimeScope } from "@decohere/crucible";
2
+ import type { HttpOptions } from "../../http/http.options.js";
3
+ import { type ExceptionContext, type ExceptionFilter, type ExceptionResult } from "./exception-filter.js";
4
+ export declare class DefaultHttpExceptionFilter implements ExceptionFilter {
5
+ private readonly logger;
6
+ catch(error: unknown, _ctx: ExceptionContext): ExceptionResult;
7
+ }
8
+ /**
9
+ * Resolves the first matching filter for an error.
10
+ * Custom filters (registered via builder) are tried first; default is last.
11
+ */
12
+ export declare class ExceptionFilterHost {
13
+ private readonly options?;
14
+ private readonly scope?;
15
+ private filters?;
16
+ constructor(options?: HttpOptions, scope?: LifetimeScope);
17
+ handle(error: unknown, ctx: ExceptionContext): Promise<ExceptionResult>;
18
+ private getFilters;
19
+ }
@@ -0,0 +1,16 @@
1
+ import type { Constructable } from "@decohere/core/reflection";
2
+ import type { HttpRequest } from "../anvil.types.js";
3
+ export type ExceptionResult = {
4
+ status: number;
5
+ body: unknown;
6
+ headers?: Record<string, string>;
7
+ };
8
+ export type ExceptionContext = {
9
+ request: HttpRequest;
10
+ };
11
+ export interface ExceptionFilter {
12
+ catch(error: unknown, ctx: ExceptionContext): ExceptionResult | Promise<ExceptionResult>;
13
+ }
14
+ /** Declare which error types this filter handles. Empty = catch-all. */
15
+ export declare function Catch(...types: Constructable[]): (target: Constructable) => void;
16
+ export declare function getCatchTypes(filterType: Constructable): Constructable[];
@@ -0,0 +1,8 @@
1
+ export * from "./anvil.types.js";
2
+ export * from "./decorators/index.js";
3
+ export * from "./filters/exception-filter.js";
4
+ export * from "./filters/exception-filter-host.js";
5
+ export * from "./middleware/http-access-log.middleware.js";
6
+ export * from "./middleware/middleware.js";
7
+ export * from "./middleware/request-id.js";
8
+ export * from "./pipes/index.js";
@@ -0,0 +1,8 @@
1
+ import type { Middleware, MiddlewareContext } from "./middleware.js";
2
+ /**
3
+ * Echoes `x-request-id` on the response and writes an HTTP access line via Scribe
4
+ * (method, path, status, durationMs, requestId). Enabled by default through `withHttp`.
5
+ */
6
+ export declare class HttpAccessLogMiddleware implements Middleware {
7
+ use(ctx: MiddlewareContext, next: () => Promise<unknown>): Promise<unknown>;
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { HttpRequest, MappedRoute } from "../anvil.types.js";
2
+ export type MiddlewareContext = {
3
+ request: HttpRequest;
4
+ route: MappedRoute;
5
+ };
6
+ export interface Middleware {
7
+ use(ctx: MiddlewareContext, next: () => Promise<unknown>): Promise<unknown>;
8
+ }
9
+ /** Compose middleware around a terminal handler; returns the handler result. */
10
+ export declare function composeMiddleware(middlewares: Middleware[], handler: () => Promise<unknown>, ctx: MiddlewareContext): Promise<unknown>;
@@ -0,0 +1,6 @@
1
+ /** Canonical inbound/outbound request id header (lowercase). */
2
+ export declare const REQUEST_ID_HEADER = "x-request-id";
3
+ /**
4
+ * Prefer an inbound `x-request-id`, then an existing id, otherwise generate one.
5
+ */
6
+ export declare function resolveRequestId(headers: Record<string, string>, fallback?: string): string;
@@ -0,0 +1,9 @@
1
+ /** Normalize a prefix to `/foo` form with no trailing slash. Empty → `""`. */
2
+ export declare function normalizePrefix(prefix: string | undefined | null): string;
3
+ /**
4
+ * Join a controller prefix with a method-relative route path.
5
+ * Preserves `@Get("/")` trailing-slash semantics (`/hello` + `/` → `/hello/`).
6
+ */
7
+ export declare function joinRoutePath(prefix: string, routePath: string): string;
8
+ /** Prepend a global prefix onto an already-joined controller route path. */
9
+ export declare function applyGlobalPrefix(globalPrefix: string, path: string): string;
@@ -0,0 +1 @@
1
+ export * from "./pipes.js";
@@ -0,0 +1,21 @@
1
+ import type { Type } from "@decohere/core/reflection";
2
+ export type PipeContext = {
3
+ fieldTypes: Record<string, Type>;
4
+ };
5
+ export declare abstract class Pipe<T = unknown> {
6
+ abstract transform(value: unknown, context?: PipeContext): T;
7
+ }
8
+ export declare class BooleanPipe extends Pipe<boolean> {
9
+ transform(value: unknown, _context?: unknown): boolean;
10
+ }
11
+ export declare class DatePipe extends Pipe<Date> {
12
+ transform(value: unknown, _context?: unknown): Date;
13
+ }
14
+ export declare class JsonPipe extends Pipe<object> {
15
+ transform(value: string, context?: PipeContext): object;
16
+ private reviver;
17
+ }
18
+ export declare class NumberPipe extends Pipe<number> {
19
+ /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined` (#503). */
20
+ transform(value: unknown, _context?: unknown): number;
21
+ }
@@ -0,0 +1 @@
1
+ export * from "./router.js";
@@ -0,0 +1,10 @@
1
+ import type { IPrincipalProvider } from "../../auth/principal-provider.js";
2
+ import type { HttpRequest, RouteArg } from "../anvil.types.js";
3
+ export declare class RouteArgumentResolve {
4
+ resolveArguments(httpRequest: HttpRequest, routeArgs: RouteArg[], principalProvider?: IPrincipalProvider): any[];
5
+ private getValue;
6
+ private mapPrimitive;
7
+ private mapObject;
8
+ private transform;
9
+ private pipeContext;
10
+ }
@@ -0,0 +1,20 @@
1
+ import type { Constructable } from "@decohere/core/reflection";
2
+ import type { HttpOptions } from "../../http/http.options.js";
3
+ import type { MappedRoute } from "../anvil.types.js";
4
+ export declare class RouteRegistry {
5
+ private readonly routeMap;
6
+ private readonly httpOptions?;
7
+ constructor(httpOptions?: HttpOptions);
8
+ addController(controller: Constructable): void;
9
+ get routes(): MapIterator<MappedRoute>;
10
+ getRoute(key: string): MappedRoute;
11
+ private addControllerRoute;
12
+ /** `@Controller({ globalPrefix: false })` opts out, unless the app put it back under. */
13
+ private takesGlobalPrefix;
14
+ private getControllerPrefix;
15
+ private getRouteArguments;
16
+ private getRouteArgument;
17
+ private getFields;
18
+ private enrichField;
19
+ private getDefaultPipes;
20
+ }
@@ -0,0 +1,22 @@
1
+ import { type LifetimeScope } from "@decohere/crucible";
2
+ import type { IAuthorizationService } from "../../auth/auth.types.js";
3
+ import type { HttpOptions } from "../../http/http.options.js";
4
+ import type { HttpRequest } from "../anvil.types.js";
5
+ import { RouteRegistry } from "./route-registry.js";
6
+ export declare class Router {
7
+ private readonly argResolver;
8
+ private readonly lifetimeScope;
9
+ private readonly routeRegistry;
10
+ private readonly authService?;
11
+ private readonly httpOptions?;
12
+ private readonly logger;
13
+ constructor(routeRegistry: RouteRegistry, lifetimeScope: LifetimeScope, authService?: IAuthorizationService, httpOptions?: HttpOptions);
14
+ /**
15
+ * Without an authorization service nothing checks tokens, so a route marked @Authorize
16
+ * would be served to anyone. Refuse to start instead of failing open (#500).
17
+ */
18
+ private assertAuthConfiguredForProtectedRoutes;
19
+ route(routeKey: string, httpRequest: HttpRequest): Promise<any>;
20
+ private resolveMiddlewares;
21
+ private authorize;
22
+ }
@@ -0,0 +1,3 @@
1
+ export { LastEventId, Signal, Sse } from "./sse.decorator.js";
2
+ export type { SseMessage, SseOptions, SseStream } from "./sse.types.js";
3
+ export { SseResponse } from "./sse.types.js";
@@ -0,0 +1,17 @@
1
+ import type { SseOptions } from "./sse.types.js";
2
+ /**
3
+ * Mark a route as a server-sent event stream. The method returns an `AsyncIterable` of
4
+ * {@link SseMessage}s (an `async *` method is the usual shape) and the HTTP package writes it as
5
+ * `text/event-stream`. Auth, validation and middleware run first, exactly as on any route,
6
+ * so an unauthorised caller gets a normal 401/403 and no event is ever sent.
7
+ *
8
+ * Combine with `@Get()` (EventSource only issues GET).
9
+ */
10
+ export declare function Sse(options?: SseOptions): (_target: object, _methodName: string, descriptor: PropertyDescriptor) => void;
11
+ /**
12
+ * Inject an `AbortSignal` that aborts when the client disconnects or the app stops. Pass it to
13
+ * whatever the producer waits on (timers, subscriptions, queries) so it stops promptly.
14
+ */
15
+ export declare const Signal: () => (target: object, propertyKey: string, parameterIndex: number) => void;
16
+ /** Inject the `Last-Event-ID` header the browser sends when it reconnects, or `undefined`. */
17
+ export declare const LastEventId: () => (target: object, propertyKey: string, parameterIndex: number) => void;
@@ -0,0 +1,16 @@
1
+ export type SseEncodeOptions = {
2
+ /** Aborted when the client disconnects or the app stops. */
3
+ signal: AbortSignal;
4
+ heartbeatMs: number;
5
+ /**
6
+ * Called once, as the stream ends, with a promise that settles when the producer has
7
+ * actually stopped (its `finally` blocks have run).
8
+ */
9
+ onProducerStopped: (stopped: Promise<void>) => void;
10
+ };
11
+ /**
12
+ * Turn the handler's events into SSE text. Writes an opening comment so the browser sees the
13
+ * response headers at once, a heartbeat whenever the producer is quiet, and stops as soon as the
14
+ * signal aborts, without waiting for a producer that is stuck.
15
+ */
16
+ export declare function encodeSse(source: AsyncIterable<unknown>, options: SseEncodeOptions): AsyncGenerator<string>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * One server-sent event. A bare `string` yielded by a handler is shorthand for `{ data }`.
3
+ *
4
+ * `data` is written as-is when it is a string and as JSON otherwise.
5
+ */
6
+ export type SseMessage<T = unknown> = {
7
+ data: T;
8
+ /** Becomes the `event:` field; the browser dispatches it to `addEventListener(event, ...)`. */
9
+ event?: string;
10
+ /** Becomes the `id:` field; the browser sends the last one back as `Last-Event-ID` on reconnect. */
11
+ id?: string;
12
+ /** Tells the browser how many milliseconds to wait before reconnecting. */
13
+ retry?: number;
14
+ };
15
+ /** What a `@Sse()` handler returns: any async iterable of messages (an `async *` method is the usual shape). */
16
+ export type SseStream<T = unknown> = AsyncIterable<SseMessage<T> | string>;
17
+ export type SseOptions = {
18
+ /**
19
+ * Milliseconds of silence before the HTTP package writes a `: heartbeat` comment, which keeps proxies
20
+ * and load balancers from closing an idle connection. Default 15000. `0` turns heartbeats off.
21
+ */
22
+ heartbeatMs?: number;
23
+ };
24
+ export declare const DEFAULT_SSE_HEARTBEAT_MS = 15000;
25
+ /**
26
+ * What the router hands the HTTP provider for an `@Sse()` route. The request-scoped services the
27
+ * handler uses must stay alive for as long as the stream runs, so the provider calls `release()`
28
+ * once the stream is over, instead of the router disposing them as soon as the handler returns.
29
+ */
30
+ export declare class SseResponse {
31
+ readonly events: AsyncIterable<unknown>;
32
+ private readonly onRelease;
33
+ private released;
34
+ constructor(events: AsyncIterable<unknown>, onRelease: () => Promise<void>);
35
+ /** Safe to call more than once. */
36
+ release(): Promise<void>;
37
+ }
@@ -0,0 +1,9 @@
1
+ /** Encode one event as the text a browser's `EventSource` parses. */
2
+ declare function formatMessage(item: unknown): string;
3
+ /** A comment line: ignored by `EventSource`, but it is traffic on the wire. */
4
+ declare function formatComment(text: string): string;
5
+ export declare const sseWire: {
6
+ formatMessage: typeof formatMessage;
7
+ formatComment: typeof formatComment;
8
+ };
9
+ export {};
@@ -0,0 +1,34 @@
1
+ import type { ContainerBuilder } from "@decohere/crucible";
2
+ import type { Constructable } from "@decohere/core/reflection";
3
+ import type { App } from "../app/app.js";
4
+ import { Builder } from "../builder/builder.js";
5
+ import "../http/http.extensions.js";
6
+ import type { HttpProvider } from "../routing/anvil.types.js";
7
+ import { type EventStreamRead, type ReadEventsOptions } from "./sse-reader.js";
8
+ export type BootModuleOptions = {
9
+ controllers: Constructable[];
10
+ /** Register services the controllers need. */
11
+ services?: (containerBuilder: ContainerBuilder) => void;
12
+ /** Add anything else to the builder (`withAuth()`, `withOpenApi()`, ...) before the app is built. */
13
+ configure?: (builder: Builder) => unknown;
14
+ /** Defaults to the Fastify provider. */
15
+ provider?: Constructable<HttpProvider>;
16
+ };
17
+ export type BootedModule = AsyncDisposable & {
18
+ app: App;
19
+ /** Origin of the listening app, e.g. `http://127.0.0.1:41233`. */
20
+ baseUrl: string;
21
+ /** `GET path` as an `EventSource` would, and collect the first `count` events. */
22
+ readEvents(path: string, options?: ReadEventsOptions): Promise<EventStreamRead>;
23
+ /** Stop the app (closing any open streams). Same as `await using`. */
24
+ stop(): Promise<void>;
25
+ };
26
+ /**
27
+ * Boot the given controllers as a real app on a free loopback port, so a test can call it over
28
+ * HTTP. The app runs the full pipeline: routing, auth, middleware, and the real provider.
29
+ *
30
+ * @example
31
+ * await using api = await bootModule({ controllers: [PriceController] });
32
+ * const { events } = await api.readEvents("/prices/stream", { count: 3 });
33
+ */
34
+ export declare function bootModule(options: BootModuleOptions): Promise<BootedModule>;
@@ -0,0 +1,4 @@
1
+ export type { BootedModule, BootModuleOptions } from "./boot-module.js";
2
+ export { bootModule } from "./boot-module.js";
3
+ export type { EventStreamRead, ReadEventsOptions, ReceivedEvent } from "./sse-reader.js";
4
+ export { readEventStream } from "./sse-reader.js";
@@ -0,0 +1,29 @@
1
+ export type ReceivedEvent = {
2
+ /** The `data:` lines joined with `\n`, as `EventSource` would give them. */
3
+ data: string;
4
+ event?: string;
5
+ id?: string;
6
+ retry?: number;
7
+ };
8
+ export type EventStreamRead = {
9
+ status: number;
10
+ headers: Headers;
11
+ /** The events received, at most the requested count. */
12
+ events: ReceivedEvent[];
13
+ /** Comment lines (`: heartbeat` and the like) seen before the read ended. */
14
+ comments: string[];
15
+ /** True when the server ended the stream (as opposed to the reader stopping at the count). */
16
+ ended: boolean;
17
+ };
18
+ export type ReadEventsOptions = {
19
+ /** Stop and close the connection after this many events. Default 1. `Infinity` reads until the server ends the stream. */
20
+ count?: number;
21
+ headers?: Record<string, string>;
22
+ /** Give up (and fail) if the read has not finished after this many milliseconds. Default 5000. */
23
+ timeoutMs?: number;
24
+ };
25
+ /**
26
+ * GET `url` as an `EventSource` would and collect events until `count` have arrived or the
27
+ * server ends the stream. A non-200 answer (401, 403, ...) returns at once with no events.
28
+ */
29
+ export declare function readEventStream(url: string, options?: ReadEventsOptions): Promise<EventStreamRead>;