@geekmidas/constructs 0.0.2 → 0.0.4

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 (187) hide show
  1. package/dist/{AWSLambdaFunction-nic3vzt3.mjs → AWSLambdaFunction-DWIZYsCy.mjs} +2 -2
  2. package/dist/{AWSLambdaFunction-nic3vzt3.mjs.map → AWSLambdaFunction-DWIZYsCy.mjs.map} +1 -1
  3. package/dist/{AWSLambdaFunction-DW9qrBNR.cjs → AWSLambdaFunction-qA5LqPsv.cjs} +2 -2
  4. package/dist/{AWSLambdaFunction-DW9qrBNR.cjs.map → AWSLambdaFunction-qA5LqPsv.cjs.map} +1 -1
  5. package/dist/{AWSLambdaSubscriberAdaptor-ZuQAhW9_.cjs → AWSLambdaSubscriberAdaptor-CmPZ10JF.cjs} +1 -1
  6. package/dist/{AWSLambdaSubscriberAdaptor-ZuQAhW9_.cjs.map → AWSLambdaSubscriberAdaptor-CmPZ10JF.cjs.map} +1 -1
  7. package/dist/{AWSLambdaSubscriberAdaptor-BhqrpTVc.mjs → AWSLambdaSubscriberAdaptor-G8y3YkWj.mjs} +1 -1
  8. package/dist/{AWSLambdaSubscriberAdaptor-BhqrpTVc.mjs.map → AWSLambdaSubscriberAdaptor-G8y3YkWj.mjs.map} +1 -1
  9. package/dist/{AmazonApiGatewayEndpointAdaptor-D_Q_NTMT.cjs → AmazonApiGatewayEndpointAdaptor-B8mozTcG.cjs} +33 -10
  10. package/dist/AmazonApiGatewayEndpointAdaptor-B8mozTcG.cjs.map +1 -0
  11. package/dist/{AmazonApiGatewayEndpointAdaptor-DtzgQ9Vb.d.cts → AmazonApiGatewayEndpointAdaptor-BFhJ2Rpz.d.cts} +5 -2
  12. package/dist/{AmazonApiGatewayEndpointAdaptor-QzIAnWzS.mjs → AmazonApiGatewayEndpointAdaptor-Bmz6Cy1e.mjs} +33 -10
  13. package/dist/AmazonApiGatewayEndpointAdaptor-Bmz6Cy1e.mjs.map +1 -0
  14. package/dist/{AmazonApiGatewayEndpointAdaptor-DNZLntHj.d.mts → AmazonApiGatewayEndpointAdaptor-BrB3RfbI.d.mts} +5 -2
  15. package/dist/{AmazonApiGatewayV1EndpointAdaptor-BF5bGWV1.mjs → AmazonApiGatewayV1EndpointAdaptor-24g3dLn5.mjs} +3 -3
  16. package/dist/{AmazonApiGatewayV1EndpointAdaptor-BF5bGWV1.mjs.map → AmazonApiGatewayV1EndpointAdaptor-24g3dLn5.mjs.map} +1 -1
  17. package/dist/{AmazonApiGatewayV1EndpointAdaptor-Gw-j61qM.d.cts → AmazonApiGatewayV1EndpointAdaptor-Bd-o8ese.d.cts} +3 -3
  18. package/dist/{AmazonApiGatewayV1EndpointAdaptor-DbJa4cpU.d.mts → AmazonApiGatewayV1EndpointAdaptor-BtNXt0-4.d.mts} +3 -3
  19. package/dist/{AmazonApiGatewayV1EndpointAdaptor-Bh4tckwd.cjs → AmazonApiGatewayV1EndpointAdaptor-D4eZ-fx5.cjs} +3 -3
  20. package/dist/{AmazonApiGatewayV1EndpointAdaptor-Bh4tckwd.cjs.map → AmazonApiGatewayV1EndpointAdaptor-D4eZ-fx5.cjs.map} +1 -1
  21. package/dist/{AmazonApiGatewayV2EndpointAdaptor-BOaOkLXF.mjs → AmazonApiGatewayV2EndpointAdaptor-Cc40RThv.mjs} +3 -3
  22. package/dist/{AmazonApiGatewayV2EndpointAdaptor-BOaOkLXF.mjs.map → AmazonApiGatewayV2EndpointAdaptor-Cc40RThv.mjs.map} +1 -1
  23. package/dist/{AmazonApiGatewayV2EndpointAdaptor-BlKn-KJ6.d.mts → AmazonApiGatewayV2EndpointAdaptor-DAJdtgek.d.mts} +3 -3
  24. package/dist/{AmazonApiGatewayV2EndpointAdaptor-LUlpwmUW.d.cts → AmazonApiGatewayV2EndpointAdaptor-DX-Uci5w.d.cts} +3 -3
  25. package/dist/{AmazonApiGatewayV2EndpointAdaptor-L4Ywv3Pk.cjs → AmazonApiGatewayV2EndpointAdaptor-J6tACl-N.cjs} +3 -3
  26. package/dist/{AmazonApiGatewayV2EndpointAdaptor-L4Ywv3Pk.cjs.map → AmazonApiGatewayV2EndpointAdaptor-J6tACl-N.cjs.map} +1 -1
  27. package/dist/{Cron-BgJo6EW6.mjs → Cron-Br2TtpGY.mjs} +1 -1
  28. package/dist/{Cron-BgJo6EW6.mjs.map → Cron-Br2TtpGY.mjs.map} +1 -1
  29. package/dist/{Cron-JYYGj5ik.cjs → Cron-DF1o3U_T.cjs} +1 -1
  30. package/dist/{Cron-JYYGj5ik.cjs.map → Cron-DF1o3U_T.cjs.map} +1 -1
  31. package/dist/{CronBuilder-DVuhB_kA.mjs → CronBuilder-DNFHMTSl.mjs} +2 -2
  32. package/dist/{CronBuilder-DVuhB_kA.mjs.map → CronBuilder-DNFHMTSl.mjs.map} +1 -1
  33. package/dist/{CronBuilder-BDDS21OP.cjs → CronBuilder-e8CAOwBV.cjs} +2 -2
  34. package/dist/{CronBuilder-BDDS21OP.cjs.map → CronBuilder-e8CAOwBV.cjs.map} +1 -1
  35. package/dist/{Endpoint-DYUjJdEs.d.mts → Endpoint-C7jPJzAH.d.mts} +115 -6
  36. package/dist/{Endpoint-D1nnEsBU.cjs → Endpoint-COGAflGh.cjs} +128 -4
  37. package/dist/Endpoint-COGAflGh.cjs.map +1 -0
  38. package/dist/{Endpoint-DNlmybXV.mjs → Endpoint-DLLZvqoh.mjs} +123 -5
  39. package/dist/Endpoint-DLLZvqoh.mjs.map +1 -0
  40. package/dist/{Endpoint-C7z9YJHK.d.cts → Endpoint-XUMNAXYy.d.cts} +115 -6
  41. package/dist/{EndpointBuilder-B2iScUND.d.mts → EndpointBuilder-CFtWQhcv.d.mts} +2 -2
  42. package/dist/{EndpointBuilder-BhRd626m.cjs → EndpointBuilder-FJktpPOu.cjs} +2 -2
  43. package/dist/{EndpointBuilder-BhRd626m.cjs.map → EndpointBuilder-FJktpPOu.cjs.map} +1 -1
  44. package/dist/{EndpointBuilder-CpjIMYb0.mjs → EndpointBuilder-oXO_ka1-.mjs} +2 -2
  45. package/dist/{EndpointBuilder-CpjIMYb0.mjs.map → EndpointBuilder-oXO_ka1-.mjs.map} +1 -1
  46. package/dist/{EndpointBuilder-1fw103D6.d.cts → EndpointBuilder-t6fVEKBH.d.cts} +2 -2
  47. package/dist/{EndpointFactory-D576BhaH.d.cts → EndpointFactory-DBRGrXAy.d.mts} +10 -10
  48. package/dist/{EndpointFactory-DZQpM-9K.d.mts → EndpointFactory-DInjHvFR.d.cts} +10 -10
  49. package/dist/{EndpointFactory-ChmVHWim.cjs → EndpointFactory-Kk1tpifs.cjs} +4 -3
  50. package/dist/EndpointFactory-Kk1tpifs.cjs.map +1 -0
  51. package/dist/{EndpointFactory-DLpEbLzL.mjs → EndpointFactory-eG8bDhOh.mjs} +4 -3
  52. package/dist/EndpointFactory-eG8bDhOh.mjs.map +1 -0
  53. package/dist/{FunctionExecutionWrapper-UzfHDM2R.cjs → FunctionExecutionWrapper-CElXEjPe.cjs} +1 -1
  54. package/dist/{FunctionExecutionWrapper-UzfHDM2R.cjs.map → FunctionExecutionWrapper-CElXEjPe.cjs.map} +1 -1
  55. package/dist/{FunctionExecutionWrapper-CPzSbfaI.mjs → FunctionExecutionWrapper-XGrSAAPD.mjs} +1 -1
  56. package/dist/{FunctionExecutionWrapper-CPzSbfaI.mjs.map → FunctionExecutionWrapper-XGrSAAPD.mjs.map} +1 -1
  57. package/dist/{HonoEndpointAdaptor-ua6mp3gt.d.cts → HonoEndpointAdaptor-BJgpbMUG.d.cts} +4 -4
  58. package/dist/{HonoEndpointAdaptor-fs2928iO.mjs → HonoEndpointAdaptor-BlT1rWHV.mjs} +24 -9
  59. package/dist/HonoEndpointAdaptor-BlT1rWHV.mjs.map +1 -0
  60. package/dist/{HonoEndpointAdaptor-01cH100U.d.mts → HonoEndpointAdaptor-C9Xe2pRp.d.mts} +2 -2
  61. package/dist/{HonoEndpointAdaptor-6LERutxi.cjs → HonoEndpointAdaptor-Ds433Q8w.cjs} +24 -9
  62. package/dist/HonoEndpointAdaptor-Ds433Q8w.cjs.map +1 -0
  63. package/dist/{TestEndpointAdaptor-CelYsQi0.mjs → TestEndpointAdaptor-BG6fzAOx.mjs} +20 -6
  64. package/dist/TestEndpointAdaptor-BG6fzAOx.mjs.map +1 -0
  65. package/dist/{TestEndpointAdaptor-B4SvJvK-.cjs → TestEndpointAdaptor-BaQaTy_1.cjs} +20 -6
  66. package/dist/TestEndpointAdaptor-BaQaTy_1.cjs.map +1 -0
  67. package/dist/{TestEndpointAdaptor-Da0ooGt2.d.mts → TestEndpointAdaptor-Db0cm1fb.d.mts} +3 -3
  68. package/dist/{TestEndpointAdaptor-CHcgyI3V.d.cts → TestEndpointAdaptor-v7A-7hTs.d.cts} +3 -3
  69. package/dist/adaptors/aws.cjs +8 -8
  70. package/dist/adaptors/aws.d.cts +5 -5
  71. package/dist/adaptors/aws.d.mts +5 -5
  72. package/dist/adaptors/aws.mjs +8 -8
  73. package/dist/adaptors/hono.cjs +4 -4
  74. package/dist/adaptors/hono.d.cts +3 -3
  75. package/dist/adaptors/hono.d.mts +3 -3
  76. package/dist/adaptors/hono.mjs +4 -4
  77. package/dist/adaptors/testing.cjs +2 -2
  78. package/dist/adaptors/testing.d.cts +3 -3
  79. package/dist/adaptors/testing.d.mts +3 -3
  80. package/dist/adaptors/testing.mjs +2 -2
  81. package/dist/crons/Cron.cjs +1 -1
  82. package/dist/crons/Cron.d.cts +1 -1
  83. package/dist/crons/Cron.d.mts +1 -1
  84. package/dist/crons/Cron.mjs +1 -1
  85. package/dist/crons/CronBuilder.cjs +2 -2
  86. package/dist/crons/CronBuilder.d.cts +1 -1
  87. package/dist/crons/CronBuilder.d.mts +1 -1
  88. package/dist/crons/CronBuilder.mjs +2 -2
  89. package/dist/crons/index.cjs +2 -2
  90. package/dist/crons/index.d.cts +5 -5
  91. package/dist/crons/index.d.mts +5 -5
  92. package/dist/crons/index.mjs +2 -2
  93. package/dist/endpoints/AmazonApiGatewayEndpointAdaptor.cjs +2 -2
  94. package/dist/endpoints/AmazonApiGatewayEndpointAdaptor.d.cts +3 -3
  95. package/dist/endpoints/AmazonApiGatewayEndpointAdaptor.d.mts +3 -3
  96. package/dist/endpoints/AmazonApiGatewayEndpointAdaptor.mjs +2 -2
  97. package/dist/endpoints/AmazonApiGatewayV1EndpointAdaptor.cjs +4 -4
  98. package/dist/endpoints/AmazonApiGatewayV1EndpointAdaptor.d.cts +4 -4
  99. package/dist/endpoints/AmazonApiGatewayV1EndpointAdaptor.d.mts +4 -4
  100. package/dist/endpoints/AmazonApiGatewayV1EndpointAdaptor.mjs +4 -4
  101. package/dist/endpoints/AmazonApiGatewayV2EndpointAdaptor.cjs +4 -4
  102. package/dist/endpoints/AmazonApiGatewayV2EndpointAdaptor.d.cts +4 -4
  103. package/dist/endpoints/AmazonApiGatewayV2EndpointAdaptor.d.mts +4 -4
  104. package/dist/endpoints/AmazonApiGatewayV2EndpointAdaptor.mjs +4 -4
  105. package/dist/endpoints/Endpoint.cjs +2 -1
  106. package/dist/endpoints/Endpoint.d.cts +3 -3
  107. package/dist/endpoints/Endpoint.d.mts +3 -3
  108. package/dist/endpoints/Endpoint.mjs +2 -2
  109. package/dist/endpoints/EndpointBuilder.cjs +2 -2
  110. package/dist/endpoints/EndpointBuilder.d.cts +3 -3
  111. package/dist/endpoints/EndpointBuilder.d.mts +3 -3
  112. package/dist/endpoints/EndpointBuilder.mjs +2 -2
  113. package/dist/endpoints/EndpointFactory.cjs +3 -3
  114. package/dist/endpoints/EndpointFactory.d.cts +4 -4
  115. package/dist/endpoints/EndpointFactory.d.mts +4 -4
  116. package/dist/endpoints/EndpointFactory.mjs +3 -3
  117. package/dist/endpoints/HonoEndpointAdaptor.cjs +4 -4
  118. package/dist/endpoints/HonoEndpointAdaptor.d.cts +3 -3
  119. package/dist/endpoints/HonoEndpointAdaptor.d.mts +3 -3
  120. package/dist/endpoints/HonoEndpointAdaptor.mjs +4 -4
  121. package/dist/endpoints/TestEndpointAdaptor.cjs +2 -2
  122. package/dist/endpoints/TestEndpointAdaptor.d.cts +3 -3
  123. package/dist/endpoints/TestEndpointAdaptor.d.mts +3 -3
  124. package/dist/endpoints/TestEndpointAdaptor.mjs +2 -2
  125. package/dist/endpoints/helpers.cjs +2 -2
  126. package/dist/endpoints/helpers.d.cts +2 -2
  127. package/dist/endpoints/helpers.d.mts +2 -2
  128. package/dist/endpoints/helpers.mjs +2 -2
  129. package/dist/endpoints/index.cjs +3 -3
  130. package/dist/endpoints/index.cjs.map +1 -1
  131. package/dist/endpoints/index.d.cts +7 -7
  132. package/dist/endpoints/index.d.mts +7 -7
  133. package/dist/endpoints/index.mjs +3 -3
  134. package/dist/endpoints/index.mjs.map +1 -1
  135. package/dist/endpoints/parseHonoQuery.cjs +1 -1
  136. package/dist/endpoints/parseHonoQuery.mjs +1 -1
  137. package/dist/endpoints/parseQueryParams.cjs +1 -1
  138. package/dist/endpoints/parseQueryParams.mjs +1 -1
  139. package/dist/functions/AWSLambdaFunction.cjs +2 -2
  140. package/dist/functions/AWSLambdaFunction.mjs +2 -2
  141. package/dist/functions/FunctionExecutionWrapper.cjs +1 -1
  142. package/dist/functions/FunctionExecutionWrapper.mjs +1 -1
  143. package/dist/functions/index.d.cts +1 -1
  144. package/dist/functions/index.d.mts +1 -1
  145. package/dist/{helpers-CP7A0U_s.mjs → helpers-CM0U-4Vk.mjs} +2 -2
  146. package/dist/{helpers-CP7A0U_s.mjs.map → helpers-CM0U-4Vk.mjs.map} +1 -1
  147. package/dist/{helpers-CjvCSIF5.cjs → helpers-go4jiRvV.cjs} +2 -2
  148. package/dist/{helpers-CjvCSIF5.cjs.map → helpers-go4jiRvV.cjs.map} +1 -1
  149. package/dist/index-BjB0W_Wq.d.mts +9 -0
  150. package/dist/index-D9vqHZie.d.cts +9 -0
  151. package/dist/{parseHonoQuery-BiPp8bEJ.cjs → parseHonoQuery-DopC24vB.cjs} +1 -1
  152. package/dist/{parseHonoQuery-BiPp8bEJ.cjs.map → parseHonoQuery-DopC24vB.cjs.map} +1 -1
  153. package/dist/{parseHonoQuery-yWRoKFFl.mjs → parseHonoQuery-znDKBhdE.mjs} +1 -1
  154. package/dist/{parseHonoQuery-yWRoKFFl.mjs.map → parseHonoQuery-znDKBhdE.mjs.map} +1 -1
  155. package/dist/{parseQueryParams-DSk9xl09.mjs → parseQueryParams-BJaRh3OB.mjs} +1 -1
  156. package/dist/{parseQueryParams-DSk9xl09.mjs.map → parseQueryParams-BJaRh3OB.mjs.map} +1 -1
  157. package/dist/{parseQueryParams-C2EjouGt.cjs → parseQueryParams-BzPop4I1.cjs} +1 -1
  158. package/dist/{parseQueryParams-C2EjouGt.cjs.map → parseQueryParams-BzPop4I1.cjs.map} +1 -1
  159. package/dist/subscribers/AWSLambdaSubscriberAdaptor.cjs +1 -1
  160. package/dist/subscribers/AWSLambdaSubscriberAdaptor.mjs +1 -1
  161. package/dist/subscribers/index.d.cts +2 -2
  162. package/dist/subscribers/index.d.mts +2 -2
  163. package/package.json +3 -3
  164. package/src/endpoints/AmazonApiGatewayEndpointAdaptor.ts +62 -13
  165. package/src/endpoints/Endpoint.ts +243 -19
  166. package/src/endpoints/EndpointFactory.ts +88 -18
  167. package/src/endpoints/HonoEndpointAdaptor.ts +52 -16
  168. package/src/endpoints/TestEndpointAdaptor.ts +45 -12
  169. package/src/endpoints/__tests__/AmazonApiGatewayV1EndpointAdaptor.spec.ts +240 -0
  170. package/src/endpoints/__tests__/AmazonApiGatewayV2EndpointAdaptor.spec.ts +177 -200
  171. package/src/endpoints/__tests__/Endpoint.cookies.spec.ts +120 -0
  172. package/src/endpoints/__tests__/Endpoint.spec.ts +12 -0
  173. package/src/endpoints/__tests__/ResponseBuilder.spec.ts +235 -0
  174. package/src/endpoints/__tests__/TestEndpointAdaptor.spec.ts +348 -0
  175. package/src/endpoints/index.ts +1 -1
  176. package/dist/AmazonApiGatewayEndpointAdaptor-D_Q_NTMT.cjs.map +0 -1
  177. package/dist/AmazonApiGatewayEndpointAdaptor-QzIAnWzS.mjs.map +0 -1
  178. package/dist/Endpoint-D1nnEsBU.cjs.map +0 -1
  179. package/dist/Endpoint-DNlmybXV.mjs.map +0 -1
  180. package/dist/EndpointFactory-ChmVHWim.cjs.map +0 -1
  181. package/dist/EndpointFactory-DLpEbLzL.mjs.map +0 -1
  182. package/dist/HonoEndpointAdaptor-6LERutxi.cjs.map +0 -1
  183. package/dist/HonoEndpointAdaptor-fs2928iO.mjs.map +0 -1
  184. package/dist/TestEndpointAdaptor-B4SvJvK-.cjs.map +0 -1
  185. package/dist/TestEndpointAdaptor-CelYsQi0.mjs.map +0 -1
  186. package/dist/index-BXTN4mwI.d.mts +0 -9
  187. package/dist/index-zOH9f4sh.d.cts +0 -9
