@beignet/core 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 (360) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +821 -30
  3. package/dist/application/index.d.ts +28 -2
  4. package/dist/application/index.d.ts.map +1 -1
  5. package/dist/application/index.js +140 -12
  6. package/dist/application/index.js.map +1 -1
  7. package/dist/client/client.d.ts +2 -2
  8. package/dist/client/client.d.ts.map +1 -1
  9. package/dist/client/client.js +136 -48
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/error-messages.d.ts +14 -0
  12. package/dist/client/error-messages.d.ts.map +1 -0
  13. package/dist/client/error-messages.js +23 -0
  14. package/dist/client/error-messages.js.map +1 -0
  15. package/dist/client/index.d.ts +8 -4
  16. package/dist/client/index.d.ts.map +1 -1
  17. package/dist/client/index.js +6 -2
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/client/types.d.ts +35 -5
  20. package/dist/client/types.d.ts.map +1 -1
  21. package/dist/client-only.d.ts +8 -0
  22. package/dist/client-only.d.ts.map +1 -0
  23. package/dist/client-only.js +8 -0
  24. package/dist/client-only.js.map +1 -0
  25. package/dist/config/index.d.ts +5 -5
  26. package/dist/config/index.d.ts.map +1 -1
  27. package/dist/config/index.js +2 -2
  28. package/dist/config/index.js.map +1 -1
  29. package/dist/contracts/catalog-errors.d.ts +27 -0
  30. package/dist/contracts/catalog-errors.d.ts.map +1 -0
  31. package/dist/contracts/catalog-errors.js +69 -0
  32. package/dist/contracts/catalog-errors.js.map +1 -0
  33. package/dist/contracts/contract-builder.d.ts +15 -12
  34. package/dist/contracts/contract-builder.d.ts.map +1 -1
  35. package/dist/contracts/contract-builder.js +15 -41
  36. package/dist/contracts/contract-builder.js.map +1 -1
  37. package/dist/contracts/contract-group.d.ts +11 -8
  38. package/dist/contracts/contract-group.d.ts.map +1 -1
  39. package/dist/contracts/contract-group.js +13 -40
  40. package/dist/contracts/contract-group.js.map +1 -1
  41. package/dist/contracts/contract-like.d.ts +1 -1
  42. package/dist/contracts/contract-like.d.ts.map +1 -1
  43. package/dist/contracts/index.d.ts +13 -9
  44. package/dist/contracts/index.d.ts.map +1 -1
  45. package/dist/contracts/index.js +9 -5
  46. package/dist/contracts/index.js.map +1 -1
  47. package/dist/contracts/openapi-meta.d.ts +48 -0
  48. package/dist/contracts/openapi-meta.d.ts.map +1 -1
  49. package/dist/contracts/openapi-meta.js +3 -0
  50. package/dist/contracts/openapi-meta.js.map +1 -1
  51. package/dist/contracts/path-template.d.ts +1 -1
  52. package/dist/contracts/path-template.js +2 -2
  53. package/dist/contracts/path-template.js.map +1 -1
  54. package/dist/contracts/schema-shape.d.ts +37 -0
  55. package/dist/contracts/schema-shape.d.ts.map +1 -0
  56. package/dist/contracts/schema-shape.js +61 -0
  57. package/dist/contracts/schema-shape.js.map +1 -0
  58. package/dist/contracts/success-status.d.ts +32 -0
  59. package/dist/contracts/success-status.d.ts.map +1 -0
  60. package/dist/contracts/success-status.js +18 -0
  61. package/dist/contracts/success-status.js.map +1 -0
  62. package/dist/contracts/types.d.ts +25 -5
  63. package/dist/contracts/types.d.ts.map +1 -1
  64. package/dist/contracts/types.js.map +1 -1
  65. package/dist/contracts/utils.d.ts +1 -1
  66. package/dist/contracts/utils.d.ts.map +1 -1
  67. package/dist/contracts/utils.js +1 -1
  68. package/dist/contracts/utils.js.map +1 -1
  69. package/dist/domain/events.d.ts +1 -1
  70. package/dist/domain/events.d.ts.map +1 -1
  71. package/dist/domain/events.js +1 -1
  72. package/dist/domain/events.js.map +1 -1
  73. package/dist/domain/index.d.ts +3 -3
  74. package/dist/domain/index.d.ts.map +1 -1
  75. package/dist/domain/index.js +3 -3
  76. package/dist/domain/index.js.map +1 -1
  77. package/dist/errors/catalog.d.ts +9 -1
  78. package/dist/errors/catalog.d.ts.map +1 -1
  79. package/dist/errors/catalog.js +7 -1
  80. package/dist/errors/catalog.js.map +1 -1
  81. package/dist/errors/http.d.ts +10 -0
  82. package/dist/errors/http.d.ts.map +1 -1
  83. package/dist/errors/http.js +11 -1
  84. package/dist/errors/http.js.map +1 -1
  85. package/dist/errors/index.d.ts +4 -4
  86. package/dist/errors/index.d.ts.map +1 -1
  87. package/dist/errors/index.js +4 -4
  88. package/dist/errors/index.js.map +1 -1
  89. package/dist/errors/response.d.ts +4 -1
  90. package/dist/errors/response.d.ts.map +1 -1
  91. package/dist/errors/response.js.map +1 -1
  92. package/dist/events/index.d.ts +10 -12
  93. package/dist/events/index.d.ts.map +1 -1
  94. package/dist/events/index.js +10 -10
  95. package/dist/events/index.js.map +1 -1
  96. package/dist/idempotency/index.d.ts +5 -3
  97. package/dist/idempotency/index.d.ts.map +1 -1
  98. package/dist/idempotency/index.js.map +1 -1
  99. package/dist/jobs/index.d.ts +148 -16
  100. package/dist/jobs/index.d.ts.map +1 -1
  101. package/dist/jobs/index.js +174 -14
  102. package/dist/jobs/index.js.map +1 -1
  103. package/dist/notifications/index.d.ts +14 -16
  104. package/dist/notifications/index.d.ts.map +1 -1
  105. package/dist/notifications/index.js +14 -14
  106. package/dist/notifications/index.js.map +1 -1
  107. package/dist/openapi/index.d.ts +8 -3
  108. package/dist/openapi/index.d.ts.map +1 -1
  109. package/dist/openapi/index.js +41 -29
  110. package/dist/openapi/index.js.map +1 -1
  111. package/dist/openapi/schema-introspector.d.ts +37 -0
  112. package/dist/openapi/schema-introspector.d.ts.map +1 -1
  113. package/dist/openapi/schema-introspector.js +23 -17
  114. package/dist/openapi/schema-introspector.js.map +1 -1
  115. package/dist/outbox/index.d.ts +18 -4
  116. package/dist/outbox/index.d.ts.map +1 -1
  117. package/dist/outbox/index.js +104 -4
  118. package/dist/outbox/index.js.map +1 -1
  119. package/dist/ports/audit.d.ts +56 -10
  120. package/dist/ports/audit.d.ts.map +1 -1
  121. package/dist/ports/audit.js +71 -3
  122. package/dist/ports/audit.js.map +1 -1
  123. package/dist/ports/auth.d.ts +92 -0
  124. package/dist/ports/auth.d.ts.map +1 -1
  125. package/dist/ports/auth.js +92 -0
  126. package/dist/ports/auth.js.map +1 -1
  127. package/dist/ports/events.d.ts +2 -2
  128. package/dist/ports/events.d.ts.map +1 -1
  129. package/dist/ports/index.d.ts +62 -33
  130. package/dist/ports/index.d.ts.map +1 -1
  131. package/dist/ports/index.js +28 -34
  132. package/dist/ports/index.js.map +1 -1
  133. package/dist/ports/policy.d.ts +32 -3
  134. package/dist/ports/policy.d.ts.map +1 -1
  135. package/dist/ports/policy.js +13 -2
  136. package/dist/ports/policy.js.map +1 -1
  137. package/dist/ports/testing.d.ts +1030 -2
  138. package/dist/ports/testing.d.ts.map +1 -1
  139. package/dist/ports/testing.js +1031 -1
  140. package/dist/ports/testing.js.map +1 -1
  141. package/dist/ports/unbound.d.ts +21 -0
  142. package/dist/ports/unbound.d.ts.map +1 -0
  143. package/dist/ports/unbound.js +57 -0
  144. package/dist/ports/unbound.js.map +1 -0
  145. package/dist/ports/unit-of-work.d.ts +1 -1
  146. package/dist/ports/unit-of-work.d.ts.map +1 -1
  147. package/dist/ports/unit-of-work.js +1 -1
  148. package/dist/ports/unit-of-work.js.map +1 -1
  149. package/dist/providers/index.d.ts +3 -2
  150. package/dist/providers/index.d.ts.map +1 -1
  151. package/dist/providers/index.js +3 -2
  152. package/dist/providers/index.js.map +1 -1
  153. package/dist/providers/instrumentation.d.ts +46 -5
  154. package/dist/providers/instrumentation.d.ts.map +1 -1
  155. package/dist/providers/instrumentation.js +25 -6
  156. package/dist/providers/instrumentation.js.map +1 -1
  157. package/dist/providers/metadata.d.ts +39 -0
  158. package/dist/providers/metadata.d.ts.map +1 -0
  159. package/dist/providers/metadata.js +169 -0
  160. package/dist/providers/metadata.js.map +1 -0
  161. package/dist/providers/provider.d.ts +114 -9
  162. package/dist/providers/provider.d.ts.map +1 -1
  163. package/dist/providers/provider.js +3 -20
  164. package/dist/providers/provider.js.map +1 -1
  165. package/dist/schedules/index.d.ts +94 -13
  166. package/dist/schedules/index.d.ts.map +1 -1
  167. package/dist/schedules/index.js +66 -12
  168. package/dist/schedules/index.js.map +1 -1
  169. package/dist/server/audit-context.d.ts +29 -0
  170. package/dist/server/audit-context.d.ts.map +1 -0
  171. package/dist/server/audit-context.js +44 -0
  172. package/dist/server/audit-context.js.map +1 -0
  173. package/dist/server/context.d.ts +141 -0
  174. package/dist/server/context.d.ts.map +1 -0
  175. package/dist/server/context.js +39 -0
  176. package/dist/server/context.js.map +1 -0
  177. package/dist/server/contract-like.d.ts +1 -1
  178. package/dist/server/contract-like.d.ts.map +1 -1
  179. package/dist/server/contract-like.js +1 -1
  180. package/dist/server/contract-like.js.map +1 -1
  181. package/dist/server/health.d.ts +2 -2
  182. package/dist/server/health.d.ts.map +1 -1
  183. package/dist/server/hooks/auth.d.ts +89 -65
  184. package/dist/server/hooks/auth.d.ts.map +1 -1
  185. package/dist/server/hooks/auth.js +84 -55
  186. package/dist/server/hooks/auth.js.map +1 -1
  187. package/dist/server/hooks/cors.d.ts +1 -1
  188. package/dist/server/hooks/cors.d.ts.map +1 -1
  189. package/dist/server/hooks/errors.d.ts +2 -2
  190. package/dist/server/hooks/errors.d.ts.map +1 -1
  191. package/dist/server/hooks/errors.js +2 -2
  192. package/dist/server/hooks/errors.js.map +1 -1
  193. package/dist/server/hooks/idempotency.d.ts +78 -0
  194. package/dist/server/hooks/idempotency.d.ts.map +1 -0
  195. package/dist/server/hooks/idempotency.js +154 -0
  196. package/dist/server/hooks/idempotency.js.map +1 -0
  197. package/dist/server/hooks/index.d.ts +8 -7
  198. package/dist/server/hooks/index.d.ts.map +1 -1
  199. package/dist/server/hooks/index.js +6 -5
  200. package/dist/server/hooks/index.js.map +1 -1
  201. package/dist/server/hooks/logging.d.ts +2 -2
  202. package/dist/server/hooks/logging.d.ts.map +1 -1
  203. package/dist/server/hooks/logging.js +1 -1
  204. package/dist/server/hooks/logging.js.map +1 -1
  205. package/dist/server/hooks/rate-limit.d.ts +25 -7
  206. package/dist/server/hooks/rate-limit.d.ts.map +1 -1
  207. package/dist/server/hooks/rate-limit.js +47 -12
  208. package/dist/server/hooks/rate-limit.js.map +1 -1
  209. package/dist/server/hooks.d.ts +1 -1
  210. package/dist/server/hooks.d.ts.map +1 -1
  211. package/dist/server/hooks.js +1 -1
  212. package/dist/server/hooks.js.map +1 -1
  213. package/dist/server/http.d.ts +84 -6
  214. package/dist/server/http.d.ts.map +1 -1
  215. package/dist/server/index.d.ts +36 -12
  216. package/dist/server/index.d.ts.map +1 -1
  217. package/dist/server/index.js +24 -8
  218. package/dist/server/index.js.map +1 -1
  219. package/dist/server/instrumentation.d.ts +108 -0
  220. package/dist/server/instrumentation.d.ts.map +1 -0
  221. package/dist/server/instrumentation.js +297 -0
  222. package/dist/server/instrumentation.js.map +1 -0
  223. package/dist/server/openapi.d.ts +3 -3
  224. package/dist/server/openapi.d.ts.map +1 -1
  225. package/dist/server/openapi.js +1 -1
  226. package/dist/server/openapi.js.map +1 -1
  227. package/dist/server/providers/index.d.ts +3 -3
  228. package/dist/server/providers/index.d.ts.map +1 -1
  229. package/dist/server/providers/index.js +3 -3
  230. package/dist/server/providers/index.js.map +1 -1
  231. package/dist/server/providers/loadProviderConfig.d.ts +2 -2
  232. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
  233. package/dist/server/providers/loadProviderConfig.js +2 -2
  234. package/dist/server/providers/loadProviderConfig.js.map +1 -1
  235. package/dist/server/request-context.d.ts +67 -0
  236. package/dist/server/request-context.d.ts.map +1 -0
  237. package/dist/server/request-context.js +79 -0
  238. package/dist/server/request-context.js.map +1 -0
  239. package/dist/server/server-context.d.ts +38 -0
  240. package/dist/server/server-context.d.ts.map +1 -0
  241. package/dist/server/server-context.js +38 -0
  242. package/dist/server/server-context.js.map +1 -0
  243. package/dist/server/server.d.ts +148 -35
  244. package/dist/server/server.d.ts.map +1 -1
  245. package/dist/server/server.js +482 -145
  246. package/dist/server/server.js.map +1 -1
  247. package/dist/server/types.d.ts +2 -2
  248. package/dist/server/types.d.ts.map +1 -1
  249. package/dist/server/types.js +2 -2
  250. package/dist/server/types.js.map +1 -1
  251. package/dist/server/use-case-route.d.ts +263 -0
  252. package/dist/server/use-case-route.d.ts.map +1 -0
  253. package/dist/server/use-case-route.js +77 -0
  254. package/dist/server/use-case-route.js.map +1 -0
  255. package/dist/server-only.d.ts +8 -0
  256. package/dist/server-only.d.ts.map +1 -0
  257. package/dist/server-only.js +8 -0
  258. package/dist/server-only.js.map +1 -0
  259. package/dist/tasks/index.d.ts +139 -0
  260. package/dist/tasks/index.d.ts.map +1 -0
  261. package/dist/tasks/index.js +98 -0
  262. package/dist/tasks/index.js.map +1 -0
  263. package/dist/testing/index.d.ts +611 -5
  264. package/dist/testing/index.d.ts.map +1 -1
  265. package/dist/testing/index.js +434 -4
  266. package/dist/testing/index.js.map +1 -1
  267. package/dist/tracing/index.d.ts +89 -0
  268. package/dist/tracing/index.d.ts.map +1 -0
  269. package/dist/tracing/index.js +101 -0
  270. package/dist/tracing/index.js.map +1 -0
  271. package/dist/uploads/client.d.ts +278 -0
  272. package/dist/uploads/client.d.ts.map +1 -0
  273. package/dist/uploads/client.js +428 -0
  274. package/dist/uploads/client.js.map +1 -0
  275. package/dist/uploads/index.d.ts +361 -0
  276. package/dist/uploads/index.d.ts.map +1 -0
  277. package/dist/uploads/index.js +543 -0
  278. package/dist/uploads/index.js.map +1 -0
  279. package/package.json +34 -3
  280. package/src/application/index.ts +193 -10
  281. package/src/client/client.ts +148 -150
  282. package/src/client/error-messages.ts +35 -0
  283. package/src/client/index.ts +12 -4
  284. package/src/client/types.ts +44 -5
  285. package/src/client-only.ts +7 -0
  286. package/src/config/index.ts +6 -6
  287. package/src/contracts/catalog-errors.ts +115 -0
  288. package/src/contracts/contract-builder.ts +39 -76
  289. package/src/contracts/contract-group.ts +33 -68
  290. package/src/contracts/contract-like.ts +1 -1
  291. package/src/contracts/index.ts +24 -11
  292. package/src/contracts/openapi-meta.ts +55 -0
  293. package/src/contracts/path-template.ts +2 -2
  294. package/src/contracts/schema-shape.ts +75 -0
  295. package/src/contracts/success-status.ts +68 -0
  296. package/src/contracts/types.ts +32 -5
  297. package/src/contracts/utils.ts +5 -2
  298. package/src/domain/events.ts +6 -2
  299. package/src/domain/index.ts +3 -3
  300. package/src/errors/catalog.ts +9 -1
  301. package/src/errors/http.ts +11 -1
  302. package/src/errors/index.ts +4 -4
  303. package/src/errors/response.ts +4 -1
  304. package/src/events/index.ts +12 -26
  305. package/src/idempotency/index.ts +5 -3
  306. package/src/jobs/index.ts +340 -29
  307. package/src/notifications/index.ts +17 -27
  308. package/src/openapi/index.ts +73 -38
  309. package/src/openapi/schema-introspector.ts +68 -17
  310. package/src/outbox/index.ts +151 -6
  311. package/src/ports/audit.ts +120 -11
  312. package/src/ports/auth.ts +132 -0
  313. package/src/ports/events.ts +2 -2
  314. package/src/ports/index.ts +104 -35
  315. package/src/ports/policy.ts +50 -3
  316. package/src/ports/testing.ts +2220 -33
  317. package/src/ports/unbound.ts +64 -0
  318. package/src/ports/unit-of-work.ts +6 -2
  319. package/src/providers/index.ts +16 -3
  320. package/src/providers/instrumentation.ts +93 -8
  321. package/src/providers/metadata.ts +234 -0
  322. package/src/providers/provider.ts +168 -9
  323. package/src/schedules/index.ts +173 -23
  324. package/src/server/audit-context.ts +45 -0
  325. package/src/server/context.ts +224 -0
  326. package/src/server/contract-like.ts +1 -1
  327. package/src/server/health.ts +2 -2
  328. package/src/server/hooks/auth.ts +175 -158
  329. package/src/server/hooks/cors.ts +1 -1
  330. package/src/server/hooks/errors.ts +7 -4
  331. package/src/server/hooks/idempotency.ts +263 -0
  332. package/src/server/hooks/index.ts +15 -12
  333. package/src/server/hooks/logging.ts +3 -3
  334. package/src/server/hooks/rate-limit.ts +85 -17
  335. package/src/server/hooks.ts +1 -1
  336. package/src/server/http.ts +112 -6
  337. package/src/server/index.ts +63 -12
  338. package/src/server/instrumentation.ts +470 -0
  339. package/src/server/openapi.ts +4 -4
  340. package/src/server/providers/index.ts +6 -3
  341. package/src/server/providers/loadProviderConfig.ts +4 -4
  342. package/src/server/request-context.ts +116 -0
  343. package/src/server/server-context.ts +44 -0
  344. package/src/server/server.ts +1045 -229
  345. package/src/server/types.ts +2 -2
  346. package/src/server/use-case-route.ts +430 -0
  347. package/src/server-only.ts +7 -0
  348. package/src/tasks/index.ts +275 -0
  349. package/src/testing/index.ts +1153 -6
  350. package/src/tracing/index.ts +176 -0
  351. package/src/uploads/client.ts +861 -0
  352. package/src/uploads/index.ts +1071 -0
  353. package/dist/ports/mailer.d.ts +0 -6
  354. package/dist/ports/mailer.d.ts.map +0 -1
  355. package/dist/ports/mailer.js +0 -2
  356. package/dist/ports/mailer.js.map +0 -1
  357. package/dist/ports/schedules.d.ts +0 -9
  358. package/dist/ports/schedules.d.ts.map +0 -1
  359. package/dist/ports/schedules.js +0 -2
  360. package/dist/ports/schedules.js.map +0 -1
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Idempotency hooks for @beignet/core/server
3
+ */
4
+ import { AppError, httpErrors } from "../../errors/index.js";
5
+ import { createIdempotencyFingerprint, IdempotencyConflictError, IdempotencyInProgressError, } from "../../idempotency/index.js";
6
+ /**
7
+ * Header set on replayed responses.
8
+ */
9
+ const IDEMPOTENCY_REPLAYED_HEADER = "idempotency-replayed";
10
+ function defaultIdempotencyScope(ctx, meta) {
11
+ const mode = meta.scope ?? "global";
12
+ switch (mode) {
13
+ case "global":
14
+ return "global";
15
+ case "actor":
16
+ return { actorId: ctx.actor?.id };
17
+ case "tenant":
18
+ return { tenantId: ctx.tenant?.id };
19
+ case "actor-tenant":
20
+ return { actorId: ctx.actor?.id, tenantId: ctx.tenant?.id };
21
+ }
22
+ }
23
+ function isReplayableHttpResponse(value) {
24
+ if (typeof value !== "object" || value === null)
25
+ return false;
26
+ const candidate = value;
27
+ if (typeof candidate.status !== "number")
28
+ return false;
29
+ if (candidate.headers !== undefined &&
30
+ (typeof candidate.headers !== "object" ||
31
+ candidate.headers === null ||
32
+ Array.isArray(candidate.headers))) {
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+ /**
38
+ * Create metadata-driven idempotency hooks.
39
+ *
40
+ * The hook reads `contract.metadata.idempotency` and enforces it with
41
+ * `ctx.ports.idempotency`. In `beforeHandle` it reserves the client key after
42
+ * request parsing, replays completed matching responses with an
43
+ * `idempotency-replayed: true` header, and rejects in-progress or conflicting
44
+ * keys with the framework `IdempotencyInProgress`/`IdempotencyConflict` catalog
45
+ * errors. In `beforeSend` it stores 2xx framework-neutral responses for replay
46
+ * and releases the reservation for errors, non-2xx responses, and native
47
+ * `Response` results, which are not replayable.
48
+ *
49
+ * Use `runIdempotently(...)` from `@beignet/core/idempotency` for non-HTTP
50
+ * workflows such as jobs, listeners, webhooks, and schedules.
51
+ *
52
+ * @param options - Optional namespace, scope, and fingerprint-input builders.
53
+ * @returns A server hook backed by `ctx.ports.idempotency`.
54
+ */
55
+ export function createIdempotencyHooks(options = {}) {
56
+ const pending = new WeakMap();
57
+ return {
58
+ name: "idempotency",
59
+ beforeHandle: async ({ ctx, contract, req, path, query, body }) => {
60
+ const meta = contract.metadata?.idempotency;
61
+ if (!meta) {
62
+ return undefined;
63
+ }
64
+ const header = (meta.header ?? "idempotency-key").toLowerCase();
65
+ const key = req.headers.get(header);
66
+ if (!key) {
67
+ if (meta.required) {
68
+ throw new AppError(httpErrors.BadRequest, {
69
+ contract: contract.name,
70
+ header,
71
+ }, `Missing required idempotency key header "${header}"`);
72
+ }
73
+ return undefined;
74
+ }
75
+ const namespace = options.namespace?.({ contract: { name: contract.name } }) ??
76
+ `http.${contract.name}`;
77
+ const scope = options.scope?.({ ctx, req, meta }) ??
78
+ defaultIdempotencyScope(ctx, meta);
79
+ const fingerprint = await createIdempotencyFingerprint(options.fingerprintInput?.({ path, query, body }) ?? {
80
+ path,
81
+ query,
82
+ body,
83
+ });
84
+ const reservation = await ctx.ports.idempotency.reserve({
85
+ namespace,
86
+ key,
87
+ scope,
88
+ fingerprint,
89
+ ttlSec: meta.ttlSec,
90
+ });
91
+ switch (reservation.status) {
92
+ case "replay": {
93
+ if (!isReplayableHttpResponse(reservation.result)) {
94
+ throw new AppError(httpErrors.InternalServerError, { namespace, key }, `Stored idempotency result for "${namespace}" key "${key}" is not a replayable HTTP response`);
95
+ }
96
+ return {
97
+ status: reservation.result.status,
98
+ headers: {
99
+ ...(reservation.result.headers ?? {}),
100
+ [IDEMPOTENCY_REPLAYED_HEADER]: "true",
101
+ },
102
+ body: reservation.result.body,
103
+ };
104
+ }
105
+ case "inProgress": {
106
+ throw new IdempotencyInProgressError(reservation);
107
+ }
108
+ case "conflict": {
109
+ throw new IdempotencyConflictError(reservation);
110
+ }
111
+ case "reserved": {
112
+ pending.set(req, {
113
+ port: ctx.ports.idempotency,
114
+ namespace,
115
+ key,
116
+ scope,
117
+ fingerprint,
118
+ });
119
+ return undefined;
120
+ }
121
+ }
122
+ },
123
+ beforeSend: async ({ req, response, error, native }) => {
124
+ const reservation = pending.get(req);
125
+ if (!reservation) {
126
+ return undefined;
127
+ }
128
+ pending.delete(req);
129
+ const { port, namespace, key, scope, fingerprint } = reservation;
130
+ if (!native &&
131
+ !error &&
132
+ response.status >= 200 &&
133
+ response.status < 300) {
134
+ await port.complete({
135
+ namespace,
136
+ key,
137
+ scope,
138
+ fingerprint,
139
+ result: {
140
+ status: response.status,
141
+ headers: response.headers,
142
+ body: response.body,
143
+ },
144
+ });
145
+ return undefined;
146
+ }
147
+ // Errors, non-2xx responses, and native `Response` results release the
148
+ // reservation. Streams are not replayable.
149
+ await port.fail({ namespace, key, scope, fingerprint, error });
150
+ return undefined;
151
+ },
152
+ };
153
+ }
154
+ //# sourceMappingURL=idempotency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/server/hooks/idempotency.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Hook utilities for @beignet/core/server
3
3
  */
4
- import type { AnyPorts } from "../../ports";
5
- import type { ServerHook } from "../http";
6
- export { type AuthHookArgs, type AuthHookAssignArgs, type AuthHookMode, type AuthHookModeInput, type AuthHooksOptions, type AuthHookUnauthorizedArgs, type CtxWithAuthPort, createAuthHooks, } from "./auth";
7
- export { applyCorsHeaders, type CorsConfig, createCorsHooks, } from "./cors";
8
- export { defaultMapErrorToResponse, type ErrorMappingConfig, type ErrorMappingResult, } from "./errors";
9
- export { createLoggingHooks, type Logger, type LoggingConfig, } from "./logging";
10
- export { type CtxWithRateLimit, createRateLimitHooks, type RateLimitOptions, } from "./rate-limit";
4
+ import type { AnyPorts } from "../../ports/index.js";
5
+ import type { ServerHook } from "../http.js";
6
+ export { type AuthHookArgs, type AuthHooksOptions, type AuthRouteHooks, createAuthHooks, } from "./auth.js";
7
+ export { applyCorsHeaders, type CorsConfig, createCorsHooks, } from "./cors.js";
8
+ export { defaultMapErrorToResponse, type ErrorMappingConfig, type ErrorMappingResult, } from "./errors.js";
9
+ export { type CtxWithIdempotency, createIdempotencyHooks, type IdempotencyHooksOptions, type IdempotencyPorts, } from "./idempotency.js";
10
+ export { createLoggingHooks, type Logger, type LoggingConfig, } from "./logging.js";
11
+ export { type CtxWithRateLimit, createRateLimitHooks, type RateLimitIpSource, type RateLimitOptions, } from "./rate-limit.js";
11
12
  /**
12
13
  * Flatten hook arrays into a single hook list.
13
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,gBAAgB,EAChB,KAAK,UAAU,EACf,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,kBAAkB,EAClB,KAAK,MAAM,EACX,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,gBAAgB,EACrB,oBAAoB,EACpB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACjE,GAAG,KAAK,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,GACvE,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAE1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAChB,KAAK,UAAU,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,yBAAyB,EACzB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,kBAAkB,EACvB,sBAAsB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,KAAK,MAAM,EACX,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,gBAAgB,EACrB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AAEzB;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACjE,GAAG,KAAK,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,GACvE,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAE1B"}
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Hook utilities for @beignet/core/server
3
3
  */
4
- export { createAuthHooks, } from "./auth";
5
- export { applyCorsHeaders, createCorsHooks, } from "./cors";
6
- export { defaultMapErrorToResponse, } from "./errors";
7
- export { createLoggingHooks, } from "./logging";
8
- export { createRateLimitHooks, } from "./rate-limit";
4
+ export { createAuthHooks, } from "./auth.js";
5
+ export { applyCorsHeaders, createCorsHooks, } from "./cors.js";
6
+ export { defaultMapErrorToResponse, } from "./errors.js";
7
+ export { createIdempotencyHooks, } from "./idempotency.js";
8
+ export { createLoggingHooks, } from "./logging.js";
9
+ export { createRateLimitHooks, } from "./rate-limit.js";
9
10
  /**
10
11
  * Flatten hook arrays into a single hook list.
11
12
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAQL,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,gBAAgB,EAEhB,eAAe,GAChB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,yBAAyB,GAG1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,kBAAkB,GAGnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,oBAAoB,GAErB,MAAM,cAAc,CAAC;AAEtB;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAG,KAAqE;IAExE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAIL,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAEhB,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,yBAAyB,GAG1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,sBAAsB,GAGvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,GAGnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,oBAAoB,GAGrB,MAAM,iBAAiB,CAAC;AAEzB;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAG,KAAqE;IAExE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC"}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Logging hook utilities for @beignet/core/server
3
3
  */
4
- import type { HttpContractConfig } from "../../contracts";
5
- import type { HttpRequestLike, ServerHook } from "../types";
4
+ import type { HttpContractConfig } from "../../contracts/index.js";
5
+ import type { HttpRequestLike, ServerHook } from "../types.js";
6
6
  /**
7
7
  * Minimal logger shape accepted by `createLoggingHooks(...)`.
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../src/server/hooks/logging.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG5D;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC;;OAEG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,GAAG;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QACtB,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,GAAG,EAAE,eAAe,CAAC;QACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;KAC/B,KAAK,IAAI,CAAC;IACX;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QACpB,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,GAAG,EAAE,eAAe,CAAC;QACrB,GAAG,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAC;QACzD,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;QAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,KAAK,IAAI,CAAC;CACZ;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EACpC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,GACzB,UAAU,CAAC,GAAG,CAAC,CAqFjB"}
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../src/server/hooks/logging.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC;;OAEG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,GAAG;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QACtB,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,GAAG,EAAE,eAAe,CAAC;QACrB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;KAC/B,KAAK,IAAI,CAAC;IACX;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QACpB,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,GAAG,EAAE,eAAe,CAAC;QACrB,GAAG,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAC;QACzD,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;QAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,KAAK,IAAI,CAAC;CACZ;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EACpC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,GACzB,UAAU,CAAC,GAAG,CAAC,CAqFjB"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Logging hook utilities for @beignet/core/server
3
3
  */
4
- import { getRequestIdFromContext } from "./utils";
4
+ import { getRequestIdFromContext } from "./utils.js";
5
5
  /**
6
6
  * Create request logging hooks.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"logging.js","sourceRoot":"","sources":["../../../src/server/hooks/logging.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"logging.js","sourceRoot":"","sources":["../../../src/server/hooks/logging.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Rate limit hooks for @beignet/core/server
3
3
  */
4
- import type { RateLimitScope } from "../../contracts";
5
- import type { ActivityActor, RateLimitPort } from "../../ports";
6
- import type { HttpRequestLike, ServerHook } from "../types";
4
+ import type { RateLimitScope } from "../../contracts/index.js";
5
+ import type { ActivityActor, RateLimitPort } from "../../ports/index.js";
6
+ import type { HttpRequestLike, ServerHook } from "../types.js";
7
7
  /**
8
8
  * Ports required by rate-limit hooks.
9
9
  */
@@ -18,6 +18,19 @@ export type CtxWithRateLimit = {
18
18
  actor?: ActivityActor;
19
19
  };
20
20
  type EarlyRateLimitScope = Exclude<RateLimitScope, "user">;
21
+ /**
22
+ * Strategy for resolving the client IP used by `ip`-scoped limits.
23
+ *
24
+ * - `"x-forwarded-for-last"` (default): the last `x-forwarded-for` entry.
25
+ * This is the address appended by the platform's trusted reverse proxy and
26
+ * cannot be chosen by the client.
27
+ * - `"x-forwarded-for-first"`: the first `x-forwarded-for` entry. This value
28
+ * is client-controlled, so only use it when a trusted edge normalizes the
29
+ * header before it reaches the app.
30
+ * - A function receives the raw request and returns the client IP, for
31
+ * platform-specific headers such as `cf-connecting-ip`.
32
+ */
33
+ export type RateLimitIpSource = "x-forwarded-for-last" | "x-forwarded-for-first" | ((req: HttpRequestLike) => string | undefined);
21
34
  /**
22
35
  * Options for `createRateLimitHooks(...)`.
23
36
  */
@@ -42,9 +55,12 @@ export interface RateLimitOptions<Ctx> {
42
55
  scope: EarlyRateLimitScope;
43
56
  }) => string;
44
57
  /**
45
- * Resolve a client IP from the raw request.
58
+ * Resolve the client IP for `ip`-scoped limits.
59
+ *
60
+ * Defaults to `"x-forwarded-for-last"`, the entry appended by the
61
+ * platform's trusted proxy. Pass a function for platform-specific headers.
46
62
  */
47
- getClientIp?: (req: HttpRequestLike) => string | undefined;
63
+ ipSource?: RateLimitIpSource;
48
64
  }
