@classytic/arc 2.41.1 → 2.44.0

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 (248) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-DSqD_Xsg.mjs → EventTransport-DZWDsaPa.mjs} +22 -17
  3. package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
  6. package/dist/acquire-BozJ-9yt.mjs +35 -0
  7. package/dist/actionPermissions-5N04zBL4.mjs +772 -0
  8. package/dist/audit/index.d.mts +46 -8
  9. package/dist/audit/index.mjs +48 -26
  10. package/dist/auth/audit.d.mts +8 -8
  11. package/dist/auth/audit.mjs +1 -1
  12. package/dist/auth/index.d.mts +46 -16
  13. package/dist/auth/index.mjs +134 -156
  14. package/dist/auth/redis-session.d.mts +4 -5
  15. package/dist/auth/redis-session.mjs +1 -1
  16. package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
  17. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  18. package/dist/boundedMap-DWiKW7lP.mjs +57 -0
  19. package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
  20. package/dist/cache/index.d.mts +31 -43
  21. package/dist/cache/index.mjs +4 -4
  22. package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
  23. package/dist/claimed-DYvQoZLv.mjs +8 -0
  24. package/dist/cleanup/index.d.mts +18 -22
  25. package/dist/cleanup/index.mjs +27 -73
  26. package/dist/cli/commands/describe.d.mts +7 -5
  27. package/dist/cli/commands/describe.mjs +7 -4
  28. package/dist/cli/commands/docs.d.mts +2 -3
  29. package/dist/cli/commands/docs.mjs +3 -3
  30. package/dist/cli/commands/doctor.d.mts +2 -3
  31. package/dist/cli/commands/doctor.mjs +1 -1
  32. package/dist/cli/commands/generate.mjs +2 -3
  33. package/dist/cli/commands/init.mjs +2 -3
  34. package/dist/cli/commands/introspect.d.mts +2 -3
  35. package/dist/cli/commands/introspect.mjs +2 -2
  36. package/dist/cli/index.mjs +3 -3
  37. package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
  38. package/dist/context/index.d.mts +11 -14
  39. package/dist/context/index.mjs +18 -38
  40. package/dist/core/index.d.mts +3 -3
  41. package/dist/core/index.mjs +8 -8
  42. package/dist/{core-5lazhva9.mjs → core-COcqo0Cp.mjs} +15 -5
  43. package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
  44. package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
  45. package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
  46. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
  47. package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
  48. package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
  49. package/dist/discovery/index.d.mts +6 -7
  50. package/dist/discovery/index.mjs +1 -1
  51. package/dist/docs/index.d.mts +11 -5
  52. package/dist/docs/index.mjs +4 -4
  53. package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
  54. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  55. package/dist/encryption/index.d.mts +5 -5
  56. package/dist/encryption/index.mjs +3 -3
  57. package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
  58. package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.mjs} +58 -23
  59. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  60. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  61. package/dist/{eventPlugin-zZY-unhr.mjs → eventPlugin-BweHAVws.mjs} +82 -21
  62. package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
  63. package/dist/events/index.d.mts +43 -18
  64. package/dist/events/index.mjs +49 -53
  65. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  66. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  67. package/dist/events/transports/redis.d.mts +5 -6
  68. package/dist/events/transports/redis.mjs +2 -2
  69. package/dist/factory/index.d.mts +42 -27
  70. package/dist/factory/index.mjs +213 -32
  71. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
  72. package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
  73. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  74. package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
  75. package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
  76. package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
  77. package/dist/health-BYAwA6YS.mjs +22 -0
  78. package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
  79. package/dist/hooks/index.d.mts +2 -2
  80. package/dist/hooks/index.mjs +2 -3
  81. package/dist/idempotency/index.d.mts +36 -10
  82. package/dist/idempotency/index.mjs +230 -111
  83. package/dist/idempotency/redis.d.mts +1 -1
  84. package/dist/idempotency/redis.mjs +77 -12
  85. package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
  86. package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
  87. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  88. package/dist/index-CUJvoyw3.d.mts +510 -0
  89. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  90. package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
  91. package/dist/index.d.mts +9 -9
  92. package/dist/index.mjs +11 -11
  93. package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
  94. package/dist/integrations/event-gateway.d.mts +11 -7
  95. package/dist/integrations/event-gateway.mjs +4 -4
  96. package/dist/integrations/index.d.mts +3 -3
  97. package/dist/integrations/index.mjs +1 -1
  98. package/dist/integrations/jobs/index.d.mts +1 -1
  99. package/dist/integrations/jobs/index.mjs +18 -5
  100. package/dist/integrations/mcp/index.d.mts +102 -89
  101. package/dist/integrations/mcp/index.mjs +19 -4
  102. package/dist/integrations/mcp/testing.d.mts +8 -9
  103. package/dist/integrations/mcp/testing.mjs +2 -2
  104. package/dist/integrations/streamline.d.mts +85 -144
  105. package/dist/integrations/streamline.mjs +131 -55
  106. package/dist/integrations/webhooks.d.mts +83 -143
  107. package/dist/integrations/webhooks.mjs +36 -80
  108. package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
  109. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  110. package/dist/integrations/websocket-redis.d.mts +5 -6
  111. package/dist/integrations/websocket-redis.mjs +1 -1
  112. package/dist/integrations/websocket.d.mts +3 -3
  113. package/dist/integrations/websocket.mjs +2 -2
  114. package/dist/{interface-DEWNOMza.d.mts → interface-JJzwH4Ro.d.mts} +34 -2
  115. package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
  116. package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
  117. package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
  118. package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
  119. package/dist/logger/index.d.mts +9 -17
  120. package/dist/logger/index.mjs +2 -153
  121. package/dist/logger-D4V1zGLI.mjs +160 -0
  122. package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
  123. package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
  124. package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
  125. package/dist/middleware/index.d.mts +5 -5
  126. package/dist/middleware/index.mjs +3 -3
  127. package/dist/migrations/index.d.mts +13 -13
  128. package/dist/migrations/index.mjs +2 -2
  129. package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
  130. package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
  131. package/dist/mongoose-CYJCLyi-.mjs +67451 -0
  132. package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
  133. package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
  134. package/dist/node-D8UZv_8c.mjs +41236 -0
  135. package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
  136. package/dist/outbox-admin/index.d.mts +5 -5
  137. package/dist/outbox-admin/index.mjs +6 -18
  138. package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.mjs} +203 -33
  139. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  140. package/dist/permissions/index.d.mts +5 -4
  141. package/dist/permissions/index.mjs +8 -6
  142. package/dist/{permissions-D8TBH44E.mjs → permissions-C5ijIz5Q.mjs} +149 -33
  143. package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
  144. package/dist/pipeline/index.d.mts +7 -7
  145. package/dist/pipeline/index.mjs +2 -2
  146. package/dist/plan-CJGMKoui.mjs +565 -0
  147. package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
  148. package/dist/plugins/index.d.mts +17 -14
  149. package/dist/plugins/index.mjs +44 -22
  150. package/dist/plugins/response-cache.d.mts +5 -6
  151. package/dist/plugins/response-cache.mjs +8 -14
  152. package/dist/plugins/tracing-entry.mjs +2 -2
  153. package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
  154. package/dist/presets/filesUpload.d.mts +8 -8
  155. package/dist/presets/filesUpload.mjs +5 -5
  156. package/dist/presets/index.d.mts +35 -40
  157. package/dist/presets/index.mjs +4 -4
  158. package/dist/presets/multiTenant.d.mts +2 -107
  159. package/dist/presets/multiTenant.mjs +22 -162
  160. package/dist/presets/search.d.mts +9 -10
  161. package/dist/presets/search.mjs +2 -2
  162. package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
  163. package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
  164. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  165. package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
  166. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  167. package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
  168. package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
  169. package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
  170. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  171. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  172. package/dist/registry/index.d.mts +15 -10
  173. package/dist/registry/index.mjs +4 -4
  174. package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
  175. package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
  176. package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
  177. package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
  178. package/dist/requirement-Bu50QALH.mjs +35 -0
  179. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  180. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  181. package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
  182. package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
  183. package/dist/routeValidator-DHtpzrzk.mjs +70 -0
  184. package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
  185. package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
  186. package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
  187. package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
  188. package/dist/schemas/index.d.mts +20 -10
  189. package/dist/schemas/index.mjs +8 -3
  190. package/dist/scim/index.d.mts +13 -13
  191. package/dist/scim/index.mjs +2 -2
  192. package/dist/scope/index.d.mts +4 -4
  193. package/dist/scope/index.mjs +6 -6
  194. package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
  195. package/dist/shutdown-LpLMH8T1.mjs +59 -0
  196. package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
  197. package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
  198. package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
  199. package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
  200. package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
  201. package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
  202. package/dist/tenancy-DxV8OuX2.mjs +43 -0
  203. package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
  204. package/dist/testing/cleanupStoreContract.d.mts +4 -5
  205. package/dist/testing/cleanupStoreContract.mjs +1 -1
  206. package/dist/testing/index.d.mts +215 -197
  207. package/dist/testing/index.mjs +289 -214
  208. package/dist/testing/outboxStoreContract.d.mts +4 -5
  209. package/dist/testing/outboxStoreContract.mjs +137 -2
  210. package/dist/testing/requiredPorts.d.mts +16 -0
  211. package/dist/testing/requiredPorts.mjs +65 -0
  212. package/dist/testing/storageContract.d.mts +4 -5
  213. package/dist/testing/storageContract.mjs +1 -1
  214. package/dist/timing-CntavTlR.mjs +46 -0
  215. package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
  216. package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.mjs} +1 -1
  217. package/dist/types/index.d.mts +4 -4
  218. package/dist/types/index.mjs +1 -1
  219. package/dist/types/storage.mjs +1 -1
  220. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  221. package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
  222. package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
  223. package/dist/usage/index.d.mts +5 -6
  224. package/dist/usage/index.mjs +5 -5
  225. package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
  226. package/dist/utils/index.d.mts +125 -264
  227. package/dist/utils/index.mjs +13 -12
  228. package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
  229. package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
  230. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  231. package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
  232. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  233. package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
  234. package/package.json +57 -59
  235. package/skills/arc/SKILL.md +13 -4
  236. package/skills/arc/references/auth.md +8 -5
  237. package/skills/arc/references/integrations.md +1 -1
  238. package/skills/arc/references/mcp.md +8 -6
  239. package/skills/arc/references/multi-tenancy.md +19 -14
  240. package/skills/arc/references/production.md +27 -10
  241. package/dist/actionPermissions-BoPGQ5-4.mjs +0 -350
  242. package/dist/index-CFfLKMH4.d.mts +0 -290
  243. package/dist/interface-YzIbF7Ii.mjs +0 -17
  244. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  245. package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
  246. package/dist/rolldown-runtime-eysyzFAF.mjs +0 -15
  247. package/dist/sync/changelog.d.mts +0 -3
  248. package/dist/sync/changelog.mjs +0 -3
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  import { randomUUID } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/requestId.ts
@@ -111,6 +111,115 @@ var requestId_default = fp(requestIdPlugin, {
111
111
  fastify: "5.x"
112
112
  });
