glasswork 0.2.0 → 0.2.2
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.
- package/dist/authorization/ability-middleware.d.ts +61 -0
- package/dist/authorization/ability-middleware.d.ts.map +1 -0
- package/dist/authorization/ability-middleware.js +74 -0
- package/dist/authorization/ability-middleware.js.map +1 -0
- package/dist/authorization/index.d.ts +46 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/authorization/index.js +46 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/route-helpers.d.ts +106 -0
- package/dist/authorization/route-helpers.d.ts.map +1 -0
- package/dist/authorization/route-helpers.js +136 -0
- package/dist/authorization/route-helpers.js.map +1 -0
- package/dist/authorization/service-proxy.d.ts +59 -0
- package/dist/authorization/service-proxy.d.ts.map +1 -0
- package/dist/authorization/service-proxy.js +110 -0
- package/dist/authorization/service-proxy.js.map +1 -0
- package/dist/authorization/types.d.ts +58 -0
- package/dist/authorization/types.d.ts.map +1 -0
- package/dist/authorization/types.js +7 -0
- package/dist/authorization/types.js.map +1 -0
- package/dist/hooks/authorization-hook.d.ts +100 -0
- package/dist/hooks/authorization-hook.d.ts.map +1 -0
- package/dist/hooks/authorization-hook.js +93 -0
- package/dist/hooks/authorization-hook.js.map +1 -0
- package/dist/hooks/index.d.ts +54 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +55 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/service-hooks.d.ts +82 -0
- package/dist/hooks/service-hooks.d.ts.map +1 -0
- package/dist/hooks/service-hooks.js +158 -0
- package/dist/hooks/service-hooks.js.map +1 -0
- package/dist/hooks/types.d.ts +151 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +2 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/http/route-helpers.d.ts +45 -7
- package/dist/http/route-helpers.d.ts.map +1 -1
- package/dist/http/route-helpers.js +80 -2
- package/dist/http/route-helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
/**
|
|
3
|
+
* Context passed to service hooks containing the current Hono request context
|
|
4
|
+
* and any additional metadata about the service call.
|
|
5
|
+
*/
|
|
6
|
+
export interface ServiceHookContext {
|
|
7
|
+
/**
|
|
8
|
+
* The Hono request context, providing access to request data,
|
|
9
|
+
* response helpers, and context variables (like user, ability, etc.)
|
|
10
|
+
*/
|
|
11
|
+
honoContext: Context;
|
|
12
|
+
/**
|
|
13
|
+
* The name of the service being called (e.g., 'authService', 'userService')
|
|
14
|
+
*/
|
|
15
|
+
serviceName: string;
|
|
16
|
+
/**
|
|
17
|
+
* The name of the method being called (e.g., 'deleteSession', 'findById')
|
|
18
|
+
*/
|
|
19
|
+
methodName: string;
|
|
20
|
+
/**
|
|
21
|
+
* Optional metadata associated with this service method.
|
|
22
|
+
* Can be used to pass configuration like authorization rules, audit info, etc.
|
|
23
|
+
*/
|
|
24
|
+
metadata?: unknown;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Hook function that runs before a service method is executed.
|
|
28
|
+
* Can be used for authorization, validation, logging, etc.
|
|
29
|
+
*
|
|
30
|
+
* @param context - Hook context with Hono context and service info
|
|
31
|
+
* @param args - Arguments being passed to the service method
|
|
32
|
+
* @throws Error to prevent the service method from executing
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const authorizationHook: ServiceBeforeHook = async (context, args) => {
|
|
37
|
+
* const { honoContext, metadata } = context;
|
|
38
|
+
* if (metadata?.authorize) {
|
|
39
|
+
* authorize(honoContext, metadata.authorize.action, metadata.authorize.subject);
|
|
40
|
+
* }
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export type ServiceBeforeHook = (context: ServiceHookContext, args: unknown[]) => Promise<void> | void;
|
|
45
|
+
/**
|
|
46
|
+
* Hook function that runs after a service method successfully executes.
|
|
47
|
+
* Can be used for audit logging, metrics, response transformation, etc.
|
|
48
|
+
*
|
|
49
|
+
* @param context - Hook context with Hono context and service info
|
|
50
|
+
* @param args - Arguments that were passed to the service method
|
|
51
|
+
* @param result - The result returned by the service method
|
|
52
|
+
* @returns Optionally return a transformed result
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const auditHook: ServiceAfterHook = async (context, args, result) => {
|
|
57
|
+
* await auditLog.create({
|
|
58
|
+
* service: context.serviceName,
|
|
59
|
+
* method: context.methodName,
|
|
60
|
+
* args,
|
|
61
|
+
* timestamp: new Date(),
|
|
62
|
+
* });
|
|
63
|
+
* };
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export type ServiceAfterHook = (context: ServiceHookContext, args: unknown[], result: unknown) => Promise<unknown | undefined> | unknown | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Hook function that runs when a service method throws an error.
|
|
69
|
+
* Can be used for error logging, error transformation, retries, etc.
|
|
70
|
+
*
|
|
71
|
+
* @param context - Hook context with Hono context and service info
|
|
72
|
+
* @param args - Arguments that were passed to the service method
|
|
73
|
+
* @param error - The error that was thrown
|
|
74
|
+
* @throws Error to propagate or throw a different error
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const errorLoggingHook: ServiceErrorHook = async (context, args, error) => {
|
|
79
|
+
* logger.error('Service error', {
|
|
80
|
+
* service: context.serviceName,
|
|
81
|
+
* method: context.methodName,
|
|
82
|
+
* error,
|
|
83
|
+
* });
|
|
84
|
+
* // Re-throw to propagate
|
|
85
|
+
* throw error;
|
|
86
|
+
* };
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export type ServiceErrorHook = (context: ServiceHookContext, args: unknown[], error: unknown) => undefined | never | Promise<undefined | never>;
|
|
90
|
+
/**
|
|
91
|
+
* A service hook that can define before, after, and error handlers.
|
|
92
|
+
*/
|
|
93
|
+
export interface ServiceHook {
|
|
94
|
+
/**
|
|
95
|
+
* Optional hook that runs before the service method executes.
|
|
96
|
+
*/
|
|
97
|
+
before?: ServiceBeforeHook;
|
|
98
|
+
/**
|
|
99
|
+
* Optional hook that runs after the service method successfully executes.
|
|
100
|
+
*/
|
|
101
|
+
after?: ServiceAfterHook;
|
|
102
|
+
/**
|
|
103
|
+
* Optional hook that runs when the service method throws an error.
|
|
104
|
+
*/
|
|
105
|
+
onError?: ServiceErrorHook;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Metadata that can be attached to service methods to configure hook behavior.
|
|
109
|
+
*/
|
|
110
|
+
export type ServiceMethodMetadata = Record<string, unknown>;
|
|
111
|
+
/**
|
|
112
|
+
* Configuration for applying hooks to a service.
|
|
113
|
+
*/
|
|
114
|
+
export interface ServiceHooksConfig {
|
|
115
|
+
/**
|
|
116
|
+
* Array of hooks to apply to the service.
|
|
117
|
+
* Hooks run in the order they're defined.
|
|
118
|
+
*/
|
|
119
|
+
hooks: ServiceHook[];
|
|
120
|
+
/**
|
|
121
|
+
* Optional metadata for service methods.
|
|
122
|
+
* Keys are method names, values are metadata objects.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* {
|
|
127
|
+
* deleteSession: {
|
|
128
|
+
* authorize: { action: 'delete', subject: 'Session' }
|
|
129
|
+
* },
|
|
130
|
+
* findById: {
|
|
131
|
+
* authorize: { action: 'read', subject: 'User' },
|
|
132
|
+
* cache: { ttl: 300 }
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
metadata?: Record<string, ServiceMethodMetadata>;
|
|
138
|
+
/**
|
|
139
|
+
* Optional service name for better debugging.
|
|
140
|
+
* If not provided, will try to infer from the service constructor name.
|
|
141
|
+
*/
|
|
142
|
+
serviceName?: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Services registry that maps service names to their wrapped instances.
|
|
146
|
+
* This is used internally by the route context to provide hooked services.
|
|
147
|
+
*/
|
|
148
|
+
export type ServicesRegistry<T = unknown> = {
|
|
149
|
+
[K in keyof T]: T[K];
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,OAAO,EAAE,KACZ,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,OAAO,EAAE,EACf,MAAM,EAAE,OAAO,KACZ,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,GAAG,SAAS,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,OAAO,EAAE,EACf,KAAK,EAAE,OAAO,KACX,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAE3B;;OAEG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAEzB;;OAEG;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,KAAK,EAAE,WAAW,EAAE,CAAC;IAErB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAEjD;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI;KACzC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":""}
|
|
@@ -37,13 +37,17 @@ export type ValibotSchema = BaseSchema<unknown, unknown, BaseIssue<unknown>> | B
|
|
|
37
37
|
*/
|
|
38
38
|
type InferSchemaType<T> = T extends ValibotSchema ? InferOutput<T> : never;
|
|
39
39
|
/**
|
|
40
|
-
* Helper type to
|
|
40
|
+
* Helper type to extract and create a union of all 2xx response types
|
|
41
41
|
*/
|
|
42
|
-
type
|
|
43
|
-
200:
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
type InferResponseUnion<TResponses> = {
|
|
43
|
+
[K in keyof TResponses]: K extends 200 | 201 ? TResponses[K] extends ValibotSchema ? InferOutput<TResponses[K]> : never : never;
|
|
44
|
+
}[keyof TResponses];
|
|
45
|
+
/**
|
|
46
|
+
* Helper type to infer the response type from all 2xx status code schemas
|
|
47
|
+
* Returns a union type of all defined 2xx responses (200, 201, etc.)
|
|
48
|
+
* Falls back to unknown if no 2xx responses are defined
|
|
49
|
+
*/
|
|
50
|
+
type InferResponseType<TResponses> = InferResponseUnion<TResponses> extends never ? unknown : InferResponseUnion<TResponses>;
|
|
47
51
|
/**
|
|
48
52
|
* HTTP status codes with descriptions
|
|
49
53
|
*/
|
|
@@ -160,10 +164,19 @@ export interface RouteContext<TBody = never, TSessionRequired extends boolean =
|
|
|
160
164
|
*
|
|
161
165
|
* Type inference works automatically:
|
|
162
166
|
* - Body type is inferred from the `body` schema
|
|
163
|
-
* - Return type is inferred
|
|
167
|
+
* - Return type is inferred as a union of all 2xx response schemas (200, 201, etc.)
|
|
168
|
+
* - Response data is automatically validated and extra keys are stripped
|
|
169
|
+
*
|
|
170
|
+
* Response Validation:
|
|
171
|
+
* - Responses are parsed through their corresponding 2xx schema
|
|
172
|
+
* - Extra keys not defined in the schema are automatically stripped
|
|
173
|
+
* - This is useful when returning Prisma models that may have extra fields
|
|
174
|
+
* - If multiple 2xx schemas are defined, each is tried in order (200, 201)
|
|
175
|
+
* - The first schema that successfully validates is used
|
|
164
176
|
*
|
|
165
177
|
* @example
|
|
166
178
|
* ```typescript
|
|
179
|
+
* // Single response type
|
|
167
180
|
* route({
|
|
168
181
|
* body: StartSessionDto,
|
|
169
182
|
* responses: { 200: StartSessionResponseDto },
|
|
@@ -172,6 +185,31 @@ export interface RouteContext<TBody = never, TSessionRequired extends boolean =
|
|
|
172
185
|
* return { email: body.email }; // Must match StartSessionResponseDto
|
|
173
186
|
* }
|
|
174
187
|
* })
|
|
188
|
+
*
|
|
189
|
+
* // Union response type (multiple 2xx responses)
|
|
190
|
+
* route({
|
|
191
|
+
* body: LoginDto,
|
|
192
|
+
* responses: {
|
|
193
|
+
* 200: MfaRequiredDto,
|
|
194
|
+
* 201: SessionDto
|
|
195
|
+
* },
|
|
196
|
+
* handler: async ({ body }) => {
|
|
197
|
+
* // Return type is: MfaRequiredDto | SessionDto
|
|
198
|
+
* if (needsMfa) return { mfaRequired: true, methods: ['totp'] };
|
|
199
|
+
* return { sessionId: '123', token: 'abc' };
|
|
200
|
+
* }
|
|
201
|
+
* })
|
|
202
|
+
*
|
|
203
|
+
* // Automatic key stripping (useful with Prisma)
|
|
204
|
+
* route({
|
|
205
|
+
* responses: { 200: UserDto },
|
|
206
|
+
* handler: async ({ services }) => {
|
|
207
|
+
* const user = await prisma.user.findUnique({ ... });
|
|
208
|
+
* // user might have { id, email, password, createdAt, ... }
|
|
209
|
+
* // But only fields in UserDto will be returned
|
|
210
|
+
* return user; // Extra keys automatically stripped!
|
|
211
|
+
* }
|
|
212
|
+
* })
|
|
175
213
|
* ```
|
|
176
214
|
*/
|
|
177
215
|
export declare function route<TBody extends ValibotSchema | undefined = undefined, TResponses extends Partial<Record<keyof typeof STATUS_DESCRIPTIONS, ValibotSchema | undefined>> = Record<never, never>, TPublic extends boolean = false>(config: RouteConfig<TBody, TResponses, TPublic>): MiddlewareHandler[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../src/http/route-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../src/http/route-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { describeRoute as honoDescribeRoute } from 'hono-openapi';
|
|
2
2
|
import { resolver, validator } from 'hono-openapi/valibot';
|
|
3
|
+
import { safeParseAsync } from 'valibot';
|
|
3
4
|
import { getClientIp } from '../utils/get-client-ip.js';
|
|
5
|
+
import { createLogger } from '../utils/logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Logger instance for route helpers
|
|
8
|
+
*/
|
|
9
|
+
const logger = createLogger('Routes');
|
|
4
10
|
/**
|
|
5
11
|
* Global storage for OpenAPI response hooks.
|
|
6
12
|
* Set during bootstrap and used when defining routes.
|
|
@@ -89,10 +95,19 @@ export function createRoutes(factory) {
|
|
|
89
95
|
*
|
|
90
96
|
* Type inference works automatically:
|
|
91
97
|
* - Body type is inferred from the `body` schema
|
|
92
|
-
* - Return type is inferred
|
|
98
|
+
* - Return type is inferred as a union of all 2xx response schemas (200, 201, etc.)
|
|
99
|
+
* - Response data is automatically validated and extra keys are stripped
|
|
100
|
+
*
|
|
101
|
+
* Response Validation:
|
|
102
|
+
* - Responses are parsed through their corresponding 2xx schema
|
|
103
|
+
* - Extra keys not defined in the schema are automatically stripped
|
|
104
|
+
* - This is useful when returning Prisma models that may have extra fields
|
|
105
|
+
* - If multiple 2xx schemas are defined, each is tried in order (200, 201)
|
|
106
|
+
* - The first schema that successfully validates is used
|
|
93
107
|
*
|
|
94
108
|
* @example
|
|
95
109
|
* ```typescript
|
|
110
|
+
* // Single response type
|
|
96
111
|
* route({
|
|
97
112
|
* body: StartSessionDto,
|
|
98
113
|
* responses: { 200: StartSessionResponseDto },
|
|
@@ -101,6 +116,31 @@ export function createRoutes(factory) {
|
|
|
101
116
|
* return { email: body.email }; // Must match StartSessionResponseDto
|
|
102
117
|
* }
|
|
103
118
|
* })
|
|
119
|
+
*
|
|
120
|
+
* // Union response type (multiple 2xx responses)
|
|
121
|
+
* route({
|
|
122
|
+
* body: LoginDto,
|
|
123
|
+
* responses: {
|
|
124
|
+
* 200: MfaRequiredDto,
|
|
125
|
+
* 201: SessionDto
|
|
126
|
+
* },
|
|
127
|
+
* handler: async ({ body }) => {
|
|
128
|
+
* // Return type is: MfaRequiredDto | SessionDto
|
|
129
|
+
* if (needsMfa) return { mfaRequired: true, methods: ['totp'] };
|
|
130
|
+
* return { sessionId: '123', token: 'abc' };
|
|
131
|
+
* }
|
|
132
|
+
* })
|
|
133
|
+
*
|
|
134
|
+
* // Automatic key stripping (useful with Prisma)
|
|
135
|
+
* route({
|
|
136
|
+
* responses: { 200: UserDto },
|
|
137
|
+
* handler: async ({ services }) => {
|
|
138
|
+
* const user = await prisma.user.findUnique({ ... });
|
|
139
|
+
* // user might have { id, email, password, createdAt, ... }
|
|
140
|
+
* // But only fields in UserDto will be returned
|
|
141
|
+
* return user; // Extra keys automatically stripped!
|
|
142
|
+
* }
|
|
143
|
+
* })
|
|
104
144
|
* ```
|
|
105
145
|
*/
|
|
106
146
|
export function route(config) {
|
|
@@ -148,10 +188,48 @@ export function route(config) {
|
|
|
148
188
|
c.status(204);
|
|
149
189
|
return c.body(null);
|
|
150
190
|
}
|
|
151
|
-
|
|
191
|
+
// Parse and validate response through schema (strips extra keys)
|
|
192
|
+
const parsedResult = await parseResponse(result, config.responses);
|
|
193
|
+
return c.json(parsedResult);
|
|
152
194
|
});
|
|
153
195
|
return middlewares;
|
|
154
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse and validate response data through the appropriate 2xx schema.
|
|
199
|
+
* Tries each 2xx schema in order (200, 201) until one successfully validates.
|
|
200
|
+
* This automatically strips extra keys not defined in the schema.
|
|
201
|
+
*
|
|
202
|
+
* @param data - The raw response data from the handler
|
|
203
|
+
* @param responses - The response schemas configuration
|
|
204
|
+
* @returns Parsed and validated data with extra keys stripped
|
|
205
|
+
*/
|
|
206
|
+
async function parseResponse(data, responses) {
|
|
207
|
+
// If no response schemas defined, return data as-is
|
|
208
|
+
if (!responses) {
|
|
209
|
+
return data;
|
|
210
|
+
}
|
|
211
|
+
// Try each 2xx schema in order
|
|
212
|
+
const successCodes = [200, 201];
|
|
213
|
+
for (const statusCode of successCodes) {
|
|
214
|
+
const schema = responses[statusCode];
|
|
215
|
+
if (!schema)
|
|
216
|
+
continue;
|
|
217
|
+
// Try to parse with this schema
|
|
218
|
+
// Use async parse to support both sync and async schemas
|
|
219
|
+
const result = await safeParseAsync(schema, data);
|
|
220
|
+
if (result.success) {
|
|
221
|
+
// Successfully parsed - return the validated data (with extra keys stripped)
|
|
222
|
+
return result.output;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// No schema matched - return original data
|
|
226
|
+
// In development, we might want to warn about this
|
|
227
|
+
if (process.env.NODE_ENV === 'development') {
|
|
228
|
+
logger.warn('Response data does not match any defined 2xx schema. ' +
|
|
229
|
+
'Data will be returned as-is without validation or key stripping.');
|
|
230
|
+
}
|
|
231
|
+
return data;
|
|
232
|
+
}
|
|
155
233
|
function buildOpenAPIMiddleware(config) {
|
|
156
234
|
const responses = buildOpenAPIResponses(config);
|
|
157
235
|
// Build security requirement based on global security schemes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-helpers.js","sourceRoot":"","sources":["../../src/http/route-helpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"route-helpers.js","sourceRoot":"","sources":["../../src/http/route-helpers.ts"],"names":[],"mappings":"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"}
|