49
65
  /**
50
66
  * Create metadata-driven rate-limit hooks.
@@ -52,9 +68,11 @@ export interface RateLimitOptions<Ctx> {
52
68
  * The hook reads `contract.metadata.rateLimit`. Global and IP-scoped limits run
53
69
  * in `onRequest` before context creation; user-scoped limits run in
54
70
  * `beforeHandle` after `ctx.actor` is available. Exceeded limits throw the
55
- * framework `TooManyRequests` app error.
71
+ * framework `TooManyRequests` app error with `scope`, `retryAfterSeconds`, and
72
+ * `resetAt` details. The bucket key is never sent to clients; denials emit a
73
+ * `rateLimit.denied` instrumentation event that carries the key for operators.
56
74
  *
57
- * @param options - Optional key builders and client-IP resolver.
75
+ * @param options - Optional key builders and client-IP source.
58
76
  * @returns A server hook backed by `ctx.ports.rateLimit`.
59
77
  */
60
78
  export declare function createRateLimitHooks<Ctx extends CtxWithRateLimit>(options?: RateLimitOptions<Ctx>): ServerHook<Ctx, RateLimitPorts>;
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/server/hooks/rate-limit.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE5D;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,KAAK,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,GAAG;IACnC;;;;OAIG;IACH,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;QACX,GAAG,EAAE,GAAG,CAAC;QACT,GAAG,EAAE,eAAe,CAAC;QACrB,KAAK,EAAE,cAAc,CAAC;KACvB,KAAK,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAChB,GAAG,EAAE,eAAe,CAAC;QACrB,KAAK,EAAE,mBAAmB,CAAC;KAC5B,KAAK,MAAM,CAAC;IACb;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC;CAC5D;AAmFD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,SAAS,gBAAgB,EAC/D,OAAO,GAAE,gBAAgB,CAAC,GAAG,CAAM,GAClC,UAAU,CAAC,GAAG,EAAE,cAAc,CAAC,CAsDjC"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/server/hooks/rate-limit.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAKzE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,KAAK,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAE3D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GACzB,sBAAsB,GACtB,uBAAuB,GACvB,CAAC,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,GAAG;IACnC;;;;OAIG;IACH,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;QACX,GAAG,EAAE,GAAG,CAAC;QACT,GAAG,EAAE,eAAe,CAAC;QACrB,KAAK,EAAE,cAAc,CAAC;KACvB,KAAK,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAChB,GAAG,EAAE,eAAe,CAAC;QACrB,KAAK,EAAE,mBAAmB,CAAC;KAC5B,KAAK,MAAM,CAAC;IACb;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AA4HD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,SAAS,gBAAgB,EAC/D,OAAO,GAAE,gBAAgB,CAAC,GAAG,CAAM,GAClC,UAAU,CAAC,GAAG,EAAE,cAAc,CAAC,CAuDjC"}
@@ -1,10 +1,26 @@
1
1
  /**
2
2
  * Rate limit hooks for @beignet/core/server
3
3
  */
