adorn-api 1.1.11 → 1.1.13

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 (75) hide show
  1. package/README.md +18 -0
  2. package/dist/adapter/express/types.d.ts +3 -46
  3. package/dist/adapter/fastify/coercion.d.ts +12 -0
  4. package/dist/adapter/fastify/coercion.js +289 -0
  5. package/dist/adapter/fastify/controllers.d.ts +7 -0
  6. package/dist/adapter/fastify/controllers.js +201 -0
  7. package/dist/adapter/fastify/index.d.ts +14 -0
  8. package/dist/adapter/fastify/index.js +67 -0
  9. package/dist/adapter/fastify/multipart.d.ts +26 -0
  10. package/dist/adapter/fastify/multipart.js +75 -0
  11. package/dist/adapter/fastify/openapi.d.ts +10 -0
  12. package/dist/adapter/fastify/openapi.js +76 -0
  13. package/dist/adapter/fastify/response-serializer.d.ts +2 -0
  14. package/dist/adapter/fastify/response-serializer.js +162 -0
  15. package/dist/adapter/fastify/types.d.ts +100 -0
  16. package/dist/adapter/fastify/types.js +2 -0
  17. package/dist/adapter/metal-orm/index.d.ts +1 -1
  18. package/dist/adapter/metal-orm/types.d.ts +23 -0
  19. package/dist/adapter/native/coercion.d.ts +12 -0
  20. package/dist/adapter/native/coercion.js +289 -0
  21. package/dist/adapter/native/controllers.d.ts +17 -0
  22. package/dist/adapter/native/controllers.js +215 -0
  23. package/dist/adapter/native/index.d.ts +14 -0
  24. package/dist/adapter/native/index.js +127 -0
  25. package/dist/adapter/native/openapi.d.ts +7 -0
  26. package/dist/adapter/native/openapi.js +82 -0
  27. package/dist/adapter/native/response-serializer.d.ts +5 -0
  28. package/dist/adapter/native/response-serializer.js +160 -0
  29. package/dist/adapter/native/router.d.ts +25 -0
  30. package/dist/adapter/native/router.js +68 -0
  31. package/dist/adapter/native/types.d.ts +77 -0
  32. package/dist/adapter/native/types.js +2 -0
  33. package/dist/core/auth.d.ts +11 -12
  34. package/dist/core/auth.js +2 -2
  35. package/dist/core/logger.d.ts +3 -4
  36. package/dist/core/logger.js +2 -2
  37. package/dist/core/streaming.d.ts +10 -10
  38. package/dist/core/streaming.js +31 -19
  39. package/dist/core/types.d.ts +102 -0
  40. package/dist/index.d.ts +6 -1
  41. package/dist/index.js +16 -1
  42. package/examples/fastify/app.ts +16 -0
  43. package/examples/fastify/index.ts +21 -0
  44. package/package.json +24 -18
  45. package/src/adapter/express/controllers.ts +249 -249
  46. package/src/adapter/express/types.ts +121 -160
  47. package/src/adapter/fastify/coercion.ts +369 -0
  48. package/src/adapter/fastify/controllers.ts +255 -0
  49. package/src/adapter/fastify/index.ts +53 -0
  50. package/src/adapter/fastify/multipart.ts +94 -0
  51. package/src/adapter/fastify/openapi.ts +93 -0
  52. package/src/adapter/fastify/response-serializer.ts +179 -0
  53. package/src/adapter/fastify/types.ts +119 -0
  54. package/src/adapter/metal-orm/index.ts +3 -0
  55. package/src/adapter/metal-orm/types.ts +25 -0
  56. package/src/adapter/native/coercion.ts +369 -0
  57. package/src/adapter/native/controllers.ts +271 -0
  58. package/src/adapter/native/index.ts +116 -0
  59. package/src/adapter/native/openapi.ts +109 -0
  60. package/src/adapter/native/response-serializer.ts +177 -0
  61. package/src/adapter/native/router.ts +90 -0
  62. package/src/adapter/native/types.ts +96 -0
  63. package/src/core/auth.ts +314 -315
  64. package/src/core/health.ts +234 -235
  65. package/src/core/logger.ts +245 -247
  66. package/src/core/streaming.ts +342 -330
  67. package/src/core/types.ts +115 -0
  68. package/src/index.ts +46 -16
  69. package/tests/e2e/fastify.e2e.test.ts +174 -0
  70. package/tests/native.test.ts +191 -0
  71. package/tests/typecheck/query-params.typecheck.ts +42 -0
  72. package/tests/unit/openapi-parameters.test.ts +97 -97
  73. package/tsconfig.json +14 -13
  74. package/tsconfig.typecheck.json +8 -0
  75. package/vitest.config.ts +47 -7
