@beignet/core 0.0.1

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 (331) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +288 -0
  3. package/dist/application/index.d.ts +260 -0
  4. package/dist/application/index.d.ts.map +1 -0
  5. package/dist/application/index.js +324 -0
  6. package/dist/application/index.js.map +1 -0
  7. package/dist/client/client.d.ts +241 -0
  8. package/dist/client/client.d.ts.map +1 -0
  9. package/dist/client/client.js +531 -0
  10. package/dist/client/client.js.map +1 -0
  11. package/dist/client/index.d.ts +10 -0
  12. package/dist/client/index.d.ts.map +1 -0
  13. package/dist/client/index.js +8 -0
  14. package/dist/client/index.js.map +1 -0
  15. package/dist/client/types.d.ts +139 -0
  16. package/dist/client/types.d.ts.map +1 -0
  17. package/dist/client/types.js +2 -0
  18. package/dist/client/types.js.map +1 -0
  19. package/dist/config/index.d.ts +122 -0
  20. package/dist/config/index.d.ts.map +1 -0
  21. package/dist/config/index.js +216 -0
  22. package/dist/config/index.js.map +1 -0
  23. package/dist/contracts/contract-builder.d.ts +121 -0
  24. package/dist/contracts/contract-builder.d.ts.map +1 -0
  25. package/dist/contracts/contract-builder.js +346 -0
  26. package/dist/contracts/contract-builder.js.map +1 -0
  27. package/dist/contracts/contract-group.d.ts +106 -0
  28. package/dist/contracts/contract-group.d.ts.map +1 -0
  29. package/dist/contracts/contract-group.js +240 -0
  30. package/dist/contracts/contract-group.js.map +1 -0
  31. package/dist/contracts/contract-like.d.ts +21 -0
  32. package/dist/contracts/contract-like.d.ts.map +1 -0
  33. package/dist/contracts/contract-like.js +9 -0
  34. package/dist/contracts/contract-like.js.map +1 -0
  35. package/dist/contracts/index.d.ts +15 -0
  36. package/dist/contracts/index.d.ts.map +1 -0
  37. package/dist/contracts/index.js +11 -0
  38. package/dist/contracts/index.js.map +1 -0
  39. package/dist/contracts/openapi-meta.d.ts +23 -0
  40. package/dist/contracts/openapi-meta.d.ts.map +1 -0
  41. package/dist/contracts/openapi-meta.js +2 -0
  42. package/dist/contracts/openapi-meta.js.map +1 -0
  43. package/dist/contracts/path-template.d.ts +17 -0
  44. package/dist/contracts/path-template.d.ts.map +1 -0
  45. package/dist/contracts/path-template.js +50 -0
  46. package/dist/contracts/path-template.js.map +1 -0
  47. package/dist/contracts/rate-limit.d.ts +50 -0
  48. package/dist/contracts/rate-limit.d.ts.map +1 -0
  49. package/dist/contracts/rate-limit.js +2 -0
  50. package/dist/contracts/rate-limit.js.map +1 -0
  51. package/dist/contracts/types.d.ts +97 -0
  52. package/dist/contracts/types.d.ts.map +1 -0
  53. package/dist/contracts/types.js +54 -0
  54. package/dist/contracts/types.js.map +1 -0
  55. package/dist/contracts/utils.d.ts +3 -0
  56. package/dist/contracts/utils.d.ts.map +1 -0
  57. package/dist/contracts/utils.js +44 -0
  58. package/dist/contracts/utils.js.map +1 -0
  59. package/dist/domain/entity.d.ts +87 -0
  60. package/dist/domain/entity.d.ts.map +1 -0
  61. package/dist/domain/entity.js +155 -0
  62. package/dist/domain/entity.js.map +1 -0
  63. package/dist/domain/events.d.ts +41 -0
  64. package/dist/domain/events.d.ts.map +1 -0
  65. package/dist/domain/events.js +21 -0
  66. package/dist/domain/events.js.map +1 -0
  67. package/dist/domain/index.d.ts +14 -0
  68. package/dist/domain/index.d.ts.map +1 -0
  69. package/dist/domain/index.js +14 -0
  70. package/dist/domain/index.js.map +1 -0
  71. package/dist/domain/value-object.d.ts +60 -0
  72. package/dist/domain/value-object.d.ts.map +1 -0
  73. package/dist/domain/value-object.js +87 -0
  74. package/dist/domain/value-object.js.map +1 -0
  75. package/dist/errors/catalog.d.ts +71 -0
  76. package/dist/errors/catalog.d.ts.map +1 -0
  77. package/dist/errors/catalog.js +71 -0
  78. package/dist/errors/catalog.js.map +1 -0
  79. package/dist/errors/http.d.ts +77 -0
  80. package/dist/errors/http.d.ts.map +1 -0
  81. package/dist/errors/http.js +74 -0
  82. package/dist/errors/http.js.map +1 -0
  83. package/dist/errors/index.d.ts +10 -0
  84. package/dist/errors/index.d.ts.map +1 -0
  85. package/dist/errors/index.js +14 -0
  86. package/dist/errors/index.js.map +1 -0
  87. package/dist/errors/response.d.ts +26 -0
  88. package/dist/errors/response.d.ts.map +1 -0
  89. package/dist/errors/response.js +34 -0
  90. package/dist/errors/response.js.map +1 -0
  91. package/dist/errors/validation.d.ts +18 -0
  92. package/dist/errors/validation.d.ts.map +1 -0
  93. package/dist/errors/validation.js +21 -0
  94. package/dist/errors/validation.js.map +1 -0
  95. package/dist/events/index.d.ts +58 -0
  96. package/dist/events/index.d.ts.map +1 -0
  97. package/dist/events/index.js +102 -0
  98. package/dist/events/index.js.map +1 -0
  99. package/dist/jobs/index.d.ts +56 -0
  100. package/dist/jobs/index.d.ts.map +1 -0
  101. package/dist/jobs/index.js +89 -0
  102. package/dist/jobs/index.js.map +1 -0
  103. package/dist/mail/index.d.ts +75 -0
  104. package/dist/mail/index.d.ts.map +1 -0
  105. package/dist/mail/index.js +84 -0
  106. package/dist/mail/index.js.map +1 -0
  107. package/dist/openapi/index.d.ts +207 -0
  108. package/dist/openapi/index.d.ts.map +1 -0
  109. package/dist/openapi/index.js +449 -0
  110. package/dist/openapi/index.js.map +1 -0
  111. package/dist/openapi/schema-introspector.d.ts +38 -0
  112. package/dist/openapi/schema-introspector.d.ts.map +1 -0
  113. package/dist/openapi/schema-introspector.js +67 -0
  114. package/dist/openapi/schema-introspector.js.map +1 -0
  115. package/dist/ports/audit.d.ts +58 -0
  116. package/dist/ports/audit.d.ts.map +1 -0
  117. package/dist/ports/audit.js +74 -0
  118. package/dist/ports/audit.js.map +1 -0
  119. package/dist/ports/auth.d.ts +23 -0
  120. package/dist/ports/auth.d.ts.map +1 -0
  121. package/dist/ports/auth.js +31 -0
  122. package/dist/ports/auth.js.map +1 -0
  123. package/dist/ports/builder.d.ts +61 -0
  124. package/dist/ports/builder.d.ts.map +1 -0
  125. package/dist/ports/builder.js +48 -0
  126. package/dist/ports/builder.js.map +1 -0
  127. package/dist/ports/cache.d.ts +15 -0
  128. package/dist/ports/cache.d.ts.map +1 -0
  129. package/dist/ports/cache.js +57 -0
  130. package/dist/ports/cache.js.map +1 -0
  131. package/dist/ports/clock.d.ts +10 -0
  132. package/dist/ports/clock.d.ts.map +1 -0
  133. package/dist/ports/clock.js +21 -0
  134. package/dist/ports/clock.js.map +1 -0
  135. package/dist/ports/events.d.ts +71 -0
  136. package/dist/ports/events.d.ts.map +1 -0
  137. package/dist/ports/events.js +2 -0
  138. package/dist/ports/events.js.map +1 -0
  139. package/dist/ports/id-generator.d.ts +12 -0
  140. package/dist/ports/id-generator.d.ts.map +1 -0
  141. package/dist/ports/id-generator.js +22 -0
  142. package/dist/ports/id-generator.js.map +1 -0
  143. package/dist/ports/index.d.ts +98 -0
  144. package/dist/ports/index.d.ts.map +1 -0
  145. package/dist/ports/index.js +67 -0
  146. package/dist/ports/index.js.map +1 -0
  147. package/dist/ports/logger.d.ts +22 -0
  148. package/dist/ports/logger.d.ts.map +1 -0
  149. package/dist/ports/logger.js +34 -0
  150. package/dist/ports/logger.js.map +1 -0
  151. package/dist/ports/mailer.d.ts +6 -0
  152. package/dist/ports/mailer.d.ts.map +1 -0
  153. package/dist/ports/mailer.js +2 -0
  154. package/dist/ports/mailer.js.map +1 -0
  155. package/dist/ports/policy.d.ts +53 -0
  156. package/dist/ports/policy.d.ts.map +1 -0
  157. package/dist/ports/policy.js +81 -0
  158. package/dist/ports/policy.js.map +1 -0
  159. package/dist/ports/rate-limit.d.ts +41 -0
  160. package/dist/ports/rate-limit.d.ts.map +1 -0
  161. package/dist/ports/rate-limit.js +37 -0
  162. package/dist/ports/rate-limit.js.map +1 -0
  163. package/dist/ports/redaction.d.ts +26 -0
  164. package/dist/ports/redaction.d.ts.map +1 -0
  165. package/dist/ports/redaction.js +126 -0
  166. package/dist/ports/redaction.js.map +1 -0
  167. package/dist/ports/schedules.d.ts +9 -0
  168. package/dist/ports/schedules.d.ts.map +1 -0
  169. package/dist/ports/schedules.js +2 -0
  170. package/dist/ports/schedules.js.map +1 -0
  171. package/dist/ports/storage.d.ts +47 -0
  172. package/dist/ports/storage.d.ts.map +1 -0
  173. package/dist/ports/storage.js +185 -0
  174. package/dist/ports/storage.js.map +1 -0
  175. package/dist/ports/testing.d.ts +73 -0
  176. package/dist/ports/testing.d.ts.map +1 -0
  177. package/dist/ports/testing.js +105 -0
  178. package/dist/ports/testing.js.map +1 -0
  179. package/dist/ports/unit-of-work.d.ts +56 -0
  180. package/dist/ports/unit-of-work.d.ts.map +1 -0
  181. package/dist/ports/unit-of-work.js +64 -0
  182. package/dist/ports/unit-of-work.js.map +1 -0
  183. package/dist/providers/index.d.ts +8 -0
  184. package/dist/providers/index.d.ts.map +1 -0
  185. package/dist/providers/index.js +8 -0
  186. package/dist/providers/index.js.map +1 -0
  187. package/dist/providers/instrumentation.d.ts +91 -0
  188. package/dist/providers/instrumentation.d.ts.map +1 -0
  189. package/dist/providers/instrumentation.js +93 -0
  190. package/dist/providers/instrumentation.js.map +1 -0
  191. package/dist/providers/provider.d.ts +146 -0
  192. package/dist/providers/provider.d.ts.map +1 -0
  193. package/dist/providers/provider.js +31 -0
  194. package/dist/providers/provider.js.map +1 -0
  195. package/dist/schedules/index.d.ts +105 -0
  196. package/dist/schedules/index.d.ts.map +1 -0
  197. package/dist/schedules/index.js +178 -0
  198. package/dist/schedules/index.js.map +1 -0
  199. package/dist/server/contract-like.d.ts +5 -0
  200. package/dist/server/contract-like.d.ts.map +1 -0
  201. package/dist/server/contract-like.js +5 -0
  202. package/dist/server/contract-like.js.map +1 -0
  203. package/dist/server/health.d.ts +41 -0
  204. package/dist/server/health.d.ts.map +1 -0
  205. package/dist/server/health.js +46 -0
  206. package/dist/server/health.js.map +1 -0
  207. package/dist/server/hooks/auth.d.ts +42 -0
  208. package/dist/server/hooks/auth.d.ts.map +1 -0
  209. package/dist/server/hooks/auth.js +61 -0
  210. package/dist/server/hooks/auth.js.map +1 -0
  211. package/dist/server/hooks/cors.d.ts +13 -0
  212. package/dist/server/hooks/cors.d.ts.map +1 -0
  213. package/dist/server/hooks/cors.js +70 -0
  214. package/dist/server/hooks/cors.js.map +1 -0
  215. package/dist/server/hooks/errors.d.ts +66 -0
  216. package/dist/server/hooks/errors.d.ts.map +1 -0
  217. package/dist/server/hooks/errors.js +83 -0
  218. package/dist/server/hooks/errors.js.map +1 -0
  219. package/dist/server/hooks/index.d.ts +12 -0
  220. package/dist/server/hooks/index.d.ts.map +1 -0
  221. package/dist/server/hooks/index.js +12 -0
  222. package/dist/server/hooks/index.js.map +1 -0
  223. package/dist/server/hooks/logging.d.ts +33 -0
  224. package/dist/server/hooks/logging.d.ts.map +1 -0
  225. package/dist/server/hooks/logging.js +90 -0
  226. package/dist/server/hooks/logging.js.map +1 -0
  227. package/dist/server/hooks/rate-limit.d.ts +29 -0
  228. package/dist/server/hooks/rate-limit.d.ts.map +1 -0
  229. package/dist/server/hooks/rate-limit.js +93 -0
  230. package/dist/server/hooks/rate-limit.js.map +1 -0
  231. package/dist/server/hooks/utils.d.ts +9 -0
  232. package/dist/server/hooks/utils.d.ts.map +1 -0
  233. package/dist/server/hooks/utils.js +16 -0
  234. package/dist/server/hooks/utils.js.map +1 -0
  235. package/dist/server/hooks.d.ts +2 -0
  236. package/dist/server/hooks.d.ts.map +1 -0
  237. package/dist/server/hooks.js +2 -0
  238. package/dist/server/hooks.js.map +1 -0
  239. package/dist/server/http.d.ts +124 -0
  240. package/dist/server/http.d.ts.map +1 -0
  241. package/dist/server/http.js +2 -0
  242. package/dist/server/http.js.map +1 -0
  243. package/dist/server/index.d.ts +19 -0
  244. package/dist/server/index.d.ts.map +1 -0
  245. package/dist/server/index.js +15 -0
  246. package/dist/server/index.js.map +1 -0
  247. package/dist/server/openapi.d.ts +32 -0
  248. package/dist/server/openapi.d.ts.map +1 -0
  249. package/dist/server/openapi.js +43 -0
  250. package/dist/server/openapi.js.map +1 -0
  251. package/dist/server/providers/index.d.ts +4 -0
  252. package/dist/server/providers/index.d.ts.map +1 -0
  253. package/dist/server/providers/index.js +4 -0
  254. package/dist/server/providers/index.js.map +1 -0
  255. package/dist/server/providers/loadProviderConfig.d.ts +7 -0
  256. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -0
  257. package/dist/server/providers/loadProviderConfig.js +42 -0
  258. package/dist/server/providers/loadProviderConfig.js.map +1 -0
  259. package/dist/server/server.d.ts +86 -0
  260. package/dist/server/server.d.ts.map +1 -0
  261. package/dist/server/server.js +1031 -0
  262. package/dist/server/server.js.map +1 -0
  263. package/dist/server/types.d.ts +3 -0
  264. package/dist/server/types.d.ts.map +1 -0
  265. package/dist/server/types.js +3 -0
  266. package/dist/server/types.js.map +1 -0
  267. package/package.json +129 -0
  268. package/src/application/index.ts +747 -0
  269. package/src/client/client.ts +1105 -0
  270. package/src/client/index.ts +45 -0
  271. package/src/client/types.ts +305 -0
  272. package/src/config/index.ts +497 -0
  273. package/src/contracts/contract-builder.ts +583 -0
  274. package/src/contracts/contract-group.ts +502 -0
  275. package/src/contracts/contract-like.ts +29 -0
  276. package/src/contracts/index.ts +53 -0
  277. package/src/contracts/openapi-meta.ts +22 -0
  278. package/src/contracts/path-template.ts +91 -0
  279. package/src/contracts/rate-limit.ts +50 -0
  280. package/src/contracts/types.ts +207 -0
  281. package/src/contracts/utils.ts +56 -0
  282. package/src/domain/entity.ts +256 -0
  283. package/src/domain/events.ts +52 -0
  284. package/src/domain/index.ts +18 -0
  285. package/src/domain/value-object.ts +135 -0
  286. package/src/errors/catalog.ts +149 -0
  287. package/src/errors/http.ts +80 -0
  288. package/src/errors/index.ts +28 -0
  289. package/src/errors/response.ts +54 -0
  290. package/src/errors/validation.ts +35 -0
  291. package/src/events/index.ts +246 -0
  292. package/src/jobs/index.ts +211 -0
  293. package/src/mail/index.ts +177 -0
  294. package/src/openapi/index.ts +865 -0
  295. package/src/openapi/schema-introspector.ts +107 -0
  296. package/src/ports/audit.ts +176 -0
  297. package/src/ports/auth.ts +76 -0
  298. package/src/ports/builder.ts +97 -0
  299. package/src/ports/cache.ts +94 -0
  300. package/src/ports/clock.ts +34 -0
  301. package/src/ports/events.ts +100 -0
  302. package/src/ports/id-generator.ts +36 -0
  303. package/src/ports/index.ts +221 -0
  304. package/src/ports/logger.ts +67 -0
  305. package/src/ports/policy.ts +242 -0
  306. package/src/ports/rate-limit.ts +91 -0
  307. package/src/ports/redaction.ts +199 -0
  308. package/src/ports/storage.ts +282 -0
  309. package/src/ports/testing.ts +234 -0
  310. package/src/ports/unit-of-work.ts +134 -0
  311. package/src/providers/index.ts +40 -0
  312. package/src/providers/instrumentation.ts +248 -0
  313. package/src/providers/provider.ts +191 -0
  314. package/src/schedules/index.ts +442 -0
  315. package/src/server/contract-like.ts +8 -0
  316. package/src/server/health.ts +82 -0
  317. package/src/server/hooks/auth.ts +147 -0
  318. package/src/server/hooks/cors.ts +87 -0
  319. package/src/server/hooks/errors.ts +126 -0
  320. package/src/server/hooks/index.ts +43 -0
  321. package/src/server/hooks/logging.ts +121 -0
  322. package/src/server/hooks/rate-limit.ts +171 -0
  323. package/src/server/hooks/utils.ts +16 -0
  324. package/src/server/hooks.ts +1 -0
  325. package/src/server/http.ts +189 -0
  326. package/src/server/index.ts +35 -0
  327. package/src/server/openapi.ts +72 -0
  328. package/src/server/providers/index.ts +3 -0
  329. package/src/server/providers/loadProviderConfig.ts +72 -0
  330. package/src/server/server.ts +1521 -0
  331. package/src/server/types.ts +2 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * CORS hook utilities for @beignet/core/server