113
113
  //#endregion
114
+ //#region src/utils/metricSeries.ts
115
+ /** Retains a fixed number of label sets; excess observations share one overflow series. */
116
+ var MetricSeries = class {
117
+ data = /* @__PURE__ */ new Map();
118
+ overflow;
119
+ limit;
120
+ constructor(limit = 1e3) {
121
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("metrics.maxSeries must be a positive safe integer");
122
+ this.limit = limit;
123
+ }
124
+ get(labels, init) {
125
+ const key = JSON.stringify(Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)));
126
+ const existing = this.data.get(key);
127
+ if (existing) return existing.value;
128
+ if (this.data.size >= this.limit) {
129
+ this.overflow ??= {
130
+ labels: { overflow: "true" },
131
+ value: init()
132
+ };
133
+ return this.overflow.value;
134
+ }
135
+ const entry = {
136
+ labels: { ...labels },
137
+ value: init()
138
+ };
139
+ this.data.set(key, entry);
140
+ return entry.value;
141
+ }
142
+ entries() {
143
+ return [...this.data.values(), ...this.overflow ? [this.overflow] : []];
144
+ }
145
+ reset() {
146
+ this.data.clear();
147
+ this.overflow = void 0;
148
+ }
149
+ };
150
+ const DURATION_BUCKETS_SECONDS = [
151
+ .001,
152
+ .002,
153
+ .005,
154
+ .01,
155
+ .025,
156
+ .05,
157
+ .1,
158
+ .25,
159
+ .5,
160
+ 1,
161
+ 2.5,
162
+ 5,
163
+ 10
164
+ ];
165
+ /** Stores disjoint bucket counts; exported buckets are cumulative and include +Inf. */
166
+ var MetricHistogram = class {
167
+ series;
168
+ bounds;
169
+ constructor(bounds, maxSeries = 1e3) {
170
+ this.bounds = bounds;
171
+ this.series = new MetricSeries(maxSeries);
172
+ }
173
+ observe(labels, value) {
174
+ if (!Number.isFinite(value) || value < 0) return;
175
+ const state = this.series.get(labels, () => ({
176
+ buckets: new Array(this.bounds.length + 1).fill(0),
177
+ sum: 0,
178
+ count: 0
179
+ }));
180
+ const index = this.bounds.findIndex((bound) => value <= bound);
181
+ const bucket = index < 0 ? this.bounds.length : index;
182
+ state.buckets[bucket] = (state.buckets[bucket] ?? 0) + 1;
183
+ state.sum += value;
184
+ state.count++;
185
+ }
186
+ values() {
187
+ return this.series.entries().flatMap(({ labels, value }) => {
188
+ let cumulative = 0;
189
+ return [
190
+ ...value.buckets.map((count, index) => {
191
+ cumulative += count;
192
+ return {
193
+ labels: {
194
+ ...labels,
195
+ le: this.bounds[index]?.toString() ?? "+Inf"
196
+ },
197
+ value: cumulative,
198
+ suffix: "_bucket"
199
+ };
200
+ }),
201
+ {
202
+ labels: { ...labels },
203
+ value: value.sum,
204
+ suffix: "_sum"
205
+ },
206
+ {
207
+ labels: { ...labels },
208
+ value: value.count,
209
+ suffix: "_count"
210
+ }
211
+ ];
212
+ });
213
+ }
214
+ reset() {
215
+ this.series.reset();
216
+ }
217
+ };
218
+ function renderMetricSample(name, sample) {
219
+ const labels = Object.entries(sample.labels).map(([key, value]) => `${key}="${value.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll("\"", "\\\"")}"`).join(",");
220
+ return `${name}${sample.suffix ?? ""}${labels ? `{${labels}}` : ""} ${sample.value}`;
221
+ }
222
+ //#endregion
114
223
  //#region src/plugins/health.ts
