adorn-api 1.1.10 → 1.1.12

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 (68) hide show
  1. package/dist/adapter/express/index.js +1 -1
  2. package/dist/adapter/express/types.d.ts +5 -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/native/coercion.d.ts +12 -0
  18. package/dist/adapter/native/coercion.js +289 -0
  19. package/dist/adapter/native/controllers.d.ts +17 -0
  20. package/dist/adapter/native/controllers.js +215 -0
  21. package/dist/adapter/native/index.d.ts +14 -0
  22. package/dist/adapter/native/index.js +127 -0
  23. package/dist/adapter/native/openapi.d.ts +7 -0
  24. package/dist/adapter/native/openapi.js +82 -0
  25. package/dist/adapter/native/response-serializer.d.ts +5 -0
  26. package/dist/adapter/native/response-serializer.js +160 -0
  27. package/dist/adapter/native/router.d.ts +25 -0
  28. package/dist/adapter/native/router.js +68 -0
  29. package/dist/adapter/native/types.d.ts +77 -0
  30. package/dist/adapter/native/types.js +2 -0
  31. package/dist/core/auth.d.ts +11 -12
  32. package/dist/core/auth.js +2 -2
  33. package/dist/core/logger.d.ts +3 -4
  34. package/dist/core/logger.js +2 -2
  35. package/dist/core/streaming.d.ts +10 -10
  36. package/dist/core/streaming.js +31 -19
  37. package/dist/core/types.d.ts +102 -0
  38. package/dist/index.d.ts +6 -1
  39. package/dist/index.js +16 -1
  40. package/examples/fastify/app.ts +16 -0
  41. package/examples/fastify/index.ts +21 -0
  42. package/package.json +22 -18
  43. package/src/adapter/express/controllers.ts +249 -249
  44. package/src/adapter/express/types.ts +121 -160
  45. package/src/adapter/fastify/coercion.ts +369 -0
  46. package/src/adapter/fastify/controllers.ts +255 -0
  47. package/src/adapter/fastify/index.ts +53 -0
  48. package/src/adapter/fastify/multipart.ts +94 -0
  49. package/src/adapter/fastify/openapi.ts +93 -0
  50. package/src/adapter/fastify/response-serializer.ts +179 -0
  51. package/src/adapter/fastify/types.ts +119 -0
  52. package/src/adapter/native/coercion.ts +369 -0
  53. package/src/adapter/native/controllers.ts +271 -0
  54. package/src/adapter/native/index.ts +116 -0
  55. package/src/adapter/native/openapi.ts +109 -0
  56. package/src/adapter/native/response-serializer.ts +177 -0
  57. package/src/adapter/native/router.ts +90 -0
  58. package/src/adapter/native/types.ts +96 -0
  59. package/src/core/auth.ts +314 -315
  60. package/src/core/health.ts +234 -235
  61. package/src/core/logger.ts +245 -247
  62. package/src/core/streaming.ts +342 -330
  63. package/src/core/types.ts +115 -0
  64. package/src/index.ts +46 -16
  65. package/tests/e2e/fastify.e2e.test.ts +174 -0
  66. package/tests/native.test.ts +191 -0
  67. package/tests/unit/openapi-parameters.test.ts +97 -97
  68. package/tsconfig.json +14 -13
@@ -43,7 +43,7 @@ async function createExpressApp(options) {
43
43
  (0, cors_1.attachCors)(app, options.cors === true ? {} : options.cors);
44
44
  }
45
45
  if (options.jsonBody ?? true) {
46
- app.use(express_1.default.json());
46
+ app.use(express_1.default.json({ limit: options.jsonLimit }));
47
47
  }
48
48
  const inputCoercion = options.inputCoercion ?? "safe";
49
49
  await (0, controllers_1.attachControllers)(app, options.controllers, inputCoercion, options.multipart, options.validation);
@@ -1,53 +1,10 @@
1
- import type { Request, Response } from "express";
2
- import type { Constructor } from "../../core/types";
1
+ import type { Constructor, RequestContext as CoreRequestContext, UploadedFileInfo } from "../../core/types";
3
2
  import type { OpenApiInfo, OpenApiServer } from "../../core/openapi";