@@ -1,235 +1,234 @@
1
- import { Dto, Field, Controller, Get, Returns, Doc } from "./decorators";
2
- import { t } from "./schema";
3
- import type { Constructor } from "./types";
4
- import type { RequestContext } from "../adapter/express/types";
5
-
6
- /**
7
- * Health status values.
8
- */
9
- export type HealthStatus = "healthy" | "degraded" | "unhealthy";
10
-
11
- /**
12
- * Result of a single health indicator check.
13
- */
14
- export interface HealthIndicatorResult {
15
- /** Status of this indicator */
16
- status: HealthStatus;
17
- /** Optional message providing details */
18
- message?: string;
19
- /** Optional additional data */
20
- data?: Record<string, unknown>;
21
- }
22
-
23
- /**
24
- * Function that performs a health check.
25
- */
26
- export type HealthIndicator = () => HealthIndicatorResult | Promise<HealthIndicatorResult>;
27
-
28
- /**
29
- * Named health indicator with a key and check function.
30
- */
31
- export interface NamedHealthIndicator {
32
- /** Unique key identifying this indicator */
33
- key: string;
34
- /** The health check function */
35
- check: HealthIndicator;
36
- }
37
-
38
- /**
39
- * Options for creating a health controller.
40
- */
41
- export interface HealthControllerOptions {
42
- /** Base path for health endpoints (default: "/health") */
43
- path?: string;
44
- /** Array of health indicators to check */
45
- indicators?: NamedHealthIndicator[];
46
- /** Tags for OpenAPI documentation */
47
- tags?: string[];
48
- }
49
-
50
- /**
51
- * DTO for individual component health status.
52
- */
53
- @Dto({ description: "Health status of a single component" })
54
- export class HealthComponentDto {
55
- @Field(t.enum(["healthy", "degraded", "unhealthy"], { description: "Component status" }))
56
- status!: HealthStatus;
57
-
58
- @Field(t.optional(t.string({ description: "Additional status message" })))
59
- message?: string;
60
-
61
- @Field(t.optional(t.record(t.any(), { description: "Additional data" })))
62
- data?: Record<string, unknown>;
63
- }
64
-
65
- /**
66
- * DTO for overall health check response.
67
- */
68
- @Dto({ description: "Overall health check response" })
69
- export class HealthResponseDto {
70
- @Field(t.enum(["healthy", "degraded", "unhealthy"], { description: "Overall status" }))
71
- status!: HealthStatus;
72
-
73
- @Field(t.optional(t.record(t.ref(HealthComponentDto), { description: "Component statuses" })))
74
- components?: Record<string, HealthComponentDto>;
75
-
76
- @Field(t.string({ description: "Timestamp of the health check" }))
77
- timestamp!: string;
78
- }
79
-
80
- /**
81
- * DTO for simple liveness probe response.
82
- */
83
- @Dto({ description: "Simple liveness probe response" })
84
- export class LivenessDto {
85
- @Field(t.literal("ok", { description: "Liveness status" }))
86
- status!: "ok";
87
- }
88
-
89
- /**
90
- * Determines the overall status from component statuses.
91
- */
92
- function aggregateStatus(statuses: HealthStatus[]): HealthStatus {
93
- if (statuses.some((s) => s === "unhealthy")) {
94
- return "unhealthy";
95
- }
96
- if (statuses.some((s) => s === "degraded")) {
97
- return "degraded";
98
- }
99
- return "healthy";
100
- }
101
-
102
- /**
103
- * Creates a health check controller with the specified options.
104
- * @param options - Health controller configuration
105
- * @returns Controller class for health endpoints
106
- */
107
- export function createHealthController(options: HealthControllerOptions = {}): Constructor {
108
- const basePath = options.path ?? "/health";
109
- const indicators = options.indicators ?? [];
110
- const tags = options.tags ?? ["Health"];
111
-
112
- @Controller({ path: basePath, tags })
113
- class HealthController {
114
- @Get("/")
115
- @Doc({ summary: "Health check", description: "Returns the health status of the application and its components" })
116
- @Returns(HealthResponseDto)
117
- async check(_ctx: RequestContext): Promise<HealthResponseDto> {
118
- const components: Record<string, HealthComponentDto> = {};
119
- const statuses: HealthStatus[] = [];
120
-
121
- for (const indicator of indicators) {
122
- try {
123
- const result = await indicator.check();
124
- components[indicator.key] = {
125
- status: result.status,
126
- message: result.message,
127
- data: result.data
128
- };
129
- statuses.push(result.status);
130
- } catch (error) {
131
- components[indicator.key] = {
132
- status: "unhealthy",
133
- message: error instanceof Error ? error.message : "Unknown error"
134
- };
135
- statuses.push("unhealthy");
136
- }
137
- }
138
-
139
- return {
140
- status: statuses.length > 0 ? aggregateStatus(statuses) : "healthy",
141
- components: Object.keys(components).length > 0 ? components : undefined,
142
- timestamp: new Date().toISOString()
143
- };
144
- }
145
-
146
- @Get("/live")
147
- @Doc({ summary: "Liveness probe", description: "Simple liveness check for container orchestration" })
148
- @Returns(LivenessDto)
149
- live(_ctx: RequestContext): LivenessDto {
150
- return { status: "ok" };
151
- }
152
-
153
- @Get("/ready")
154
- @Doc({ summary: "Readiness probe", description: "Checks if application is ready to receive traffic" })
155
- @Returns(HealthResponseDto)
156
- async ready(_ctx: RequestContext): Promise<HealthResponseDto> {
157
- return this.check(_ctx);
158
- }
159
- }
160
-
161
- return HealthController;
162
- }
163
-
164
- /**
165
- * Creates a database health indicator.
166
- * @param name - Name of the database
167
- * @param pingFn - Function to ping the database (should throw on failure)
168
- * @returns Named health indicator
169
- */
170
- export function databaseIndicator(
171
- name: string,
172
- pingFn: () => Promise<void> | void
173
- ): NamedHealthIndicator {
174
- return {
175
- key: name,
176
- check: async () => {
177
- try {
178
- await pingFn();
179
- return { status: "healthy" };
180
- } catch (error) {
181
- return {
182
- status: "unhealthy",
183
- message: error instanceof Error ? error.message : "Database unreachable"
184
- };
185
- }
186
- }
187
- };
188
- }
189
-
190
- /**
191
- * Creates a memory usage health indicator.
192
- * @param thresholds - Memory thresholds in MB
193
- * @returns Named health indicator
194
- */
195
- export function memoryIndicator(thresholds?: {
196
- degradedMB?: number;
197
- unhealthyMB?: number;
198
- }): NamedHealthIndicator {
199
- const degradedMB = thresholds?.degradedMB ?? 512;
200
- const unhealthyMB = thresholds?.unhealthyMB ?? 1024;
201
-
202
- return {
203
- key: "memory",
204
- check: () => {
205
- const usage = process.memoryUsage();
206
- const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
207
-
208
- let status: HealthStatus = "healthy";
209
- if (heapUsedMB >= unhealthyMB) {
210
- status = "unhealthy";
211
- } else if (heapUsedMB >= degradedMB) {
212
- status = "degraded";
213
- }
214
-
215
- return {
216
- status,
217
- data: {
218
- heapUsedMB,
219
- heapTotalMB: Math.round(usage.heapTotal / 1024 / 1024),
220
- rssMB: Math.round(usage.rss / 1024 / 1024)
221
- }
222
- };
223
- }
224
- };
225
- }
226
-
227
- /**
228
- * Creates a custom health indicator.
229
- * @param key - Unique identifier for this indicator
230
- * @param check - Health check function
231
- * @returns Named health indicator
232
- */
233
- export function customIndicator(key: string, check: HealthIndicator): NamedHealthIndicator {
234
- return { key, check };
235
- }
1
+ import { Dto, Field, Controller, Get, Returns, Doc } from "./decorators";
2
+ import { t } from "./schema";
3
+ import type { Constructor, RequestContext } from "./types";
4
+
5
+ /**
6
+ * Health status values.
7
+ */
8
+ export type HealthStatus = "healthy" | "degraded" | "unhealthy";
9
+
10
+ /**
11
+ * Result of a single health indicator check.
12
+ */
13
+ export interface HealthIndicatorResult {
14
+ /** Status of this indicator */
15
+ status: HealthStatus;
16
+ /** Optional message providing details */
17
+ message?: string;
18
+ /** Optional additional data */
19
+ data?: Record<string, unknown>;
20
+ }
21
+
22
+ /**
23
+ * Function that performs a health check.
24
+ */
25
+ export type HealthIndicator = () => HealthIndicatorResult | Promise<HealthIndicatorResult>;
26
+
27
+ /**
28
+ * Named health indicator with a key and check function.
29
+ */
30
+ export interface NamedHealthIndicator {
31
+ /** Unique key identifying this indicator */
32
+ key: string;
33
+ /** The health check function */
34
+ check: HealthIndicator;
35
+ }
36
+
37
+ /**
38
+ * Options for creating a health controller.
39
+ */
40
+ export interface HealthControllerOptions {
41
+ /** Base path for health endpoints (default: "/health") */
42
+ path?: string;
43
+ /** Array of health indicators to check */
44
+ indicators?: NamedHealthIndicator[];
45
+ /** Tags for OpenAPI documentation */
46
+ tags?: string[];
47
+ }
48
+
49
+ /**
50
+ * DTO for individual component health status.
51
+ */
52
+ @Dto({ description: "Health status of a single component" })
53
+ export class HealthComponentDto {
54
+ @Field(t.enum(["healthy", "degraded", "unhealthy"], { description: "Component status" }))
55
+ status!: HealthStatus;
56
+
57
+ @Field(t.optional(t.string({ description: "Additional status message" })))
58
+ message?: string;
59
+
60
+ @Field(t.optional(t.record(t.any(), { description: "Additional data" })))
61
+ data?: Record<string, unknown>;
62
+ }
63
+
64
+ /**
65
+ * DTO for overall health check response.
66
+ */
67
+ @Dto({ description: "Overall health check response" })
68
+ export class HealthResponseDto {
69
+ @Field(t.enum(["healthy", "degraded", "unhealthy"], { description: "Overall status" }))
70
+ status!: HealthStatus;
71
+
72
+ @Field(t.optional(t.record(t.ref(HealthComponentDto), { description: "Component statuses" })))
73
+ components?: Record<string, HealthComponentDto>;
74
+
75
+ @Field(t.string({ description: "Timestamp of the health check" }))
76
+ timestamp!: string;
77
+ }
78
+
79
+ /**
80
+ * DTO for simple liveness probe response.
81
+ */
82
+ @Dto({ description: "Simple liveness probe response" })
83
+ export class LivenessDto {
84
+ @Field(t.literal("ok", { description: "Liveness status" }))
85
+ status!: "ok";
86
+ }
87
+
88
+ /**
89
+ * Determines the overall status from component statuses.
90
+ */
91
+ function aggregateStatus(statuses: HealthStatus[]): HealthStatus {
92
+ if (statuses.some((s) => s === "unhealthy")) {
93
+ return "unhealthy";
94
+ }
95
+ if (statuses.some((s) => s === "degraded")) {
96
+ return "degraded";
97
+ }
98
+ return "healthy";
99
+ }
100
+
101
+ /**
102
+ * Creates a health check controller with the specified options.
103
+ * @param options - Health controller configuration
104
+ * @returns Controller class for health endpoints
105
+ */
106
+ export function createHealthController(options: HealthControllerOptions = {}): Constructor {
107
+ const basePath = options.path ?? "/health";
108
+ const indicators = options.indicators ?? [];
109
+ const tags = options.tags ?? ["Health"];
110
+
111
+ @Controller({ path: basePath, tags })
112
+ class HealthController {
113
+ @Get("/")
114
+ @Doc({ summary: "Health check", description: "Returns the health status of the application and its components" })
115
+ @Returns(HealthResponseDto)
116
+ async check(_ctx: RequestContext): Promise<HealthResponseDto> {
117
+ const components: Record<string, HealthComponentDto> = {};
118
+ const statuses: HealthStatus[] = [];
119
+
120
+ for (const indicator of indicators) {
121
+ try {
122
+ const result = await indicator.check();
123
+ components[indicator.key] = {
124
+ status: result.status,
125
+ message: result.message,
126
+ data: result.data
127
+ };
128
+ statuses.push(result.status);
129
+ } catch (error) {
130
+ components[indicator.key] = {
131
+ status: "unhealthy",
132
+ message: error instanceof Error ? error.message : "Unknown error"
133
+ };
134
+ statuses.push("unhealthy");
135
+ }
136
+ }
137
+
138
+ return {
139
+ status: statuses.length > 0 ? aggregateStatus(statuses) : "healthy",
140
+ components: Object.keys(components).length > 0 ? components : undefined,
141
+ timestamp: new Date().toISOString()
142
+ };
143
+ }
144
+
145
+ @Get("/live")
146
+ @Doc({ summary: "Liveness probe", description: "Simple liveness check for container orchestration" })
147
+ @Returns(LivenessDto)
148
+ live(_ctx: RequestContext): LivenessDto {
149
+ return { status: "ok" };
150
+ }
151
+
152
+ @Get("/ready")
153
+ @Doc({ summary: "Readiness probe", description: "Checks if application is ready to receive traffic" })
154
+ @Returns(HealthResponseDto)
155
+ async ready(_ctx: RequestContext): Promise<HealthResponseDto> {
156
+ return this.check(_ctx);
157
+ }
158
+ }
159
+
160
+ return HealthController;
161
+ }
162
+
163
+ /**
164
+ * Creates a database health indicator.
165
+ * @param name - Name of the database
166
+ * @param pingFn - Function to ping the database (should throw on failure)
167
+ * @returns Named health indicator
168
+ */
169
+ export function databaseIndicator(
170
+ name: string,
171
+ pingFn: () => Promise<void> | void
172
+ ): NamedHealthIndicator {
173
+ return {
174
+ key: name,
175
+ check: async () => {
176
+ try {
177
+ await pingFn();
178
+ return { status: "healthy" };
179
+ } catch (error) {
180
+ return {
181
+ status: "unhealthy",
182
+ message: error instanceof Error ? error.message : "Database unreachable"
183
+ };
184
+ }
185
+ }
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Creates a memory usage health indicator.
191
+ * @param thresholds - Memory thresholds in MB
192
+ * @returns Named health indicator
193
+ */
194
+ export function memoryIndicator(thresholds?: {
195
+ degradedMB?: number;
196
+ unhealthyMB?: number;
197
+ }): NamedHealthIndicator {
198
+ const degradedMB = thresholds?.degradedMB ?? 512;
199
+ const unhealthyMB = thresholds?.unhealthyMB ?? 1024;
200
+
201
+ return {
202
+ key: "memory",
203
+ check: () => {
204
+ const usage = process.memoryUsage();
205
+ const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
206
+
207
+ let status: HealthStatus = "healthy";
208
+ if (heapUsedMB >= unhealthyMB) {
209
+ status = "unhealthy";
210
+ } else if (heapUsedMB >= degradedMB) {
211
+ status = "degraded";
212
+ }
213
+
214
+ return {
215
+ status,
216
+ data: {
217
+ heapUsedMB,
218
+ heapTotalMB: Math.round(usage.heapTotal / 1024 / 1024),
219
+ rssMB: Math.round(usage.rss / 1024 / 1024)
220
+ }
221
+ };
222
+ }
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Creates a custom health indicator.
228
+ * @param key - Unique identifier for this indicator
229
+ * @param check - Health check function
230
+ * @returns Named health indicator
231
+ */
232
+ export function customIndicator(key: string, check: HealthIndicator): NamedHealthIndicator {
233
+ return { key, check };
234
+ }