115
224
  /**
116
225
  * Merge health checks without losing ownership information.
@@ -130,47 +239,12 @@ function mergeHealthChecks(groups) {
130
239
  }
131
240
  return merged;
132
241
  }
133
- /**
134
- * Fixed histogram bucket upper bounds (ms). Recording is O(#buckets) per
135
- * request; the scrape derives quantile UPPER BOUNDS from cumulative counts
136
- * in O(#buckets) — no sample retention, no per-scrape sort (the previous
137
- * 10k-sample ring buffer copied + sorted on EVERY scrape), and bucket
138
- * counts aggregate correctly across instances.
139
- */
140
- const DURATION_BUCKETS_MS = [
141
- 1,
142
- 2,
143
- 5,
144
- 10,
145
- 25,
146
- 50,
147
- 100,
148
- 250,
149
- 500,
150
- 1e3,
151
- 2500,
152
- 5e3,
153
- 1e4
154
- ];
155
242
  function createHttpMetrics() {
156
243
  return {
157
244
  requestsTotal: {},
158
- durationBuckets: new Array(DURATION_BUCKETS_MS.length + 1).fill(0),
159
- durationCount: 0,
160
- durationSumMs: 0,
161
- startTime: Date.now()
245
+ duration: new MetricHistogram(DURATION_BUCKETS_SECONDS.map((seconds) => seconds * 1e3))
162
246
  };
163
247
  }