@@ -6,11 +6,7 @@ import type { OpenAPIV3_1 } from 'openapi-types';
6
6
 
7
7
  import type { Service, ServiceRecord } from '@geekmidas/services';
8
8
  import { ConstructType } from '../Construct';
9
- import {
10
- Function,
11
- type FunctionContext,
12
- type FunctionHandler,
13
- } from '../functions';
9
+ import { Function, type FunctionHandler } from '../functions';
14
10
 
15
11
  import type {
16
12
  EventPublisher,
@@ -88,6 +84,8 @@ export class Endpoint<
88
84
  tags?: string[];
89
85
  /** The HTTP success status code to return (default: 200) */
90
86
  public readonly status: SuccessStatus;
87
+ /** Default headers to apply to all responses */
88
+ public readonly defaultHeaders: Record<string, string> = {};
91
89
  /** Function to extract session data from the request context */
92
90
  public getSession: SessionFn<TServices, TLogger, TSession> = () =>
93
91
  ({}) as TSession;
@@ -95,6 +93,14 @@ export class Endpoint<
95
93
  public authorize: AuthorizeFn<TServices, TLogger, TSession> = () => true;
96
94
  /** Optional rate limiting configuration */
97
95
  public rateLimit?: RateLimitConfig;
96
+ /** The endpoint handler function */
97
+ private endpointFn!: EndpointHandler<
98
+ TInput,
99
+ TServices,
100
+ TLogger,
101
+ OutSchema,
102
+ TSession
103
+ >;
98
104
 
99
105
  /**
100
106
  * Builds a complete OpenAPI 3.1 schema from an array of endpoints.
@@ -188,6 +194,81 @@ export class Endpoint<
188
194
  };
189
195
  }
190
196
 
197
+ /**
198
+ * Parses cookie string and creates a cookie lookup function.
199
+ *
200
+ * @param cookieHeader - The Cookie header value
201
+ * @returns Function to retrieve cookie values by name
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const cookieFn = Endpoint.createCookies('session=abc123; theme=dark');
206
+ * cookieFn('session'); // Returns 'abc123'
207
+ * cookieFn('theme'); // Returns 'dark'
208
+ * ```
209
+ */
210
+ static createCookies(cookieHeader: string | undefined): CookieFn {
211
+ const cookieMap = new Map<string, string>();
212
+
213
+ if (cookieHeader) {
214
+ // Parse cookie string: "name1=value1; name2=value2"
215
+ const cookies = cookieHeader.split(';');
216
+ for (const cookie of cookies) {
217
+ const [name, ...valueParts] = cookie.trim().split('=');
218
+ if (name) {
219
+ const value = valueParts.join('='); // Handle values with = in them
220
+ cookieMap.set(name, decodeURIComponent(value));
221
+ }
222
+ }
223
+ }
224
+
225
+ return function get(name: string): string | undefined {
226
+ return cookieMap.get(name);
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Formats a cookie as a Set-Cookie header string.
232
+ *
233
+ * @param name - Cookie name
234
+ * @param value - Cookie value
235
+ * @param options - Cookie options (httpOnly, secure, sameSite, etc.)
236
+ * @returns Formatted Set-Cookie header string
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * const header = Endpoint.formatCookieHeader('session', 'abc123', {
241
+ * httpOnly: true,
242
+ * secure: true,
243
+ * sameSite: 'strict',
244
+ * maxAge: 3600
245
+ * });
246
+ * // Returns: "session=abc123; Max-Age=3600; HttpOnly; Secure; SameSite=Strict"
247
+ * ```
248
+ */
249
+ static formatCookieHeader(
250
+ name: string,
251
+ value: string,
252
+ options?: CookieOptions,
253
+ ): string {
254
+ let cookie = `${name}=${value}`;
255
+
256
+ if (options) {
257
+ if (options.domain) cookie += `; Domain=${options.domain}`;
258
+ if (options.path) cookie += `; Path=${options.path}`;
259
+ if (options.expires)
260
+ cookie += `; Expires=${options.expires.toUTCString()}`;
261
+ if (options.maxAge !== undefined) cookie += `; Max-Age=${options.maxAge}`;
262
+ if (options.httpOnly) cookie += '; HttpOnly';
263
+ if (options.secure) cookie += '; Secure';
264
+ if (options.sameSite) {
265
+ cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
266
+ }
267
+ }
268
+
269
+ return cookie;
270
+ }
271
+
191
272
  /**
192
273
  * Extracts and refines input data from the endpoint context.
193
274
  *
@@ -207,18 +288,36 @@ export class Endpoint<
207
288
  return input;
208
289
  }
209
290
 
210
- handler: EndpointHandler<TInput, TServices, TLogger, OutSchema, TSession> = (
291
+ handler = (
211
292
  ctx: EndpointContext<TInput, TServices, TLogger, TSession>,
293
+ response: ResponseBuilder,
212
294
  ): OutSchema extends StandardSchemaV1
213
- ? InferStandardSchema<OutSchema> | Promise<InferStandardSchema<OutSchema>>
214
- : void | Promise<void> => {
215
- return this.fn({
216
- ...this.refineInput(ctx),
217
- services: ctx.services,
218
- logger: ctx.logger,
219
- header: ctx.header,
220
- session: ctx.session,
221
- } as unknown as FunctionContext<TInput, TServices, TLogger>);
295
+ ?
296
+ | InferStandardSchema<OutSchema>
297
+ | ResponseWithMetadata<InferStandardSchema<OutSchema>>
298
+ | Promise<InferStandardSchema<OutSchema>>
299
+ | Promise<ResponseWithMetadata<InferStandardSchema<OutSchema>>>
300
+ :
301
+ | any
302
+ | ResponseWithMetadata<any>
303
+ | Promise<any>
304
+ | Promise<ResponseWithMetadata<any>> => {
305
+ // Apply default headers to response builder
306
+ for (const [key, value] of Object.entries(this.defaultHeaders)) {
307
+ response.header(key, value);
308
+ }
309
+
310
+ return this.endpointFn(
311
+ {
312
+ ...this.refineInput(ctx),
313
+ services: ctx.services,
314
+ logger: ctx.logger,
315
+ header: ctx.header,
316
+ cookie: ctx.cookie,
317
+ session: ctx.session,
318
+ } as EndpointContext<TInput, TServices, TLogger, TSession>,
319
+ response,
320
+ );
222
321
  };
223
322
 
224
323
  /**
@@ -235,6 +334,20 @@ export class Endpoint<
235
334
  );
236
335
  }
237
336
 
337
+ /**
338
+ * Helper to check if response has metadata
339
+ */
340
+ static hasMetadata<T>(
341
+ response: T | ResponseWithMetadata<T>,
342
+ ): response is ResponseWithMetadata<T> {
343
+ return (
344
+ response !== null &&
345
+ typeof response === 'object' &&
346
+ 'data' in response &&
347
+ 'metadata' in response
348
+ );
349
+ }
350
+
238
351
  /**
239
352
  * Converts Express-style route params to OpenAPI format.
240
353
  * @returns Route with ':param' converted to '{param}'
@@ -433,6 +546,8 @@ export class Endpoint<
433
546
  this.description = description;
434
547
  this.tags = tags;
435
548
  this.status = status;
549
+ this.endpointFn = fn;
550
+
436
551
  if (getSession) {
437
552
  this.getSession = getSession;
438
553
  }
@@ -553,6 +668,7 @@ export type AuthorizeContext<
553
668
  services: ServiceRecord<TServices>;
554
669
  logger: TLogger;
555
670
  header: HeaderFn;
671
+ cookie: CookieFn;
556
672
  session: TSession;
557
673
  };
558
674
  /**
@@ -587,6 +703,7 @@ export type SessionContext<
587
703
  services: ServiceRecord<TServices>;
588
704
  logger: TLogger;
589
705
  header: HeaderFn;
706
+ cookie: CookieFn;
590
707
  };
591
708
  /**
592
709
  * Function type for extracting session data from a request.
@@ -669,9 +786,96 @@ export type EndpointHeaders = Map<string, string>;
669
786
  */
670
787
  export type HeaderFn = SingleHeaderFn;
671
788
 
789
+ /**
790
+ * Function type for retrieving cookie values.
791
+ *
792
+ * @param name - The cookie name
793
+ * @returns The cookie value or undefined if not found
794
+ *
795
+ * @example
796
+ * ```typescript
797
+ * const sessionId = cookie('session');
798
+ * ```
799
+ */
800
+ export type CookieFn = (name: string) => string | undefined;
801
+
802
+ /**
803
+ * Cookie options matching standard Set-Cookie attributes
804
+ */
805
+ export interface CookieOptions {
806
+ domain?: string;
807
+ path?: string;
808
+ expires?: Date;
809
+ maxAge?: number;
810
+ httpOnly?: boolean;
811
+ secure?: boolean;
812
+ sameSite?: 'strict' | 'lax' | 'none';
813
+ }
814
+
815
+ /**
816
+ * Response metadata that handlers can set
817
+ */
818
+ export interface ResponseMetadata {
819
+ headers?: Record<string, string>;
820
+ cookies?: Map<string, { value: string; options?: CookieOptions }>;
821
+ status?: SuccessStatus;
822
+ }
823
+
824
+ /**
825
+ * Return type for handlers that want to set response metadata
826
+ */
827
+ export interface ResponseWithMetadata<T> {
828
+ data: T;
829
+ metadata: ResponseMetadata;
830
+ }
831
+
832
+ /**
833
+ * Response builder for fluent API in handlers
834
+ */
835
+ export class ResponseBuilder {
836
+ private metadata: ResponseMetadata = {
837
+ headers: {},
838
+ cookies: new Map(),
839
+ };
840
+
841
+ header(key: string, value: string): this {
842
+ this.metadata.headers![key] = value;
843
+ return this;
844
+ }
845
+
846
+ cookie(name: string, value: string, options?: CookieOptions): this {
847
+ this.metadata.cookies!.set(name, { value, options });
848
+ return this;
849
+ }
850
+
851
+ deleteCookie(
852
+ name: string,
853
+ options?: Pick<CookieOptions, 'domain' | 'path'>,
854
+ ): this {
855
+ this.metadata.cookies!.set(name, {
856
+ value: '',
857
+ options: { ...options, maxAge: 0, expires: new Date(0) },
858
+ });
859
+ return this;
860
+ }
861
+
862
+ status(code: SuccessStatus): this {
863
+ this.metadata.status = code;
864
+ return this;
865
+ }
866
+
867
+ send<T>(data: T): ResponseWithMetadata<T> {
868
+ return { data, metadata: this.metadata };
869
+ }
870
+
871
+ getMetadata(): ResponseMetadata {
872
+ return this.metadata;
873
+ }
874
+ }
875
+
672
876
  /**
673
877
  * The execution context provided to endpoint handlers.
674
- * Contains all parsed input data, services, logger, headers, and session.
878
+ * Contains all parsed input data, services, logger, headers, cookies, and session.
675
879
  *
676
880
  * @template Input - The input schemas (body, query, params)
677
881
  * @template TServices - Available service dependencies
@@ -690,6 +894,8 @@ export type EndpointContext<
690
894
  logger: TLogger;
691
895
  /** Function to retrieve request headers */
692
896
  header: HeaderFn;
897
+ /** Function to retrieve request cookies */
898
+ cookie: CookieFn;
693
899
  /** Session data extracted by getSession */
694
900
  session: TSession;
695
901
  } & InferComposableStandardSchema<Input>;
@@ -704,14 +910,23 @@ export type EndpointContext<
704
910
  * @template TSession - Session data type
705
911
  *
706
912
  * @param ctx - The endpoint execution context
707
- * @returns The response data (validated if OutSchema is provided)
913
+ * @param response - Response builder for setting cookies, headers, and status
914
+ * @returns The response data (validated if OutSchema is provided) or ResponseWithMetadata
708
915
  *
709
916
  * @example
710
917
  * ```typescript
918
+ * // Simple response
711
919
  * const handler: EndpointHandler<Input, [UserService], Logger, UserSchema> =
712
920
  * async ({ params, services }) => {
713
921
  * return await services.users.findById(params.id);
714
922
  * };
923
+ *
924
+ * // With response builder
925
+ * const handler: EndpointHandler<Input, [UserService], Logger, UserSchema> =
926
+ * async ({ params, services }, response) => {
927
+ * const user = await services.users.findById(params.id);
928
+ * return response.header('X-User-Id', user.id).send(user);
929
+ * };
715
930
  * ```
716
931
  */
717
932
  export type EndpointHandler<
@@ -722,9 +937,18 @@ export type EndpointHandler<
722
937
  TSession = unknown,
723
938
  > = (
724
939
  ctx: EndpointContext<TInput, TServices, TLogger, TSession>,
940
+ response: ResponseBuilder,
725
941
  ) => OutSchema extends StandardSchemaV1
726
- ? InferStandardSchema<OutSchema> | Promise<InferStandardSchema<OutSchema>>
727
- : any | Promise<any>;
942
+ ?
943
+ | InferStandardSchema<OutSchema>
944
+ | ResponseWithMetadata<InferStandardSchema<OutSchema>>
945
+ | Promise<InferStandardSchema<OutSchema>>
946
+ | Promise<ResponseWithMetadata<InferStandardSchema<OutSchema>>>
947
+ :
948
+ | unknown
949
+ | ResponseWithMetadata<unknown>
950
+ | Promise<unknown>
951
+ | Promise<ResponseWithMetadata<unknown>>;
728
952
 
729
953
  /**
730
954
  * HTTP success status codes that can be returned by endpoints.
@@ -102,10 +102,18 @@ export class EndpointFactory<
102
102
  JoinPaths<TBasePath, TPath>,
103
103
  TLogger,
104
104
  TSession,
105
- TEventPublisher
105
+ TEventPublisher,
106
+ TEventPublisherServiceName
106
107
  > {
107
108
  const newBasePath = EndpointFactory.joinPaths(path, this.basePath);
108
- return new EndpointFactory({
109
+ return new EndpointFactory<
110
+ TServices,
111
+ JoinPaths<TBasePath, TPath>,
112
+ TLogger,
113
+ TSession,
114
+ TEventPublisher,
115
+ TEventPublisherServiceName
116
+ >({
109
117
  defaultServices: this.defaultServices,
110
118
  basePath: newBasePath,
111
119
  defaultAuthorizeFn: this.defaultAuthorizeFn,
@@ -118,8 +126,22 @@ export class EndpointFactory<
118
126
  // Create a new factory with authorization
119
127
  authorize(
120
128
  fn: AuthorizeFn<TServices, TLogger, TSession>,
121
- ): EndpointFactory<TServices, TBasePath, TLogger, TSession, TEventPublisher> {
122
- return new EndpointFactory({
129
+ ): EndpointFactory<
130
+ TServices,
131
+ TBasePath,
132
+ TLogger,
133
+ TSession,
134
+ TEventPublisher,
135
+ TEventPublisherServiceName
136
+ > {
137
+ return new EndpointFactory<
138
+ TServices,
139
+ TBasePath,
140
+ TLogger,
141
+ TSession,
142
+ TEventPublisher,
143
+ TEventPublisherServiceName
144
+ >({
123
145
  defaultServices: this.defaultServices,
124
146
  basePath: this.basePath,
125
147
  defaultAuthorizeFn: fn,
@@ -137,14 +159,16 @@ export class EndpointFactory<
137
159
  TBasePath,
138
160
  TLogger,
139
161
  TSession,
140
- TEventPublisher
162
+ TEventPublisher,
163
+ TEventPublisherServiceName
141
164
  > {
142
165
  return new EndpointFactory<
143
166
  [...S, ...TServices],
144
167
  TBasePath,
145
168
  TLogger,
146
169
  TSession,
147
- TEventPublisher
170
+ TEventPublisher,
171
+ TEventPublisherServiceName
148
172
  >({
149
173
  defaultServices: [...services, ...this.defaultServices],
150
174
  basePath: this.basePath,
@@ -157,14 +181,34 @@ export class EndpointFactory<
157
181
 
158
182
  logger<L extends Logger>(
159
183
  logger: L,
160
- ): EndpointFactory<TServices, TBasePath, L, TSession> {
161
- return new EndpointFactory<TServices, TBasePath, L, TSession>({
184
+ ): EndpointFactory<
185
+ TServices,
186
+ TBasePath,
187
+ L,
188
+ TSession,
189
+ TEventPublisher,
190
+ TEventPublisherServiceName
191
+ > {
192
+ return new EndpointFactory<
193
+ TServices,
194
+ TBasePath,
195
+ L,
196
+ TSession,
197
+ TEventPublisher,
198
+ TEventPublisherServiceName
199
+ >({
162
200
  defaultServices: this.defaultServices,
163
201
  basePath: this.basePath,
164
- defaultAuthorizeFn: this.defaultAuthorizeFn,
202
+ defaultAuthorizeFn: this.defaultAuthorizeFn as unknown as AuthorizeFn<
203
+ TServices,
204
+ L,
205
+ TSession
206
+ >,
165
207
  defaultLogger: logger,
166
- defaultSessionExtractor: this.defaultSessionExtractor,
167
- } as EndpointFactoryOptions<TServices, TBasePath, L, TSession>);
208
+ defaultSessionExtractor: this
209
+ .defaultSessionExtractor as unknown as SessionFn<TServices, L, TSession>,
210
+ defaultEventPublisher: this.defaultEventPublisher,
211
+ });
168
212
  }
169
213
 
170
214
  publisher<
@@ -172,8 +216,15 @@ export class EndpointFactory<
172
216
  TServiceName extends string = string,
173
217
  >(
174
218
  publisher: Service<TServiceName, T>,
175
- ): EndpointFactory<TServices, TBasePath, TLogger, TSession, T> {
176
- return new EndpointFactory<TServices, TBasePath, TLogger, TSession, T>({
219
+ ): EndpointFactory<TServices, TBasePath, TLogger, TSession, T, TServiceName> {
220
+ return new EndpointFactory<
221
+ TServices,
222
+ TBasePath,
223
+ TLogger,
224
+ TSession,
225
+ T,
226
+ TServiceName
227
+ >({
177
228
  defaultServices: this.defaultServices,
178
229
  basePath: this.basePath,
179
230
  defaultAuthorizeFn: this.defaultAuthorizeFn,
@@ -183,15 +234,34 @@ export class EndpointFactory<
183
234
  });
184
235
  }
185
236
 
186
- session<T>(session: SessionFn<TServices, TLogger, T>) {
187
- return new EndpointFactory<TServices, TBasePath, TLogger, T>({
237
+ session<T>(
238
+ session: SessionFn<TServices, TLogger, T>,
239
+ ): EndpointFactory<
240
+ TServices,
241
+ TBasePath,
242
+ TLogger,
243
+ T,
244
+ TEventPublisher,
245
+ TEventPublisherServiceName
246
+ > {
247
+ return new EndpointFactory<
248
+ TServices,
249
+ TBasePath,
250
+ TLogger,
251
+ T,
252
+ TEventPublisher,
253
+ TEventPublisherServiceName
254
+ >({
188
255
  defaultServices: this.defaultServices,
189
256
  basePath: this.basePath,
190
- // @ts-ignore
191
- defaultAuthorizeFn: this.defaultAuthorizeFn,
257
+ defaultAuthorizeFn: this.defaultAuthorizeFn as unknown as AuthorizeFn<
258
+ TServices,
259
+ TLogger,
260
+ T
261
+ >,
192
262
  defaultLogger: this.defaultLogger,
193
263
  defaultSessionExtractor: session,
194
- defaultEventPublisher: this.defaultEventPublisher as any,
264
+ defaultEventPublisher: this.defaultEventPublisher,
195
265
  });
196
266
  }
197
267
 
@@ -4,12 +4,14 @@ import type { Logger } from '@geekmidas/logger';
4
4
  import { checkRateLimit, getRateLimitHeaders } from '@geekmidas/rate-limit';
5
5
  import type { StandardSchemaV1 } from '@standard-schema/spec';
6
6
  import { type Context, Hono } from 'hono';
7
+ import { setCookie } from 'hono/cookie';
7
8
  import { validator } from 'hono/validator';
8
9
  import type { HttpMethod, LowerHttpMethod } from '../types';
9
10
  import {
10
11
  Endpoint,
11
12
  type EndpointContext,
12
13
  type EndpointSchemas,
14
+ ResponseBuilder,
13
15
  } from './Endpoint';
14
16
  import { getEndpointsFromRoutes } from './helpers';
15
17
  import { parseHonoQuery } from './parseHonoQuery';
@@ -242,6 +244,7 @@ export class HonoEndpoint<
242
244
  const headerValues = c.req.header();
243
245
 
244
246
  const header = Endpoint.createHeaders(headerValues);
247
+ const cookie = Endpoint.createCookies(headerValues.cookie);
245
248
 
246
249
  const services = await serviceDiscovery.register(endpoint.services);
247
250
 
@@ -249,10 +252,12 @@ export class HonoEndpoint<
249
252
  services,
250
253
  logger,
251
254
  header,
255
+ cookie,
252
256
  });
253
257
 
254
258
  const isAuthorized = await endpoint.authorize({
255
259
  header,
260
+ cookie,
256
261
  services,
257
262
  logger,
258
263
  session,
@@ -286,29 +291,60 @@ export class HonoEndpoint<
286
291
  }
287
292
  }
288
293
 
289
- const response = await endpoint.handler({
290
- services,
291
- logger,
292
- body: c.req.valid('json'),
293
- query: c.req.valid('query'),
294
- params: c.req.valid('param'),
295
- session,
296
- header: Endpoint.createHeaders(headerValues),
297
- } as unknown as EndpointContext<
298
- TInput,
299
- TServices,
300
- TLogger,
301
- TSession
302
- >);
294
+ const responseBuilder = new ResponseBuilder();
295
+ const response = await endpoint.handler(
296
+ {
297
+ services,
298
+ logger,
299
+ body: c.req.valid('json'),
300
+ query: c.req.valid('query'),
301
+ params: c.req.valid('param'),
302
+ session,
303
+ header: Endpoint.createHeaders(headerValues),
304
+ cookie: Endpoint.createCookies(headerValues.cookie),
305
+ } as unknown as EndpointContext<
306
+ TInput,
307
+ TServices,
308
+ TLogger,
309
+ TSession
310
+ >,
311
+ responseBuilder,
312
+ );
303
313
 
304
314
  // Publish events if configured
305
315
 
306
316
  // Validate output if schema is defined
307
317
 
308
318
  try {
309
- const status = endpoint.status as ContentfulStatusCode;
319
+ // Check if response has metadata
320
+ let data = response;
321
+ let metadata = responseBuilder.getMetadata();
322
+ let status = endpoint.status as ContentfulStatusCode;
323
+
324
+ if (Endpoint.hasMetadata(response)) {
325
+ data = response.data;
326
+ metadata = response.metadata;
327
+ }
328
+
329
+ // Apply response metadata
330
+ if (metadata.status) {
331
+ status = metadata.status as ContentfulStatusCode;
332
+ }
333
+
334
+ if (metadata.headers) {
335
+ for (const [key, value] of Object.entries(metadata.headers)) {
336
+ c.header(key, value);
337
+ }
338
+ }
339
+
340
+ if (metadata.cookies) {
341
+ for (const [name, { value, options }] of metadata.cookies) {
342
+ setCookie(c, name, value, options);
343
+ }
344
+ }
345
+
310
346
  const output = endpoint.outputSchema
311
- ? await endpoint.parseOutput(response)
347
+ ? await endpoint.parseOutput(data)
312
348
  : ({} as any);
313
349
  // @ts-ignore
314
350
  c.set('__response', output);