4
- import type { SseEmitter, StreamWriter } from "../../core/streaming";
5
- /**
6
- * Uploaded file information from multipart form data.
7
- */
8
- export interface UploadedFileInfo {
9
- /** Original filename as provided by the client */
10
- originalName: string;
11
- /** MIME type of the file */
12
- mimeType: string;
13
- /** Size of the file in bytes */
14
- size: number;
15
- /** File buffer (when using memory storage) */
16
- buffer?: Buffer;
17
- /** Path to the file on disk (when using disk storage) */
18
- path?: string;
19
- /** Field name from the form */
20
- fieldName: string;
21
- }
3
+ export { UploadedFileInfo };
22
4
  /**
23
5
  * Request context provided to route handlers.
24
6
  */
25
- export interface RequestContext<TBody = unknown, TQuery extends object | undefined = Record<string, unknown>, TParams extends object | undefined = Record<string, string | number | boolean | undefined>, THeaders extends object | undefined = Record<string, string | string[] | undefined>, TFiles extends Record<string, UploadedFileInfo | UploadedFileInfo[]> | undefined = undefined> {
26
- /** Express request object */
27
- req: Request;
28
- /** Express response object */
29
- res: Response;
30
- /** Parsed request body */
31
- body: TBody;
32
- /** Parsed query parameters */
33
- query: TQuery;
34
- /** Parsed path parameters */
35
- params: TParams;
36
- /** Request headers */
37
- headers: THeaders;
38
- /** Uploaded files (when using multipart handling) */
39
- files: TFiles;
40
- /**
41
- * Server-Sent Events emitter for streaming events to client.
42
- * Only available on routes marked with @Sse decorator.
43
- */
44
- sse?: SseEmitter;
45
- /**
46
- * Stream writer for streaming responses.
47
- * Available on routes marked with @Streaming or @Sse decorator.
48
- */
49
- stream?: StreamWriter;
50
- }
7
+ export type RequestContext<TBody = any, TQuery extends object | undefined = Record<string, any>, TParams extends object | undefined = Record<string, any>, THeaders extends object | undefined = Record<string, any>, TFiles extends Record<string, UploadedFileInfo | UploadedFileInfo[]> | undefined = any> = CoreRequestContext<TBody, TQuery, TParams, THeaders, TFiles>;
51
8
  /**
52
9
  * Input coercion modes.
53
10
  */