164
- /** Smallest bucket upper bound whose cumulative count reaches `q × total`. */
165
- function bucketQuantile(metrics, q) {
166
- const target = Math.ceil(metrics.durationCount * q);
167
- let cumulative = 0;
168
- for (let i = 0; i < metrics.durationBuckets.length; i++) {
169
- cumulative += metrics.durationBuckets[i] ?? 0;
170
- if (cumulative >= target) return DURATION_BUCKETS_MS[i] ?? DURATION_BUCKETS_MS[DURATION_BUCKETS_MS.length - 1] ?? 0;
171
- }
172
- return DURATION_BUCKETS_MS[DURATION_BUCKETS_MS.length - 1] ?? 0;
173
- }
174
248
  const healthPlugin = async (fastify, opts = {}) => {
175
249
  const { prefix = "/_health", checks = [], metrics = false, metricsCollector, version, collectHttpMetrics = metrics } = opts;
176
250
  const readinessChecks = mergeHealthChecks([{
@@ -315,12 +389,8 @@ const healthPlugin = async (fastify, opts = {}) => {
315
389
  lines.push("# HELP http_requests_total Total HTTP requests by status code", "# TYPE http_requests_total counter");
316
390
  for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) lines.push(`http_requests_total{status="${status}"} ${count}`);
317
391
  lines.push("");
318
- if (httpMetrics.durationCount > 0) {
319
- const p50 = bucketQuantile(httpMetrics, .5);
320
- const p95 = bucketQuantile(httpMetrics, .95);
321
- const p99 = bucketQuantile(httpMetrics, .99);
322
- lines.push("# HELP http_request_duration_milliseconds HTTP request duration (bucket upper bounds)", "# TYPE http_request_duration_milliseconds summary", `http_request_duration_milliseconds{quantile="0.5"} ${p50.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.95"} ${p95.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.99"} ${p99.toFixed(2)}`, `http_request_duration_milliseconds_sum ${httpMetrics.durationSumMs.toFixed(2)}`, `http_request_duration_milliseconds_count ${httpMetrics.durationCount}`, "");
323
- }
392
+ const samples = httpMetrics.duration.values();
393
+ if (samples.length > 0) lines.push("# HELP http_request_duration_milliseconds HTTP request duration in milliseconds", "# TYPE http_request_duration_milliseconds histogram", ...samples.map((sample) => renderMetricSample("http_request_duration_milliseconds", sample)), "");
324
394
  }
325
395
  return lines.join("\n");
326
396
  });
@@ -328,17 +398,7 @@ const healthPlugin = async (fastify, opts = {}) => {
328
398
  const duration = reply.elapsedTime;
329
399
  const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
330
400
  httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
331
- let bucket = DURATION_BUCKETS_MS.length;
332
- for (let i = 0; i < DURATION_BUCKETS_MS.length; i++) {
333
- const bound = DURATION_BUCKETS_MS[i];
334
- if (bound !== void 0 && duration <= bound) {
335
- bucket = i;
336
- break;
337
- }
338
- }
339
- httpMetrics.durationBuckets[bucket] = (httpMetrics.durationBuckets[bucket] ?? 0) + 1;
340
- httpMetrics.durationCount++;
341
- httpMetrics.durationSumMs += duration;
401
+ httpMetrics.duration.observe({}, duration);
342
402
  });