3
+ */
4
+
5
+ import type { HttpRequestLike, ServerHook } from "../types";
6
+
7
+ export interface CorsConfig {
8
+ origins?: string[] | "*";
9
+ methods?: string[];
10
+ headers?: string[];
11
+ credentials?: boolean;
12
+ }
13
+
14
+ const DEFAULT_CORS: Required<CorsConfig> = {
15
+ origins: "*",
16
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
17
+ headers: ["Content-Type", "Authorization"],
18
+ credentials: false,
19
+ };
20
+
21
+ function appendVaryOrigin(headers: Record<string, string>): void {
22
+ const varyKey =
23
+ Object.keys(headers).find((key) => key.toLowerCase() === "vary") ?? "Vary";
24
+ const current = headers[varyKey];
25
+ const values = current?.split(",").map((value) => value.trim().toLowerCase());
26
+ if (values?.includes("origin")) return;
27
+
28
+ headers[varyKey] = current ? `${current}, Origin` : "Origin";
29
+ }
30
+
31
+ export function applyCorsHeaders(
32
+ headers: Record<string, string>,
33
+ req: HttpRequestLike,
34
+ corsConfig: CorsConfig,
35
+ ): void {
36
+ const origins = corsConfig.origins ?? DEFAULT_CORS.origins;
37
+ const methods = corsConfig.methods ?? DEFAULT_CORS.methods;
38
+ const allowedHeaders = corsConfig.headers ?? DEFAULT_CORS.headers;
39
+ const credentials = corsConfig.credentials ?? DEFAULT_CORS.credentials;
40
+
41
+ if (credentials && origins === "*") {
42
+ const requestOrigin = req.headers.get("Origin");
43
+ if (requestOrigin) {
44
+ headers["Access-Control-Allow-Origin"] = requestOrigin;
45
+ appendVaryOrigin(headers);
46
+ }
47
+ } else if (origins === "*") {
48
+ headers["Access-Control-Allow-Origin"] = "*";
49
+ } else if (Array.isArray(origins)) {
50
+ const requestOrigin = req.headers.get("Origin");
51
+ if (requestOrigin && origins.includes(requestOrigin)) {
52
+ headers["Access-Control-Allow-Origin"] = requestOrigin;
53
+ appendVaryOrigin(headers);
54
+ }
55
+ }
56
+
57
+ headers["Access-Control-Allow-Methods"] = methods.join(", ");
58
+ headers["Access-Control-Allow-Headers"] = allowedHeaders.join(", ");
59
+
60
+ if (credentials) {
61
+ headers["Access-Control-Allow-Credentials"] = "true";
62
+ }
63
+ }
64
+
65
+ export function createCorsHooks<Ctx>(config: CorsConfig): ServerHook<Ctx> {
66
+ return {
67
+ name: "cors",
68
+ onRequest: ({ req }) => {
69
+ if (req.method !== "OPTIONS") return undefined;
70
+ const headers: Record<string, string> = {};
71
+ applyCorsHeaders(headers, req, config);
72
+ return {
73
+ status: 204,
74
+ headers,
75
+ body: null,
76
+ };
77
+ },
78
+ beforeSend: ({ req, response }) => {
79
+ const headers = { ...(response.headers ?? {}) };
80
+ applyCorsHeaders(headers, req, config);
81
+ return {
82
+ ...response,
83
+ headers,
84
+ };
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Framework-agnostic error mapping utilities for @beignet/core/server
3
+ */
4
+
5
+ import { createErrorResponseBody, type ErrorResponseBody } from "../../errors";
6
+ import type { AppEnvironment } from "../health";
7
+ import { getRequestIdFromContext } from "./utils";
8
+
9
+ /**
10
+ * Re-export AppEnvironment for convenience
11
+ */
12
+ export type { AppEnvironment } from "../health";
13
+
14
+ /**
15
+ * Error mapping result
16
+ */
17
+ export interface ErrorMappingResult {
18
+ status: number;
19
+ body: unknown;
20
+ headers?: Record<string, string>;
21
+ }
22
+
23
+ /**
24
+ * Error mapping configuration
25
+ */
26
+ export interface ErrorMappingConfig<Ctx> {
27
+ /** Custom error mapper function */
28
+ mapErrorToResponse?: (err: unknown, ctx: Ctx) => ErrorMappingResult;
29
+
30
+ /** Include stack traces in error responses (default: true in dev/test, false in production) */
31
+ includeStackInResponse?: boolean;
32
+
33
+ /** Application environment */
34
+ env?: AppEnvironment;
35
+ }
36
+
37
+ /**
38
+ * Create default error response body
39
+ */
40
+ function createDefaultErrorBody(
41
+ err: unknown,
42
+ includeStack: boolean,
43
+ requestId?: string,
44
+ ): ErrorResponseBody {
45
+ return createErrorResponseBody({
46
+ code: "INTERNAL_SERVER_ERROR",
47
+ message: "Internal server error",
48
+ requestId,
49
+ details:
50
+ includeStack && err instanceof Error
51
+ ? {
52
+ error: {
53
+ message: err.message,
54
+ stack: err.stack,
55
+ },
56
+ }
57
+ : undefined,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Default error mapping function that handles unknown errors and converts them
63
+ * to a standard error response format.
64
+ *
65
+ * **Important:** This function does NOT handle AppError instances from @beignet/core/errors.
66
+ * AppError is handled separately in the router's error handling flow before reaching
67
+ * this function. This function is only called for truly unexpected errors that bypass normal
68
+ * error handling (e.g., unhandled exceptions, infrastructure errors).
69
+ *
70
+ * This function:
71
+ * 1. First tries the custom mapErrorToResponse if provided
72
+ * 2. Falls back to a default 500 error response
73
+ * 3. Optionally includes stack traces in development/test environments
74
+ *
75
+ * @param err - The error that was thrown (excluding AppError instances)
76
+ * @param ctx - The request context
77
+ * @param config - Error mapping configuration
78
+ * @returns An error mapping result with status, body, and optional headers
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * const errorConfig = {
83
+ * mapErrorToResponse: (err, ctx) => ({
84
+ * status: 500,
85
+ * body: {
86
+ * code: "INTERNAL_SERVER_ERROR",
87
+ * message: "Custom error",
88
+ * requestId: ctx.requestId,
89
+ * },
90
+ * }),
91
+ * includeStackInResponse: true,
92
+ * env: "development",
93
+ * };
94
+ *
95
+ * const result = defaultMapErrorToResponse(error, ctx, errorConfig);
96
+ * ```
97
+ */
98
+ export function defaultMapErrorToResponse<Ctx>(
99
+ err: unknown,
100
+ ctx: Ctx,
101
+ config: ErrorMappingConfig<Ctx>,
102
+ ): ErrorMappingResult {
103
+ // First, try the user's custom error handler
104
+ if (config.mapErrorToResponse) {
105
+ try {
106
+ return config.mapErrorToResponse(err, ctx);
107
+ } catch {
108
+ // Fall through to default error response below
109
+ }
110
+ }
111
+
112
+ // Determine if stack traces should be included
113
+ const includeStack =
114
+ config.includeStackInResponse ??
115
+ (config.env === "development" || config.env === "test");
116
+
117
+ // Default error response
118
+ const requestId = getRequestIdFromContext(ctx);
119
+ const body = createDefaultErrorBody(err, includeStack, requestId);
120
+
121
+ return {
122
+ status: 500,
123
+ body,
124
+ headers: { "Content-Type": "application/json" },
125
+ };
126
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Hook utilities for @beignet/core/server
3
+ */
4
+
5
+ import type { AnyPorts } from "../../ports";
6
+ import type { ServerHook } from "../http";
7
+
8
+ export {
9
+ type AuthHookArgs,
10
+ type AuthHookAssignArgs,
11
+ type AuthHookMode,
12
+ type AuthHookModeInput,
13
+ type AuthHooksOptions,
14
+ type AuthHookUnauthorizedArgs,
15
+ type CtxWithAuthPort,
16
+ createAuthHooks,
17
+ } from "./auth";
18
+ export {
19
+ applyCorsHeaders,
20
+ type CorsConfig,
21
+ createCorsHooks,
22
+ } from "./cors";
23
+ export {
24
+ defaultMapErrorToResponse,
25
+ type ErrorMappingConfig,
26
+ type ErrorMappingResult,
27
+ } from "./errors";
28
+ export {
29
+ createLoggingHooks,
30
+ type Logger,
31
+ type LoggingConfig,
32
+ } from "./logging";
33
+ export {
34
+ type CtxWithRateLimit,
35
+ createRateLimitHooks,
36
+ type RateLimitOptions,
37
+ } from "./rate-limit";
38
+
39
+ export function composeHooks<Ctx, Ports extends AnyPorts = AnyPorts>(
40
+ ...hooks: (ServerHook<Ctx, Ports> | readonly ServerHook<Ctx, Ports>[])[]
41
+ ): ServerHook<Ctx, Ports>[] {
42
+ return hooks.flatMap((hook) => (Array.isArray(hook) ? hook : [hook]));
43
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Logging hook utilities for @beignet/core/server
3
+ */
4
+
5
+ import type { HttpContractConfig } from "../../contracts";
6
+ import type { HttpRequestLike, ServerHook } from "../types";
7
+ import { getRequestIdFromContext } from "./utils";
8
+
9
+ export interface Logger {
10
+ info: (...args: unknown[]) => void;
11
+ error: (...args: unknown[]) => void;
12
+ warn?: (...args: unknown[]) => void;
13
+ debug?: (...args: unknown[]) => void;
14
+ }
15
+
16
+ export interface LoggingConfig<Ctx> {
17
+ logger?: Logger;
18
+ requestIdHeader?: string;
19
+ onRequestStart?: (args: {
20
+ ctx?: Ctx;
21
+ req: HttpRequestLike;
22
+ contract?: HttpContractConfig;
23
+ }) => void;
24
+ onRequestEnd?: (args: {
25
+ ctx?: Ctx;
26
+ req: HttpRequestLike;
27
+ res: { status: number; headers: Record<string, string> };
28
+ durationMs: number;
29
+ contract?: HttpContractConfig;
30
+ error?: unknown;
31
+ }) => void;
32
+ }
33
+
34
+ export function createLoggingHooks<Ctx>(
35
+ config: LoggingConfig<Ctx>,
36
+ ): ServerHook<Ctx> {
37
+ const requestIdHeader = config.requestIdHeader;
38
+
39
+ return {
40
+ name: "logging",
41
+ onRequest: ({ req, contract }) => {
42
+ if (config.onRequestStart) {
43
+ try {
44
+ config.onRequestStart({ req, contract, ctx: undefined });
45
+ } catch {
46
+ // Ignore logging errors
47
+ }
48
+ return undefined;
49
+ }
50
+
51
+ if (!config.logger) return undefined;
52
+ try {
53
+ const url = new URL(req.url);
54
+ config.logger.info(
55
+ { method: req.method, url: url.pathname },
56
+ "Request start",
57
+ );
58
+ } catch {
59
+ // Ignore logging errors
60
+ }
61
+ return undefined;
62
+ },
63
+ ...(requestIdHeader
64
+ ? {
65
+ beforeSend: ({ ctx, response }) => {
66
+ const requestId = getRequestIdFromContext(ctx);
67
+ if (!requestId) {
68
+ return response;
69
+ }
70
+
71
+ return {
72
+ ...response,
73
+ headers: {
74
+ ...(response.headers ?? {}),
75
+ [requestIdHeader]: String(requestId),
76
+ },
77
+ };
78
+ },
79
+ }
80
+ : {}),
81
+ afterSend: ({ ctx, req, response, durationMs, contract, error }) => {
82
+ const headers = response.headers ?? {};
83
+ if (config.onRequestEnd) {
84
+ try {
85
+ config.onRequestEnd({
86
+ ctx,
87
+ req,
88
+ res: { status: response.status, headers },
89
+ durationMs,
90
+ contract,
91
+ error,
92
+ });
93
+ } catch {
94
+ // Ignore logging errors
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (!config.logger) return;
100
+ try {
101
+ const url = new URL(req.url);
102
+ const requestId = getRequestIdFromContext(ctx);
103
+ const payload = {
104
+ method: req.method,
105
+ url: url.pathname,
106
+ status: response.status,
107
+ durationMs: Math.round(durationMs),
108
+ ...(requestId !== undefined ? { requestId } : {}),
109
+ ...(error !== undefined ? { error } : {}),
110
+ };
111
+ if (error !== undefined) {
112
+ config.logger.error(payload, "Request complete with error");
113
+ return;
114
+ }
115
+ config.logger.info(payload, "Request complete");
116
+ } catch {
117
+ // Ignore logging errors
118
+ }
119
+ },
120
+ };
121
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Rate limit hooks for @beignet/core/server
3
+ */
4
+
5
+ import type { RateLimitScope } from "../../contracts";
6
+ import { AppError, httpErrors } from "../../errors";
7
+ import type { ActivityActor, RateLimitPort } from "../../ports";
8
+ import type { HttpRequestLike, ServerHook } from "../types";
9
+
10
+ export type RateLimitPorts = {
11
+ rateLimit: RateLimitPort;
12
+ };
13
+
14
+ export type CtxWithRateLimit = {
15
+ ports: RateLimitPorts;
16
+ actor?: ActivityActor;
17
+ };
18
+
19
+ type EarlyRateLimitScope = Exclude<RateLimitScope, "user">;
20
+
21
+ export interface RateLimitOptions<Ctx> {
22
+ key?: (args: {
23
+ ctx: Ctx;
24
+ req: HttpRequestLike;
25
+ scope: RateLimitScope;
26
+ }) => string;
27
+ earlyKey?: (args: {
28
+ req: HttpRequestLike;
29
+ scope: EarlyRateLimitScope;
30
+ }) => string;
31
+ getClientIp?: (req: HttpRequestLike) => string | undefined;
32
+ }
33
+
34
+ function defaultGetClientIp(req: HttpRequestLike): string | undefined {
35
+ const xfwd = req.headers.get("x-forwarded-for") ?? "";
36
+ return xfwd.split(",")[0].trim() || undefined;
37
+ }
38
+
39
+ function emitUserKey(userId: string): string {
40
+ return `user:${userId}`;
41
+ }
42
+
43
+ function emitIpKey(ip: string): string {
44
+ return `ip:${ip}`;
45
+ }
46
+
47
+ function defaultRateLimitKey<Ctx extends CtxWithRateLimit>(
48
+ args: {
49
+ ctx: Ctx;
50
+ req: HttpRequestLike;
51
+ scope: RateLimitScope;
52
+ },
53
+ getClientIp: (req: HttpRequestLike) => string | undefined,
54
+ ): string {
55
+ const { ctx, req, scope } = args;
56
+
57
+ if (scope === "user" && ctx.actor?.type === "user" && ctx.actor.id) {
58
+ return emitUserKey(ctx.actor.id);
59
+ }
60
+
61
+ if (scope === "ip") {
62
+ const ip = getClientIp(req) || "unknown";
63
+ return emitIpKey(ip);
64
+ }
65
+
66
+ return "global";
67
+ }
68
+
69
+ function defaultEarlyRateLimitKey(
70
+ args: {
71
+ req: HttpRequestLike;
72
+ scope: EarlyRateLimitScope;
73
+ },
74
+ getClientIp: (req: HttpRequestLike) => string | undefined,
75
+ ): string {
76
+ if (args.scope === "ip") {
77
+ const ip = getClientIp(args.req) || "unknown";
78
+ return emitIpKey(ip);
79
+ }
80
+
81
+ return "global";
82
+ }
83
+
84
+ async function enforceRateLimit(
85
+ rateLimit: RateLimitPort,
86
+ args: {
87
+ key: string;
88
+ limit: number;
89
+ windowSec: number;
90
+ scope: RateLimitScope;
91
+ },
92
+ ): Promise<void> {
93
+ const result = await rateLimit.hit({
94
+ key: args.key,
95
+ limit: args.limit,
96
+ windowSec: args.windowSec,
97
+ });
98
+
99
+ if (result.allowed) {
100
+ return;
101
+ }
102
+
103
+ throw new AppError(
104
+ httpErrors.TooManyRequests,
105
+ {
106
+ key: args.key,
107
+ scope: args.scope,
108
+ retryAfterSeconds: result.retryAfterSeconds,
109
+ resetAt: result.resetAt?.toISOString() ?? null,
110
+ },
111
+ "Rate limit exceeded",
112
+ );
113
+ }
114
+
115
+ export function createRateLimitHooks<Ctx extends CtxWithRateLimit>(
116
+ options: RateLimitOptions<Ctx> = {},
117
+ ): ServerHook<Ctx, RateLimitPorts> {
118
+ const getClientIp = options.getClientIp ?? defaultGetClientIp;
119
+
120
+ return {
121
+ name: "rate-limit",
122
+ onRequest: async ({ contract, ports, req }) => {
123
+ const rlMeta = contract.metadata?.rateLimit;
124
+ if (!rlMeta) {
125
+ return undefined;
126
+ }
127
+
128
+ const scope: RateLimitScope = rlMeta.scope ?? "global";
129
+ if (scope === "user") {
130
+ return undefined;
131
+ }
132
+
133
+ const key =
134
+ options.earlyKey?.({ req, scope }) ??
135
+ defaultEarlyRateLimitKey({ req, scope }, getClientIp);
136
+
137
+ await enforceRateLimit(ports.rateLimit, {
138
+ key,
139
+ limit: rlMeta.max,
140
+ windowSec: rlMeta.windowSec,
141
+ scope,
142
+ });
143
+
144
+ return undefined;
145
+ },
146
+ beforeHandle: async ({ ctx, contract, req }) => {
147
+ const rlMeta = contract.metadata?.rateLimit;
148
+ if (!rlMeta) {
149
+ return undefined;
150
+ }
151
+
152
+ const scope: RateLimitScope = rlMeta.scope ?? "global";
153
+ if (scope !== "user") {
154
+ return undefined;
155
+ }
156
+
157
+ const key =
158
+ options.key?.({ ctx, req, scope }) ??
159
+ defaultRateLimitKey({ ctx, req, scope }, getClientIp);
160
+
161
+ await enforceRateLimit(ctx.ports.rateLimit, {
162
+ key,
163
+ limit: rlMeta.max,
164
+ windowSec: rlMeta.windowSec,
165
+ scope,
166
+ });
167
+
168
+ return undefined;
169
+ },
170
+ };
171
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared utilities for server hooks.
3
+ */
4
+
5
+ /**
6
+ * Extract requestId from context object if it exists.
7
+ * This helper reduces type casting duplication throughout the codebase.
8
+ */
9
+ export function getRequestIdFromContext(ctx: unknown): string | undefined {
10
+ if (ctx !== null && ctx !== undefined && typeof ctx === "object") {
11
+ const ctxRecord = ctx as Record<string, unknown>;
12
+ const requestId = ctxRecord.requestId;
13
+ return typeof requestId === "string" ? requestId : undefined;
14
+ }
15
+ return undefined;
16
+ }
@@ -0,0 +1 @@
1
+ export * from "./hooks/index";