@@ -129,6 +86,8 @@ export interface ExpressAdapterOptions {
129
86
  controllers: Constructor[];
130
87
  /** Whether to enable JSON body parsing */
131
88
  jsonBody?: boolean;
89
+ /** Max JSON body size (e.g. "50mb"). Defaults to Express's "100kb". */
90
+ jsonLimit?: string;
132
91
  /** OpenAPI configuration */
133
92
  openApi?: OpenApiExpressOptions;
134
93
  /** Input coercion setting */
@@ -0,0 +1,12 @@
1
+ import { type InputMeta } from "../../core/metadata";
2
+ import type { InputCoercionMode } from "./types";
3
+ export type InputLocation = "params" | "query" | "body";
4
+ interface CoerceInputOptions {
5
+ mode: InputCoercionMode;
6
+ location: InputLocation;
7
+ }
8
+ /**
9
+ * Creates an input coercer function for the given input metadata.
10
+ */
11
+ export declare function createInputCoercer<T extends Record<string, unknown> = Record<string, unknown>>(input: InputMeta | undefined, options: CoerceInputOptions): ((value: T) => T) | undefined;
12
+ export {};
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createInputCoercer = createInputCoercer;
4
+ const metadata_1 = require("../../core/metadata");
5
+ const coerce_1 = require("../../core/coerce");
6
+ const errors_1 = require("../../core/errors");
7
+ /**
8
+ * Creates an input coercer function for the given input metadata.
9
+ */
10
+ function createInputCoercer(input, options) {
11
+ if (!input) {
12
+ return undefined;
13
+ }
14
+ const fields = extractFields(input.schema);
15
+ if (!fields.length) {
16
+ return undefined;
17
+ }
18
+ return (value) => {
19
+ const result = coerceRecord(value, fields, options.mode);
20
+ if (options.mode === "strict" && result.invalidFields.length) {
21
+ throw new errors_1.HttpError(400, buildInvalidMessage(options.location, result.invalidFields));
22
+ }
23
+ return result.value;
24
+ };
25
+ }
26
+ function coerceRecord(value, fields, mode) {
27
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
28
+ return { value, invalidFields: [] };
29
+ }
30
+ const input = value;
31
+ let changed = false;
32
+ const output = { ...input };
33
+ const invalidFields = [];
34
+ for (const field of fields) {
35
+ if (!(field.name in input)) {
36
+ continue;
37
+ }
38
+ const original = input[field.name];
39
+ const result = coerceValue(original, field.schema, mode);
40
+ if (!result.ok && mode === "strict") {
41
+ invalidFields.push(field.name);
42
+ }
43
+ if (result.changed) {
44
+ output[field.name] = result.value;
45
+ changed = true;
46
+ }
47
+ }
48
+ return { value: changed ? output : value, invalidFields };
49
+ }
50
+ function coerceValue(value, schema, mode) {
51
+ switch (schema.kind) {
52
+ case "integer":
53
+ return coerceNumber(value, schema, true);
54
+ case "number":
55
+ return coerceNumber(value, schema, false);
56
+ case "boolean": {
57
+ return coerceBoolean(value);
58
+ }
59
+ case "string": {
60
+ return coerceString(value, schema);
61
+ }
62
+ case "array":
63
+ return coerceArrayValue(value, schema, mode);
64
+ case "object":
65
+ return coerceObjectValue(value, schema, mode);
66
+ case "record":
67
+ return coerceRecordValue(value, schema, mode);
68
+ case "ref":
69
+ return coerceRefValue(value, schema, mode);
70
+ case "union":
71
+ return coerceUnionValue(value, schema, mode);
72
+ default:
73
+ return { value, ok: true, changed: false };
74
+ }
75
+ }
76
+ function coerceNumber(value, schema, integer) {
77
+ if (!isPresent(value)) {
78
+ return { value, ok: true, changed: false };
79
+ }
80
+ const parsed = integer
81
+ ? coerce_1.coerce.integer(value, { min: schema.minimum, max: schema.maximum })
82
+ : coerce_1.coerce.number(value, { min: schema.minimum, max: schema.maximum });
83
+ if (parsed === undefined) {
84
+ return { value, ok: false, changed: false };
85
+ }
86
+ if (schema.exclusiveMinimum !== undefined && parsed <= schema.exclusiveMinimum) {
87
+ return { value, ok: false, changed: false };
88
+ }
89
+ if (schema.exclusiveMaximum !== undefined && parsed >= schema.exclusiveMaximum) {
90
+ return { value, ok: false, changed: false };
91
+ }
92
+ return { value: parsed, ok: true, changed: parsed !== value };
93
+ }
94
+ function coerceBoolean(value) {
95
+ if (!isPresent(value)) {
96
+ return { value, ok: true, changed: false };
97
+ }
98
+ const parsed = coerce_1.coerce.boolean(value);
99
+ if (parsed === undefined) {
100
+ return { value, ok: false, changed: false };
101
+ }
102
+ return { value: parsed, ok: true, changed: parsed !== value };
103
+ }
104
+ function coerceString(value, schema) {
105
+ if (!isPresent(value)) {
106
+ return { value, ok: true, changed: false };
107
+ }
108
+ if (schema.format === "date" || schema.format === "date-time") {
109
+ const parsed = parseDateValue(value);
110
+ if (!parsed) {
111
+ return { value, ok: false, changed: false };
112
+ }
113
+ return { value: parsed, ok: true, changed: parsed !== value };
114
+ }
115
+ const parsed = coerce_1.coerce.string(value);
116
+ if (parsed === undefined) {
117
+ return { value, ok: true, changed: false };
118
+ }
119
+ return { value: parsed, ok: true, changed: parsed !== value };
120
+ }
121
+ function parseDateValue(value) {
122
+ if (value instanceof Date) {
123
+ return Number.isNaN(value.getTime()) ? undefined : value;
124
+ }
125
+ const text = coerce_1.coerce.string(value);
126
+ if (text === undefined) {
127
+ return undefined;
128
+ }
129
+ const parsed = new Date(text);
130
+ if (Number.isNaN(parsed.getTime())) {
131
+ return undefined;
132
+ }
133
+ return parsed;
134
+ }
135
+ function coerceArrayValue(value, schema, mode) {
136
+ if (value === undefined || value === null) {
137
+ return { value, ok: true, changed: false };
138
+ }
139
+ let input;
140
+ let changed;
141
+ if (Array.isArray(value)) {
142
+ input = value;
143
+ changed = false;
144
+ }
145
+ else if (typeof value === "string" && value.includes(",")) {
146
+ input = value.split(",").map((s) => s.trim());
147
+ changed = true;
148
+ }
149
+ else {
150
+ input = [value];
151
+ changed = true;
152
+ }
153
+ let ok = true;
154
+ const output = input.map((entry) => {
155
+ const result = coerceValue(entry, schema.items, mode);
156
+ if (!result.ok) {
157
+ ok = false;
158
+ }
159
+ if (result.changed) {
160
+ changed = true;
161
+ }
162
+ return result.value;
163
+ });
164
+ return { value: changed ? output : value, ok, changed };
165
+ }
166
+ function coerceObjectValue(value, schema, mode) {
167
+ if (value === undefined || value === null) {
168
+ return { value, ok: true, changed: false };
169
+ }
170
+ if (typeof value !== "object" || Array.isArray(value)) {
171
+ return { value, ok: mode === "safe", changed: false };
172
+ }
173
+ const properties = schema.properties ?? {};
174
+ const fields = Object.entries(properties).map(([name, fieldSchema]) => ({
175
+ name,
176
+ schema: fieldSchema
177
+ }));
178
+ if (!fields.length) {
179
+ return { value, ok: true, changed: false };
180
+ }
181
+ const result = coerceRecord(value, fields, mode);
182
+ return {
183
+ value: result.value,
184
+ ok: result.invalidFields.length === 0,
185
+ changed: result.value !== value
186
+ };
187
+ }
188
+ function coerceRecordValue(value, schema, mode) {
189
+ if (value === undefined || value === null) {
190
+ return { value, ok: true, changed: false };
191
+ }
192
+ if (typeof value !== "object" || Array.isArray(value)) {
193
+ return { value, ok: mode === "safe", changed: false };
194
+ }
195
+ const input = value;
196
+ let changed = false;
197
+ let ok = true;
198
+ const output = { ...input };
199
+ for (const [key, entry] of Object.entries(input)) {
200
+ const result = coerceValue(entry, schema.values, mode);
201
+ if (!result.ok) {
202
+ ok = false;
203
+ }
204
+ if (result.changed) {
205
+ output[key] = result.value;
206
+ changed = true;
207
+ }
208
+ }
209
+ return { value: changed ? output : value, ok, changed };
210
+ }
211
+ function coerceRefValue(value, schema, mode) {
212
+ if (value === undefined || value === null) {
213
+ return { value, ok: true, changed: false };
214
+ }
215
+ if (typeof value !== "object" || Array.isArray(value)) {
216
+ return { value, ok: mode === "safe", changed: false };
217
+ }
218
+ const meta = getDtoMetaSafe(schema.dto);
219
+ const fields = Object.entries(meta.fields).map(([name, field]) => ({
220
+ name,
221
+ schema: field.schema
222
+ }));
223
+ if (!fields.length) {
224
+ return { value, ok: true, changed: false };
225
+ }
226
+ const result = coerceRecord(value, fields, mode);
227
+ return {
228
+ value: result.value,
229
+ ok: result.invalidFields.length === 0,
230
+ changed: result.value !== value
231
+ };
232
+ }
233
+ function coerceUnionValue(value, schema, mode) {
234
+ let fallback;
235
+ for (const option of schema.anyOf) {
236
+ const result = coerceValue(value, option, mode);
237
+ if (!result.ok) {
238
+ continue;
239
+ }
240
+ if (result.changed) {
241
+ return result;
242
+ }
243
+ fallback ??= result;
244
+ }
245
+ if (fallback) {
246
+ return fallback;
247
+ }
248
+ return { value, ok: mode === "safe", changed: false };
249
+ }
250
+ function extractFields(schema) {
251
+ if (isSchemaNode(schema)) {
252
+ if (schema.kind === "object" && schema.properties) {
253
+ return Object.entries(schema.properties).map(([name, fieldSchema]) => ({
254
+ name,
255
+ schema: fieldSchema
256
+ }));
257
+ }
258
+ return [];
259
+ }
260
+ const meta = getDtoMetaSafe(schema);
261
+ return Object.entries(meta.fields).map(([name, field]) => ({
262
+ name,
263
+ schema: field.schema
264
+ }));
265
+ }
266
+ function getDtoMetaSafe(dto) {
267
+ const meta = (0, metadata_1.getDtoMeta)(dto);
268
+ if (!meta) {
269
+ throw new Error(`DTO "${dto.name}" is missing @Dto decorator.`);
270
+ }
271
+ return meta;
272
+ }
273
+ function isSchemaNode(value) {
274
+ return !!value && typeof value === "object" && "kind" in value;
275
+ }
276
+ function isPresent(value) {
277
+ return coerce_1.coerce.string(value) !== undefined;
278
+ }
279
+ function buildInvalidMessage(location, fields) {
280
+ let label = "query parameter";
281
+ if (location === "params") {
282
+ label = "path parameter";
283
+ }
284
+ else if (location === "body") {
285
+ label = "request body field";
286
+ }
287
+ const suffix = fields.length > 1 ? "s" : "";
288
+ return `Invalid ${label}${suffix}: ${fields.join(", ")}.`;
289
+ }
@@ -0,0 +1,7 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ import type { Constructor } from "../../core/types";
3
+ import type { InputCoercionSetting, MultipartOptions, ValidationOptions } from "./types";
4
+ /**
5
+ * Attaches controllers to a Fastify application.
6
+ */
7
+ export declare function attachControllers(app: FastifyInstance, controllers: Constructor[], inputCoercion?: InputCoercionSetting, multipart?: boolean | MultipartOptions, validation?: boolean | ValidationOptions): Promise<void>;
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachControllers = attachControllers;
4
+ const metadata_1 = require("../../core/metadata");
5
+ const auth_1 = require("../../core/auth");
6
+ const errors_1 = require("../../core/errors");
7
+ const response_1 = require("../../core/response");
8
+ const coercion_1 = require("./coercion");
9
+ const response_serializer_1 = require("./response-serializer");
10
+ const multipart_1 = require("./multipart");
11
+ const lifecycle_1 = require("../../core/lifecycle");
12
+ const streaming_1 = require("../../core/streaming");
13
+ const validation_1 = require("../../core/validation");
14
+ const validation_errors_1 = require("../../core/validation-errors");
15
+ /**
16
+ * Attaches controllers to a Fastify application.
17
+ */
18
+ async function attachControllers(app, controllers, inputCoercion = "safe", multipart, validation) {
19
+ const multipartOptions = (0, multipart_1.normalizeMultipartOptions)(multipart);
20
+ for (const controller of controllers) {
21
+ const meta = (0, metadata_1.getControllerMeta)(controller);
22
+ if (!meta) {
23
+ throw new Error(`Controller "${controller.name}" is missing @Controller decorator.`);
24
+ }
25
+ const instance = new controller();
26
+ lifecycle_1.lifecycleRegistry.register(instance);
27
+ await lifecycle_1.lifecycleRegistry.callOnModuleInit(instance);
28
+ for (const route of meta.routes) {
29
+ const path = joinPaths(meta.basePath, route.path);
30
+ const handler = instance[route.handlerName];
31
+ if (typeof handler !== "function") {
32
+ throw new Error(`Handler "${String(route.handlerName)}" is not a function on ${controller.name}.`);
33
+ }
34
+ const coerceParams = inputCoercion === false
35
+ ? undefined
36
+ : (0, coercion_1.createInputCoercer)(route.params, { mode: inputCoercion, location: "params" });
37
+ const coerceQuery = inputCoercion === false
38
+ ? undefined
39
+ : (0, coercion_1.createInputCoercer)(route.query, { mode: inputCoercion, location: "query" });
40
+ const coerceBody = inputCoercion === false
41
+ ? undefined
42
+ : (0, coercion_1.createInputCoercer)(route.body, { mode: inputCoercion, location: "body" });
43
+ const isValidationEnabled = validation !== false && validation?.enabled !== false;
44
+ const authMeta = (0, auth_1.getRouteAuthMeta)(controller, route.handlerName);
45
+ app.route({
46
+ method: route.httpMethod.toUpperCase(),
47
+ url: path,
48
+ handler: async (req, reply) => {
49
+ try {
50
+ // Apply auth guard if metadata exists
51
+ if (authMeta && authMeta.requiresAuth && !authMeta.isPublic) {
52
+ const user = req.user || req.raw.user;
53
+ if (!user) {
54
+ throw new errors_1.HttpError(401, "Unauthorized");
55
+ }
56
+ if (authMeta.roles?.length) {
57
+ const hasRole = authMeta.roles.some((role) => user.roles?.includes(role));
58
+ if (!hasRole) {
59
+ throw new errors_1.HttpError(403, "Insufficient permissions");
60
+ }
61
+ }
62
+ if (authMeta.allRoles?.length) {
63
+ const hasAllRoles = authMeta.allRoles.every((role) => user.roles?.includes(role));
64
+ if (!hasAllRoles) {
65
+ throw new errors_1.HttpError(403, "Insufficient permissions");
66
+ }
67
+ }
68
+ if (authMeta.guard) {
69
+ const allowed = await authMeta.guard(user, req);
70
+ if (!allowed) {
71
+ throw new errors_1.HttpError(403, "Access denied by guard");
72
+ }
73
+ }
74
+ }
75
+ let files = undefined;
76
+ if (multipartOptions && (0, multipart_1.hasFileUploads)(route.files)) {
77
+ files = await (0, multipart_1.extractFiles)(req);
78
+ }
79
+ const body = req.body;
80
+ const query = (coerceQuery && req.query && Object.keys(req.query).length > 0) ? coerceQuery(req.query) : req.query;
81
+ const params = (coerceParams && req.params && Object.keys(req.params).length > 0) ? coerceParams(req.params) : req.params;
82
+ if (isValidationEnabled) {
83
+ const validationErrors = [];
84
+ if (route.body) {
85
+ const bodyErrors = (0, validation_1.validate)(body, route.body.schema);
86
+ validationErrors.push(...bodyErrors);
87
+ }
88
+ if (route.query) {
89
+ const queryErrors = (0, validation_1.validate)(query, route.query.schema);
90
+ validationErrors.push(...queryErrors);
91
+ }
92
+ if (route.params) {
93
+ const paramsErrors = (0, validation_1.validate)(params, route.params.schema);
94
+ validationErrors.push(...paramsErrors);
95
+ }
96
+ if (route.headers) {
97
+ const headersErrors = (0, validation_1.validate)(req.headers, route.headers.schema);
98
+ validationErrors.push(...headersErrors);
99
+ }
100
+ if (validationErrors.length > 0) {
101
+ throw new validation_errors_1.ValidationErrors(validationErrors);
102
+ }
103
+ }
104
+ const ctx = {
105
+ req,
106
+ res: reply.raw,
107
+ body,
108
+ query,
109
+ params,
110
+ headers: req.headers,
111
+ files,
112
+ sse: route.sse ? (0, streaming_1.createSseEmitter)(reply.raw) : undefined,
113
+ stream: route.streaming || route.sse ? (0, streaming_1.createStreamWriter)(reply.raw) : undefined
114
+ };
115
+ const result = await handler.call(instance, ctx);
116
+ if (reply.sent || route.sse || route.streaming) {
117
+ return;
118
+ }
119
+ if ((0, response_1.isHttpResponse)(result)) {
120
+ if (result.headers) {
121
+ reply.headers(result.headers);
122
+ }
123
+ if (result.body === undefined) {
124
+ reply.status(result.status).send();
125
+ }
126
+ else if (route.raw) {
127
+ if (!reply.getHeader("Content-Type")) {
128
+ const ct = getResponseContentType(route) ?? "application/octet-stream";
129
+ reply.type(ct);
130
+ }
131
+ reply.status(result.status).send(result.body);
132
+ }
133
+ else {
134
+ const responseSchema = getResponseSchemaForStatus(route, result.status);
135
+ const output = responseSchema ? (0, response_serializer_1.serializeResponse)(result.body, responseSchema) : result.body;
136
+ reply.status(result.status).send(output);
137
+ }
138
+ return;
139
+ }
140
+ if (result === undefined) {
141
+ reply.status(defaultStatus(route)).send();
142
+ return;
143
+ }
144
+ if (route.raw) {
145
+ if (!reply.getHeader("Content-Type")) {
146
+ const ct = getResponseContentType(route) ?? "application/octet-stream";
147
+ reply.type(ct);
148
+ }
149
+ reply.status(defaultStatus(route)).send(result);
150
+ }
151
+ else {
152
+ const responseSchema = getResponseSchema(route);
153
+ const output = responseSchema ? (0, response_serializer_1.serializeResponse)(result, responseSchema) : result;
154
+ reply.status(defaultStatus(route)).send(output);
155
+ }
156
+ }
157
+ catch (error) {
158
+ if ((0, validation_errors_1.isValidationErrors)(error)) {
159
+ reply.status(error.status).send(error.body);
160
+ return;
161
+ }
162
+ if ((0, errors_1.isHttpError)(error)) {
163
+ if (error.headers) {
164
+ reply.headers(error.headers);
165
+ }
166
+ const body = error.body ?? { message: error.message };
167
+ reply.status(error.status).send(body);
168
+ return;
169
+ }
170
+ throw error;
171
+ }
172
+ }
173
+ });
174
+ }
175
+ }
176
+ }
177
+ function defaultStatus(route) {
178
+ const responses = route.responses ?? [];
179
+ const success = responses.find((response) => !response.error && response.status < 400);
180
+ return success?.status ?? 200;
181
+ }
182
+ function getResponseSchema(route) {
183
+ const responses = route.responses ?? [];
184
+ const success = responses.find((response) => !response.error && response.status < 400);
185
+ return success?.schema;
186
+ }
187
+ function getResponseContentType(route) {
188
+ const responses = route.responses ?? [];
189
+ const success = responses.find((r) => !r.error && r.status < 400);
190
+ return success?.contentType;
191
+ }
192
+ function getResponseSchemaForStatus(route, status) {
193
+ const responses = route.responses ?? [];
194
+ const response = responses.find((r) => r.status === status);
195
+ return response?.schema;
196
+ }
197
+ function joinPaths(base, path) {
198
+ const normalizedBase = base.replace(/\/+$/, "");
199
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
200
+ return `${normalizedBase}${normalizedPath}`;
201
+ }
@@ -0,0 +1,14 @@
1
+ import type { FastifyAdapterOptions } from "./types";
2
+ export * from "./types";
3
+ export { attachControllers } from "./controllers";
4
+ export { attachOpenApi } from "./openapi";
5
+ /**
6
+ * Creates a Fastify application with Adorn controllers.
7
+ * @param options - Fastify adapter options
8
+ * @returns Configured Fastify application
9
+ */
10
+ export declare function createFastifyApp(options: FastifyAdapterOptions): Promise<any>;
11
+ /**
12
+ * Trigger shutdown hooks for graceful application shutdown.
13
+ */
14
+ export declare function shutdownApp(signal?: string): Promise<void>;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.attachOpenApi = exports.attachControllers = void 0;
21
+ exports.createFastifyApp = createFastifyApp;
22
+ exports.shutdownApp = shutdownApp;
23
+ const fastify_1 = __importDefault(require("fastify"));
24
+ const controllers_1 = require("./controllers");
25
+ const openapi_1 = require("./openapi");
26
+ const lifecycle_1 = require("../../core/lifecycle");
27
+ const cors_1 = __importDefault(require("@fastify/cors"));
28
+ const multipart_1 = __importDefault(require("@fastify/multipart"));
29
+ __exportStar(require("./types"), exports);
30
+ var controllers_2 = require("./controllers");
31
+ Object.defineProperty(exports, "attachControllers", { enumerable: true, get: function () { return controllers_2.attachControllers; } });
32
+ var openapi_2 = require("./openapi");
33
+ Object.defineProperty(exports, "attachOpenApi", { enumerable: true, get: function () { return openapi_2.attachOpenApi; } });
34
+ /**
35
+ * Creates a Fastify application with Adorn controllers.
36
+ * @param options - Fastify adapter options
37
+ * @returns Configured Fastify application
38
+ */
39
+ async function createFastifyApp(options) {
40
+ const app = (0, fastify_1.default)({
41
+ bodyLimit: options.bodyLimit
42
+ });
43
+ if (options.cors) {
44
+ app.register(cors_1.default, options.cors === true ? {} : options.cors);
45
+ }
46
+ if (options.multipart) {
47
+ app.register(multipart_1.default, {
48
+ limits: {
49
+ fileSize: typeof options.multipart === "object" ? options.multipart.maxFileSize : undefined
50
+ }
51
+ });
52
+ }
53
+ const inputCoercion = options.inputCoercion ?? "safe";
54
+ await (0, controllers_1.attachControllers)(app, options.controllers, inputCoercion, options.multipart, options.validation);
55
+ if (options.openApi) {
56
+ (0, openapi_1.attachOpenApi)(app, options.controllers, options.openApi);
57
+ }
58
+ await lifecycle_1.lifecycleRegistry.callOnApplicationBootstrap();
59
+ return app;
60
+ }
61
+ /**
62
+ * Trigger shutdown hooks for graceful application shutdown.
63
+ */
64
+ async function shutdownApp(signal) {
65
+ await lifecycle_1.lifecycleRegistry.callShutdownHooks(signal);
66
+ lifecycle_1.lifecycleRegistry.clear();
67
+ }