343
403
  fastify.log?.debug?.(`Health plugin registered at ${prefix} (alias), ${prefix}/live, ${prefix}/ready${metrics ? `, ${prefix}/metrics` : ""}`);
344
404
  };
@@ -383,4 +443,4 @@ var health_default = fp(healthPlugin, {
383
443
  fastify: "5.x"
384
444
  });
385
445
  //#endregion
386
- export { requestIdPlugin as a, createRequestIdGenerator as i, health_default as n, requestId_default as o, mergeHealthChecks as r, sanitizeRequestId as s, healthPlugin as t };
446
+ export { MetricHistogram as a, createRequestIdGenerator as c, DURATION_BUCKETS_SECONDS as i, requestId_default as l, health_default as n, MetricSeries as o, mergeHealthChecks as r, renderMetricSample as s, healthPlugin as t, sanitizeRequestId as u };
@@ -1,3 +1,3 @@
1
1
 
2
- import { an as HookRegistration, cn as afterCreate, dn as beforeCreate, en as DefineHookOptions, fn as beforeDelete, hn as defineHook, in as HookPhase, ln as afterDelete, mn as createHookSystem, nn as HookHandler, on as HookSystem, pn as beforeUpdate, rn as HookOperation, sn as HookSystemOptions, tn as HookContext, un as afterUpdate } from "../index-DHbyPo5j.mjs";
3
- export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
2
+ import { _n as beforeDelete, an as DefineHookOptions, bn as defineHook, cn as HookOperation, dn as HookSystem, fn as HookSystemOptions, gn as beforeCreate, hn as afterUpdate, in as AroundHookHandler, ln as HookPhase, mn as afterDelete, on as HookContext, pn as afterCreate, sn as HookHandler, un as HookRegistration, vn as beforeUpdate, yn as createHookSystem } from "../index-WH7xgvxS.mjs";
3
+ export { type AroundHookHandler, type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,3 +1,2 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-CMF94SEA.mjs";
3
- export { HookSystem, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ export { t as HookSystem, n as afterCreate, r as afterDelete, i as afterUpdate, a as beforeCreate, o as beforeDelete, s as beforeUpdate, c as createHookSystem, l as defineHook } from "../HookSystem-yjEgn7Cm.mjs";
@@ -1,6 +1,7 @@
1
1
 
2
- import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-DEWNOMza.mjs";
3
- import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-Bzi1y3F9.mjs";
2
+ import { t as RouteIdempotencyConfig } from "../requirement-CbiLdQ1n.mjs";
3
+ import { a as createIdempotencyResult, i as IdempotencyStore, n as IdempotencyLock, r as IdempotencyResult, t as IdempotencyAcquisition } from "../interface-JJzwH4Ro.mjs";
4
+ import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-BDgv9Glw.mjs";
4
5
  import { FastifyPluginAsync } from "fastify";
5
6
  import { RepositoryLike } from "@classytic/repo-core/adapter";
6
7
  //#region src/idempotency/idempotencyPlugin.d.ts
@@ -13,7 +14,11 @@ interface IdempotencyPluginOptions {
13
14
  ttlMs?: number;
14
15
  /** Lock timeout in ms (default: 30000 = 30s) */
15
16
  lockTimeoutMs?: number;
16
- /** HTTP methods to apply idempotency to (default: ['POST', 'PUT', 'PATCH']) */
17
+ /**
18
+ * HTTP methods a key is honoured on when the route does not declare `config.idempotency`
19
+ * (default: ['POST', 'PUT', 'PATCH']). A route declaring `required: true` is fenced whatever
20
+ * its method — DELETE included — and whatever `include` / `exclude` say.
21
+ */
17
22
  methods?: string[];
18
23
  /** URL patterns to include (regex). If set, only matching URLs use idempotency */
19
24
  include?: RegExp[];
@@ -63,6 +68,8 @@ interface IdempotencyPluginOptions {
63
68
  * remain as the runtime backstop.
64
69
  */
65
70
  selfCheck?: boolean;
71
+ /** Migration escape for legacy custom stores without atomic completion fencing; warns at boot. */
72
+ allowUnfencedCompletion?: boolean;
66
73
  }
67
74
  declare module "fastify" {
68
75
  interface FastifyRequest {
@@ -70,8 +77,20 @@ declare module "fastify" {
70
77
  idempotencyKey?: string;
71
78
  /** Whether this response was replayed from cache */
72
79
  idempotencyReplayed?: boolean;
73
- /** @internal Full key with fingerprint for store lookups */
80
+ /** @internal The operation's storage key */
74
81
  _idempotencyFullKey?: string;
82
+ /** @internal `fingerprintRequest` of this request's body, stored with its response */
83
+ _idempotencyFingerprint?: string;
84
+ }
85
+ interface FastifyContextConfig {
86
+ /**
87
+ * Per-route idempotency. `required: true` (or, on an action route, `requiredActions`
88
+ * naming the `body.action` values) refuses a call with no idempotency key (400
89
+ * `arc.idempotency_key_required`) and dedupes a keyed one, on any method. Boot refuses the
90
+ * declaration when nothing would enforce it: the plugin disabled, or the route's
91
+ * `preHandler` lacking `fastify.idempotency.middleware`.
92
+ */
93
+ idempotency?: RouteIdempotencyConfig;
75
94
  }
76
95
  interface FastifyInstance {
77
96
  /** Idempotency utilities */
@@ -93,7 +112,7 @@ declare module "fastify" {
93
112
  * }, handler);
94
113
  * ```
95
114
  */
96
- middleware: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
115
+ middleware: (request: FastifyRequest, reply: FastifyReply) => Promise<undefined | FastifyReply>;
97
116
  /**
98
117
  * The resolved backing store. Exposed so non-HTTP executors (MCP
99
118
  * tools via `mcpPlugin`'s execution wiring, jobs, workflows) can run
@@ -101,10 +120,13 @@ declare module "fastify" {
101
120
  * backend. `undefined` when the plugin is disabled.
102
121
  */
103
122
  store?: IdempotencyStore;
123
+ /** Explicit migration escape shared with non-HTTP executors. */
124
+ readonly allowUnfencedCompletion?: boolean;
125
+ readonly ttlMs?: number;
126
+ readonly lockTimeoutMs?: number;
104
127
  };
105
128
  }
106
129
  }
107
- declare const idempotencyPlugin: FastifyPluginAsync<IdempotencyPluginOptions>;
108
130
  declare const _default: FastifyPluginAsync<IdempotencyPluginOptions>;
109
131
  //#endregion
110
132
  //#region src/idempotency/repository-idempotency-adapter.d.ts
@@ -132,11 +154,11 @@ declare const _default: FastifyPluginAsync<IdempotencyPluginOptions>;
132
154
  * filters on it are never stripped — see `idempotencyPlugin`'s
133
155
  * `repository` option docs.
134
156
  */
135
- declare class IdempotencyStoreMisconfiguredError extends Error {
157
+ export declare class IdempotencyStoreMisconfiguredError extends Error {
136
158
  readonly code = "IDEMPOTENCY_STORE_MISCONFIGURED";
137
159
  constructor(operation: string, expectedKey: string, actualKey: unknown);
138
160
  }
139
- declare function repositoryAsIdempotencyStore(repository: RepositoryLike, defaultTtlMs: number): IdempotencyStore;
161
+ export declare function repositoryAsIdempotencyStore(repository: RepositoryLike, defaultTtlMs: number): IdempotencyStore;
140
162
  //#endregion
141
163
  //#region src/idempotency/stores/memory.d.ts
142
164
  interface MemoryIdempotencyStoreOptions {
@@ -147,8 +169,9 @@ interface MemoryIdempotencyStoreOptions {
147
169
  /** Maximum entries before oldest are evicted (default: 10000) */
148
170
  maxEntries?: number;
149
171
  }
150
- declare class MemoryIdempotencyStore implements IdempotencyStore {
172
+ export declare class MemoryIdempotencyStore implements IdempotencyStore {
151
173
  readonly name = "memory";
174
+ readonly completionReleasesLock = true;
152
175
  private results;
153
176
  private locks;
154
177
  private ttlMs;
@@ -157,6 +180,9 @@ declare class MemoryIdempotencyStore implements IdempotencyStore {
157
180
  constructor(options?: MemoryIdempotencyStoreOptions);
158
181
  get(key: string): Promise<IdempotencyResult | undefined>;
159
182
  set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
183
+ private writeResult;
184
+ setIfLocked(key: string, requestId: string, result: Omit<IdempotencyResult, "key">): Promise<boolean>;
185
+ acquire(key: string, requestId: string, lockTtlMs: number): Promise<IdempotencyAcquisition>;
160
186
  tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
161
187
  unlock(key: string, requestId: string): Promise<void>;
162
188
  isLocked(key: string): Promise<boolean>;
@@ -173,4 +199,4 @@ declare class MemoryIdempotencyStore implements IdempotencyStore {
173
199
  private evictOldest;
174
200
  }
175
201
  //#endregion
176
- export { type IdempotencyLock, type IdempotencyPluginOptions, type IdempotencyResult, type IdempotencyStore, IdempotencyStoreMisconfiguredError, MemoryIdempotencyStore, type MemoryIdempotencyStoreOptions, type RedisClient, type RedisIdempotencyStoreOptions, createIdempotencyResult, _default as idempotencyPlugin, idempotencyPlugin as idempotencyPluginFn, repositoryAsIdempotencyStore };
202
+ export { type IdempotencyAcquisition, type IdempotencyLock, type IdempotencyPluginOptions, type IdempotencyResult, type IdempotencyStore, type MemoryIdempotencyStoreOptions, type RedisClient, type RedisIdempotencyStoreOptions, createIdempotencyResult, _default as idempotencyPlugin };