4
- import { AppError, httpErrors } from "../../errors";
5
- function defaultGetClientIp(req) {
6
- const xfwd = req.headers.get("x-forwarded-for") ?? "";
7
- return xfwd.split(",")[0].trim() || undefined;
4
+ import { AppError, httpErrors } from "../../errors/index.js";
5
+ import { createProviderInstrumentation, } from "../../providers/index.js";
6
+ function parseForwardedFor(req) {
7
+ const header = req.headers.get("x-forwarded-for") ?? "";
8
+ return header
9
+ .split(",")
10
+ .map((entry) => entry.trim())
11
+ .filter(Boolean);
12
+ }
13
+ function resolveClientIp(req, ipSource) {
14
+ if (typeof ipSource === "function") {
15
+ return ipSource(req) || undefined;
16
+ }
17
+ const entries = parseForwardedFor(req);
18
+ if (entries.length === 0) {
19
+ return undefined;
20
+ }
21
+ return ipSource === "x-forwarded-for-first"
22
+ ? entries[0]
23
+ : entries[entries.length - 1];
8
24
  }
9
25
  function emitUserKey(userId) {
10
26
  return `user:${userId}`;
@@ -30,8 +46,8 @@ function defaultEarlyRateLimitKey(args, getClientIp) {
30
46
  }
31
47
  return "global";
32
48
  }
33
- async function enforceRateLimit(rateLimit, args) {
34
- const result = await rateLimit.hit({
49
+ async function enforceRateLimit(ports, args) {
50
+ const result = await ports.rateLimit.hit({
35
51
  key: args.key,
36
52
  limit: args.limit,
37
53
  windowSec: args.windowSec,
@@ -39,8 +55,24 @@ async function enforceRateLimit(rateLimit, args) {
39
55
  if (result.allowed) {
40
56
  return;
41
57
  }
58
+ // App ports may carry an `instrumentation` or `devtools` sink alongside the
59
+ // typed rate-limit port; the helper resolves them at runtime.
60
+ const instrumentation = createProviderInstrumentation(ports, {
61
+ providerName: "rate-limit",
62
+ watcher: "rateLimit",
63
+ });
64
+ instrumentation.custom({
65
+ name: "rateLimit.denied",
66
+ label: "Rate limit denied",
67
+ summary: `Rate limit denied for ${args.key}`,
68
+ details: {
69
+ key: args.key,
70
+ scope: args.scope,
71
+ limit: args.limit,
72
+ windowSec: args.windowSec,
73
+ },
74
+ });
42
75
  throw new AppError(httpErrors.TooManyRequests, {
43
- key: args.key,
44
76
  scope: args.scope,
45
77
  retryAfterSeconds: result.retryAfterSeconds,
46
78
  resetAt: result.resetAt?.toISOString() ?? null,
@@ -52,13 +84,16 @@ async function enforceRateLimit(rateLimit, args) {
52
84
  * The hook reads `contract.metadata.rateLimit`. Global and IP-scoped limits run
53
85
  * in `onRequest` before context creation; user-scoped limits run in
54
86
  * `beforeHandle` after `ctx.actor` is available. Exceeded limits throw the
55
- * framework `TooManyRequests` app error.
87
+ * framework `TooManyRequests` app error with `scope`, `retryAfterSeconds`, and
88
+ * `resetAt` details. The bucket key is never sent to clients; denials emit a
89
+ * `rateLimit.denied` instrumentation event that carries the key for operators.
56
90
  *
57
- * @param options - Optional key builders and client-IP resolver.
91
+ * @param options - Optional key builders and client-IP source.
58
92
  * @returns A server hook backed by `ctx.ports.rateLimit`.
59
93
  */
60
94
  export function createRateLimitHooks(options = {}) {
61
- const getClientIp = options.getClientIp ?? defaultGetClientIp;
95
+ const ipSource = options.ipSource ?? "x-forwarded-for-last";
96
+ const getClientIp = (req) => resolveClientIp(req, ipSource);
62
97
  return {
63
98
  name: "rate-limit",
64
99
  onRequest: async ({ contract, ports, req }) => {
@@ -72,7 +107,7 @@ export function createRateLimitHooks(options = {}) {
72
107
  }
73
108
  const key = options.earlyKey?.({ req, scope }) ??
74
109
  defaultEarlyRateLimitKey({ req, scope }, getClientIp);
75
- await enforceRateLimit(ports.rateLimit, {
110
+ await enforceRateLimit(ports, {
76
111
  key,
77
112
  limit: rlMeta.max,
78
113
  windowSec: rlMeta.windowSec,
@@ -91,7 +126,7 @@ export function createRateLimitHooks(options = {}) {
91
126
  }
92
127
  const key = options.key?.({ ctx, req, scope }) ??
93
128
  defaultRateLimitKey({ ctx, req, scope }, getClientIp);
94
- await enforceRateLimit(ctx.ports.rateLimit, {
129
+ await enforceRateLimit(ctx.ports, {
95
130
  key,
96
131
  limit: rlMeta.max,
97
132
  windowSec: rlMeta.windowSec,
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/server/hooks/rate-limit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/server/hooks/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export * from "./hooks/index";
1
+ export * from "./hooks/index.js";
2
2
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/server/hooks.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/server/hooks.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
@@ -1,2 +1,2 @@
1
- export * from "./hooks/index";
1
+ export * from "./hooks/index.js";
2
2
  //# sourceMappingURL=hooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/server/hooks.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/server/hooks.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
@@ -1,5 +1,5 @@
1
- import type { HttpContractConfig, InferHeaderSchemaOutput, InferOutput, StandardSchema } from "../contracts";
2
- import type { AnyPorts } from "../ports";
1
+ import type { HttpContractConfig, InferHeaderSchemaOutput, InferOutput, StandardSchema } from "../contracts/index.js";
2
+ import type { AnyPorts } from "../ports/index.js";
3
3
  /**
4
4
  * Framework-neutral request shape consumed by Beignet server adapters.
5
5
  *
@@ -75,6 +75,41 @@ export interface HttpResponseLike {
75
75
  * `Response` only when the current adapter can pass it through unchanged.
76
76
  */
77
77
  export type HttpResponse = HttpResponseLike | Response;
78
+ /**
79
+ * Framework-neutral Beignet API handler consumed by HTTP adapters.
80
+ */
81
+ export type HttpAdapterApiHandler = (req: HttpRequestLike) => Promise<HttpResponse>;
82
+ /**
83
+ * Native handler shape produced by an HTTP adapter.
84
+ */
85
+ export type HttpAdapterHandler<NativeRequest, NativeResponse> = (req: NativeRequest) => Promise<NativeResponse>;
86
+ /**
87
+ * Contract implemented by packages that adapt Beignet's framework-neutral
88
+ * server runtime to a platform HTTP API.
89
+ *
90
+ * Core owns request parsing, hooks, route matching, validation, error mapping,
91
+ * response ownership, and provider lifecycle. Adapters own only the conversion
92
+ * between the platform request/response types and Beignet's `HttpRequestLike`
93
+ * / `HttpResponse` boundary.
94
+ */
95
+ export interface HttpAdapter<NativeRequest, NativeResponse> {
96
+ /**
97
+ * Human-readable adapter name for diagnostics and documentation.
98
+ */
99
+ name: string;
100
+ /**
101
+ * Convert a platform request into Beignet's framework-neutral request shape.
102
+ */
103
+ toRequestLike(req: NativeRequest): HttpRequestLike;
104
+ /**
105
+ * Convert a Beignet response into the platform response type.
106
+ */
107
+ toNativeResponse(res: HttpResponse): NativeResponse | Promise<NativeResponse>;
108
+ /**
109
+ * Wrap a Beignet API handler in the platform's native handler shape.
110
+ */
111
+ createHandler(handler: HttpAdapterApiHandler): HttpAdapterHandler<NativeRequest, NativeResponse>;
112
+ }
78
113
  type InferSchemaOrFallback<T extends StandardSchema | null, Fallback> = T extends StandardSchema ? InferOutput<T> : Fallback;
79
114
  /**
80
115
  * Infer the handler path parameter type for a contract.
@@ -101,7 +136,7 @@ export interface HandlerArgs<Ctx, C extends HttpContractConfig> {
101
136
  */
102
137
  req: HttpRequestLike;
103
138
  /**
104
- * Application context returned by `createContext`.
139
+ * Application context assembled by the server context blueprint.
105
140
  */
106
141
  ctx: Ctx;
107
142
  /**
@@ -133,6 +168,40 @@ export type Handler<Ctx, C extends HttpContractConfig> = (args: HandlerArgs<Ctx,
133
168
  * Value or promise of that value.
134
169
  */
135
170
  export type MaybePromise<T> = T | Promise<T>;
171
+ /**
172
+ * Arguments passed to a route-scoped hook after request parsing and context
173
+ * creation.
174
+ */
175
+ export type RouteHookArgs<Ctx, C extends HttpContractConfig = HttpContractConfig> = HandlerArgs<Ctx, C>;
176
+ /**
177
+ * Hook that runs only for the route or route group where it is attached.
178
+ *
179
+ * Route hooks are for scoped policy and context enrichment such as
180
+ * authentication, tenant resolution, feature gates, and idempotency. They add
181
+ * fields to the handler context instead of replacing the app context.
182
+ *
183
+ * Hook additions must not include `gate`: the server re-attaches the gate
184
+ * declared by the context blueprint after every hook, so identity changes are
185
+ * picked up automatically.
186
+ */
187
+ export interface RouteHook<Ctx, AddedCtx extends object & {
188
+ gate?: never;
189
+ } = Record<string, never>> {
190
+ /**
191
+ * Optional name used in diagnostics and devtools.
192
+ */
193
+ name?: string;
194
+ /**
195
+ * Resolve additional context for this route or throw to stop handling.
196
+ */
197
+ resolve: (args: RouteHookArgs<Ctx>) => MaybePromise<AddedCtx | undefined>;
198
+ }
199
+ type AddedCtxFromHook<Hook> = Hook extends RouteHook<infer _Ctx, infer AddedCtx> ? AddedCtx : unknown;
200
+ type UnionToIntersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Intersection) => void ? Intersection : never;
201
+ /**
202
+ * Intersection of the context fields added by a route hook list.
203
+ */
204
+ export type AddedCtxFromHooks<Hooks extends readonly unknown[]> = Hooks extends readonly [] ? unknown : UnionToIntersection<AddedCtxFromHook<Hooks[number]>>;
136
205
  /**
137
206
  * Hook that runs after a route is matched but before request parsing and
138
207
  * context creation.
@@ -168,10 +237,11 @@ export type BeforeHandleHook<Ctx, C extends HttpContractConfig = HttpContractCon
168
237
  body: InferBody<C>;
169
238
  }) => MaybePromise<BeforeHandleResult<Ctx>>;
170
239
  /**
171
- * Hook that runs before a framework-neutral response is returned.
240
+ * Hook that runs before the response is returned.
172
241
  *
173
242
  * Return a response to replace or decorate the outgoing response. Hooks run in
174
- * declaration order.
243
+ * declaration order. For native web `Response` results the hook receives a
244
+ * headers-only view and only header changes are applied.
175
245
  */
176
246
  export type BeforeSendHook<Ctx, C extends HttpContractConfig = HttpContractConfig> = (args: {
177
247
  req: HttpRequestLike;
@@ -183,6 +253,13 @@ export type BeforeSendHook<Ctx, C extends HttpContractConfig = HttpContractConfi
183
253
  body?: InferBody<C>;
184
254
  response: HttpResponseLike;
185
255
  error?: unknown;
256
+ /**
257
+ * True when the route returned a native web Response. The response argument
258
+ * is a headers-only view ({ status, headers }); the body is not readable and
259
+ * returned body/status changes are ignored. Header changes are merged onto
260
+ * the native Response.
261
+ */
262
+ native?: boolean;
186
263
  }) => MaybePromise<HttpResponseLike | undefined>;
187
264
  /**
188
265
  * Hook that runs after the response has been prepared.
@@ -253,7 +330,8 @@ export interface ServerHook<Ctx, Ports extends AnyPorts = AnyPorts> {
253
330
  */
254
331
  beforeHandle?: BeforeHandleHook<Ctx>;
255
332
  /**
256
- * Runs before a framework-neutral response is returned.
333
+ * Runs before the response is returned. Native web `Response` results get a
334
+ * headers-only view with `native: true`.
257
335
  */
258
336
  beforeSend?: BeforeSendHook<Ctx>;
259
337
  /**