@classytic/arc 1.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
  4. package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
  5. package/dist/HookSystem-BsGV-j2l.mjs +405 -0
  6. package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
  7. package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
  8. package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
  9. package/dist/adapters/index.d.mts +5 -0
  10. package/dist/adapters/index.mjs +3 -0
  11. package/dist/audit/index.d.mts +82 -0
  12. package/dist/audit/index.d.mts.map +1 -0
  13. package/dist/audit/index.mjs +276 -0
  14. package/dist/audit/index.mjs.map +1 -0
  15. package/dist/audit/mongodb.d.mts +5 -0
  16. package/dist/audit/mongodb.mjs +3 -0
  17. package/dist/audited-C3T5DTUx.mjs +141 -0
  18. package/dist/audited-C3T5DTUx.mjs.map +1 -0
  19. package/dist/auth/index.d.mts +189 -0
  20. package/dist/auth/index.d.mts.map +1 -0
  21. package/dist/auth/index.mjs +1102 -0
  22. package/dist/auth/index.mjs.map +1 -0
  23. package/dist/auth/redis-session.d.mts +44 -0
  24. package/dist/auth/redis-session.d.mts.map +1 -0
  25. package/dist/auth/redis-session.mjs +76 -0
  26. package/dist/auth/redis-session.mjs.map +1 -0
  27. package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
  28. package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
  29. package/dist/cache/index.d.mts +146 -0
  30. package/dist/cache/index.d.mts.map +1 -0
  31. package/dist/cache/index.mjs +92 -0
  32. package/dist/cache/index.mjs.map +1 -0
  33. package/dist/caching-Bl28lYsR.mjs +94 -0
  34. package/dist/caching-Bl28lYsR.mjs.map +1 -0
  35. package/dist/chunk-C7Uep-_p.mjs +20 -0
  36. package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
  37. package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
  38. package/dist/cli/commands/describe.d.mts +19 -0
  39. package/dist/cli/commands/describe.d.mts.map +1 -0
  40. package/dist/cli/commands/describe.mjs +239 -0
  41. package/dist/cli/commands/describe.mjs.map +1 -0
  42. package/dist/cli/commands/docs.d.mts +14 -0
  43. package/dist/cli/commands/docs.d.mts.map +1 -0
  44. package/dist/cli/commands/docs.mjs +53 -0
  45. package/dist/cli/commands/docs.mjs.map +1 -0
  46. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
  47. package/dist/cli/commands/generate.d.mts.map +1 -0
  48. package/dist/cli/commands/generate.mjs +358 -0
  49. package/dist/cli/commands/generate.mjs.map +1 -0
  50. package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
  51. package/dist/cli/commands/init.d.mts.map +1 -0
  52. package/dist/cli/commands/{init.js → init.mjs} +807 -616
  53. package/dist/cli/commands/init.mjs.map +1 -0
  54. package/dist/cli/commands/introspect.d.mts +11 -0
  55. package/dist/cli/commands/introspect.d.mts.map +1 -0
  56. package/dist/cli/commands/introspect.mjs +76 -0
  57. package/dist/cli/commands/introspect.mjs.map +1 -0
  58. package/dist/cli/index.d.mts +17 -0
  59. package/dist/cli/index.d.mts.map +1 -0
  60. package/dist/cli/index.mjs +157 -0
  61. package/dist/cli/index.mjs.map +1 -0
  62. package/dist/constants-DdXFXQtN.mjs +85 -0
  63. package/dist/constants-DdXFXQtN.mjs.map +1 -0
  64. package/dist/core/index.d.mts +5 -0
  65. package/dist/core/index.mjs +4 -0
  66. package/dist/createApp-CUgNqegw.mjs +560 -0
  67. package/dist/createApp-CUgNqegw.mjs.map +1 -0
  68. package/dist/defineResource-k0_BDn8v.mjs +2197 -0
  69. package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
  70. package/dist/discovery/index.d.mts +47 -0
  71. package/dist/discovery/index.d.mts.map +1 -0
  72. package/dist/discovery/index.mjs +110 -0
  73. package/dist/discovery/index.mjs.map +1 -0
  74. package/dist/docs/index.d.mts +163 -0
  75. package/dist/docs/index.d.mts.map +1 -0
  76. package/dist/docs/index.mjs +73 -0
  77. package/dist/docs/index.mjs.map +1 -0
  78. package/dist/elevation-BRy3yFWT.mjs +113 -0
  79. package/dist/elevation-BRy3yFWT.mjs.map +1 -0
  80. package/dist/elevation-B_2dRLVP.d.mts +88 -0
  81. package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
  82. package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
  83. package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
  84. package/dist/errorHandler-C1okiriz.mjs +109 -0
  85. package/dist/errorHandler-C1okiriz.mjs.map +1 -0
  86. package/dist/errors-B9bZok84.mjs +212 -0
  87. package/dist/errors-B9bZok84.mjs.map +1 -0
  88. package/dist/errors-ChKiFz62.d.mts +125 -0
  89. package/dist/errors-ChKiFz62.d.mts.map +1 -0
  90. package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
  91. package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
  92. package/dist/eventPlugin-DGR_B2on.mjs +230 -0
  93. package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
  94. package/dist/events/index.d.mts +54 -0
  95. package/dist/events/index.d.mts.map +1 -0
  96. package/dist/events/index.mjs +52 -0
  97. package/dist/events/index.mjs.map +1 -0
  98. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  99. package/dist/events/transports/redis-stream-entry.mjs +178 -0
  100. package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
  101. package/dist/events/transports/redis.d.mts +77 -0
  102. package/dist/events/transports/redis.d.mts.map +1 -0
  103. package/dist/events/transports/redis.mjs +125 -0
  104. package/dist/events/transports/redis.mjs.map +1 -0
  105. package/dist/externalPaths-DlINfKbP.d.mts +51 -0
  106. package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
  107. package/dist/factory/index.d.mts +64 -0
  108. package/dist/factory/index.d.mts.map +1 -0
  109. package/dist/factory/index.mjs +3 -0
  110. package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
  111. package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
  112. package/dist/fields-DyaDVX4J.d.mts +110 -0
  113. package/dist/fields-DyaDVX4J.d.mts.map +1 -0
  114. package/dist/fields-iagOozy0.mjs +115 -0
  115. package/dist/fields-iagOozy0.mjs.map +1 -0
  116. package/dist/hooks/index.d.mts +4 -0
  117. package/dist/hooks/index.mjs +3 -0
  118. package/dist/idempotency/index.d.mts +97 -0
  119. package/dist/idempotency/index.d.mts.map +1 -0
  120. package/dist/idempotency/index.mjs +320 -0
  121. package/dist/idempotency/index.mjs.map +1 -0
  122. package/dist/idempotency/mongodb.d.mts +2 -0
  123. package/dist/idempotency/mongodb.mjs +115 -0
  124. package/dist/idempotency/mongodb.mjs.map +1 -0
  125. package/dist/idempotency/redis.d.mts +2 -0
  126. package/dist/idempotency/redis.mjs +104 -0
  127. package/dist/idempotency/redis.mjs.map +1 -0
  128. package/dist/index.d.mts +261 -0
  129. package/dist/index.d.mts.map +1 -0
  130. package/dist/index.mjs +105 -0
  131. package/dist/index.mjs.map +1 -0
  132. package/dist/integrations/event-gateway.d.mts +47 -0
  133. package/dist/integrations/event-gateway.d.mts.map +1 -0
  134. package/dist/integrations/event-gateway.mjs +44 -0
  135. package/dist/integrations/event-gateway.mjs.map +1 -0
  136. package/dist/integrations/index.d.mts +5 -0
  137. package/dist/integrations/index.mjs +1 -0
  138. package/dist/integrations/jobs.d.mts +104 -0
  139. package/dist/integrations/jobs.d.mts.map +1 -0
  140. package/dist/integrations/jobs.mjs +124 -0
  141. package/dist/integrations/jobs.mjs.map +1 -0
  142. package/dist/integrations/streamline.d.mts +61 -0
  143. package/dist/integrations/streamline.d.mts.map +1 -0
  144. package/dist/integrations/streamline.mjs +126 -0
  145. package/dist/integrations/streamline.mjs.map +1 -0
  146. package/dist/integrations/websocket.d.mts +83 -0
  147. package/dist/integrations/websocket.d.mts.map +1 -0
  148. package/dist/integrations/websocket.mjs +289 -0
  149. package/dist/integrations/websocket.mjs.map +1 -0
  150. package/dist/interface-B01JvPVc.d.mts +78 -0
  151. package/dist/interface-B01JvPVc.d.mts.map +1 -0
  152. package/dist/interface-CZe8IkMf.d.mts +55 -0
  153. package/dist/interface-CZe8IkMf.d.mts.map +1 -0
  154. package/dist/interface-Ch8HU9uM.d.mts +1098 -0
  155. package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
  156. package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
  157. package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
  158. package/dist/keys-BqNejWup.mjs +43 -0
  159. package/dist/keys-BqNejWup.mjs.map +1 -0
  160. package/dist/logger-Df2O2WsW.mjs +79 -0
  161. package/dist/logger-Df2O2WsW.mjs.map +1 -0
  162. package/dist/memory-cQgelFOj.mjs +144 -0
  163. package/dist/memory-cQgelFOj.mjs.map +1 -0
  164. package/dist/migrations/index.d.mts +157 -0
  165. package/dist/migrations/index.d.mts.map +1 -0
  166. package/dist/migrations/index.mjs +261 -0
  167. package/dist/migrations/index.mjs.map +1 -0
  168. package/dist/mongodb-BfJVlUJH.mjs +94 -0
  169. package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
  170. package/dist/mongodb-CGzRbfAK.d.mts +119 -0
  171. package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
  172. package/dist/mongodb-JN-9JA7K.d.mts +72 -0
  173. package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
  174. package/dist/openapi-G3Cw7XuM.mjs +524 -0
  175. package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
  176. package/dist/org/index.d.mts +69 -0
  177. package/dist/org/index.d.mts.map +1 -0
  178. package/dist/org/index.mjs +514 -0
  179. package/dist/org/index.mjs.map +1 -0
  180. package/dist/org/types.d.mts +83 -0
  181. package/dist/org/types.d.mts.map +1 -0
  182. package/dist/org/types.mjs +1 -0
  183. package/dist/permissions/index.d.mts +279 -0
  184. package/dist/permissions/index.d.mts.map +1 -0
  185. package/dist/permissions/index.mjs +579 -0
  186. package/dist/permissions/index.mjs.map +1 -0
  187. package/dist/plugins/index.d.mts +173 -0
  188. package/dist/plugins/index.d.mts.map +1 -0
  189. package/dist/plugins/index.mjs +523 -0
  190. package/dist/plugins/index.mjs.map +1 -0
  191. package/dist/plugins/response-cache.d.mts +88 -0
  192. package/dist/plugins/response-cache.d.mts.map +1 -0
  193. package/dist/plugins/response-cache.mjs +284 -0
  194. package/dist/plugins/response-cache.mjs.map +1 -0
  195. package/dist/plugins/tracing-entry.d.mts +2 -0
  196. package/dist/plugins/tracing-entry.mjs +186 -0
  197. package/dist/plugins/tracing-entry.mjs.map +1 -0
  198. package/dist/pluralize-CEweyOEm.mjs +87 -0
  199. package/dist/pluralize-CEweyOEm.mjs.map +1 -0
  200. package/dist/policies/{index.d.ts → index.d.mts} +204 -169
  201. package/dist/policies/index.d.mts.map +1 -0
  202. package/dist/policies/index.mjs +322 -0
  203. package/dist/policies/index.mjs.map +1 -0
  204. package/dist/presets/{index.d.ts → index.d.mts} +63 -131
  205. package/dist/presets/index.d.mts.map +1 -0
  206. package/dist/presets/index.mjs +144 -0
  207. package/dist/presets/index.mjs.map +1 -0
  208. package/dist/presets/multiTenant.d.mts +25 -0
  209. package/dist/presets/multiTenant.d.mts.map +1 -0
  210. package/dist/presets/multiTenant.mjs +114 -0
  211. package/dist/presets/multiTenant.mjs.map +1 -0
  212. package/dist/presets-BITljm96.mjs +120 -0
  213. package/dist/presets-BITljm96.mjs.map +1 -0
  214. package/dist/presets-DzSMwlKj.d.mts +58 -0
  215. package/dist/presets-DzSMwlKj.d.mts.map +1 -0
  216. package/dist/prisma-DJbMt3yf.mjs +628 -0
  217. package/dist/prisma-DJbMt3yf.mjs.map +1 -0
  218. package/dist/prisma-Dg9GoVdj.d.mts +275 -0
  219. package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
  220. package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
  221. package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
  222. package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
  223. package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
  224. package/dist/redis-D-JAeLtm.d.mts +50 -0
  225. package/dist/redis-D-JAeLtm.d.mts.map +1 -0
  226. package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
  227. package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
  228. package/dist/registry/index.d.mts +12 -0
  229. package/dist/registry/index.d.mts.map +1 -0
  230. package/dist/registry/index.mjs +4 -0
  231. package/dist/requestContext-QQD6ROJc.mjs +56 -0
  232. package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
  233. package/dist/schemaConverter-BwrmWroW.mjs +99 -0
  234. package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
  235. package/dist/schemas/index.d.mts +64 -0
  236. package/dist/schemas/index.d.mts.map +1 -0
  237. package/dist/schemas/index.mjs +83 -0
  238. package/dist/schemas/index.mjs.map +1 -0
  239. package/dist/scope/index.d.mts +22 -0
  240. package/dist/scope/index.d.mts.map +1 -0
  241. package/dist/scope/index.mjs +66 -0
  242. package/dist/scope/index.mjs.map +1 -0
  243. package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
  244. package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
  245. package/dist/sse-B3c3_yZp.mjs +124 -0
  246. package/dist/sse-B3c3_yZp.mjs.map +1 -0
  247. package/dist/testing/index.d.mts +908 -0
  248. package/dist/testing/index.d.mts.map +1 -0
  249. package/dist/testing/index.mjs +1977 -0
  250. package/dist/testing/index.mjs.map +1 -0
  251. package/dist/tracing-Cc7vVQPp.d.mts +71 -0
  252. package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
  253. package/dist/typeGuards-DhMNLuvU.mjs +10 -0
  254. package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
  255. package/dist/types/index.d.mts +947 -0
  256. package/dist/types/index.d.mts.map +1 -0
  257. package/dist/types/index.mjs +15 -0
  258. package/dist/types/index.mjs.map +1 -0
  259. package/dist/types-Beqn1Un7.mjs +39 -0
  260. package/dist/types-Beqn1Un7.mjs.map +1 -0
  261. package/dist/types-CIgB7UUl.d.mts +446 -0
  262. package/dist/types-CIgB7UUl.d.mts.map +1 -0
  263. package/dist/types-aYB4V7uN.d.mts +87 -0
  264. package/dist/types-aYB4V7uN.d.mts.map +1 -0
  265. package/dist/utils/index.d.mts +748 -0
  266. package/dist/utils/index.d.mts.map +1 -0
  267. package/dist/utils/index.mjs +6 -0
  268. package/package.json +194 -68
  269. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  270. package/dist/adapters/index.d.ts +0 -237
  271. package/dist/adapters/index.js +0 -668
  272. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  273. package/dist/audit/index.d.ts +0 -195
  274. package/dist/audit/index.js +0 -319
  275. package/dist/auth/index.d.ts +0 -47
  276. package/dist/auth/index.js +0 -174
  277. package/dist/cli/commands/docs.d.ts +0 -11
  278. package/dist/cli/commands/docs.js +0 -474
  279. package/dist/cli/commands/generate.js +0 -334
  280. package/dist/cli/commands/introspect.d.ts +0 -8
  281. package/dist/cli/commands/introspect.js +0 -338
  282. package/dist/cli/index.d.ts +0 -4
  283. package/dist/cli/index.js +0 -3269
  284. package/dist/core/index.d.ts +0 -220
  285. package/dist/core/index.js +0 -2786
  286. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  287. package/dist/docs/index.d.ts +0 -166
  288. package/dist/docs/index.js +0 -658
  289. package/dist/errors-8WIxGS_6.d.ts +0 -122
  290. package/dist/events/index.d.ts +0 -117
  291. package/dist/events/index.js +0 -89
  292. package/dist/factory/index.d.ts +0 -38
  293. package/dist/factory/index.js +0 -1652
  294. package/dist/hooks/index.d.ts +0 -4
  295. package/dist/hooks/index.js +0 -199
  296. package/dist/idempotency/index.d.ts +0 -323
  297. package/dist/idempotency/index.js +0 -500
  298. package/dist/index-B4t03KQ0.d.ts +0 -1366
  299. package/dist/index.d.ts +0 -135
  300. package/dist/index.js +0 -4756
  301. package/dist/migrations/index.d.ts +0 -185
  302. package/dist/migrations/index.js +0 -274
  303. package/dist/org/index.d.ts +0 -129
  304. package/dist/org/index.js +0 -220
  305. package/dist/permissions/index.d.ts +0 -144
  306. package/dist/permissions/index.js +0 -103
  307. package/dist/plugins/index.d.ts +0 -46
  308. package/dist/plugins/index.js +0 -1069
  309. package/dist/policies/index.js +0 -196
  310. package/dist/presets/index.js +0 -384
  311. package/dist/presets/multiTenant.d.ts +0 -39
  312. package/dist/presets/multiTenant.js +0 -112
  313. package/dist/registry/index.d.ts +0 -16
  314. package/dist/registry/index.js +0 -253
  315. package/dist/testing/index.d.ts +0 -618
  316. package/dist/testing/index.js +0 -48020
  317. package/dist/types/index.d.ts +0 -4
  318. package/dist/types/index.js +0 -8
  319. package/dist/types-B99TBmFV.d.ts +0 -76
  320. package/dist/types-BvckRbs2.d.ts +0 -143
  321. package/dist/utils/index.d.ts +0 -679
  322. package/dist/utils/index.js +0 -931
@@ -1,500 +0,0 @@
1
- import fp from 'fastify-plugin';
2
- import { createHash } from 'crypto';
3
-
4
- // src/idempotency/idempotencyPlugin.ts
5
-
6
- // src/idempotency/stores/interface.ts
7
- function createIdempotencyResult(statusCode, body, headers, ttlMs) {
8
- const now = /* @__PURE__ */ new Date();
9
- return {
10
- statusCode,
11
- headers,
12
- body,
13
- createdAt: now,
14
- expiresAt: new Date(now.getTime() + ttlMs)
15
- };
16
- }
17
-
18
- // src/idempotency/stores/memory.ts
19
- var MemoryIdempotencyStore = class {
20
- name = "memory";
21
- results = /* @__PURE__ */ new Map();
22
- locks = /* @__PURE__ */ new Map();
23
- ttlMs;
24
- maxEntries;
25
- cleanupInterval = null;
26
- constructor(options = {}) {
27
- this.ttlMs = options.ttlMs ?? 864e5;
28
- this.maxEntries = options.maxEntries ?? 1e4;
29
- const cleanupIntervalMs = options.cleanupIntervalMs ?? 6e4;
30
- this.cleanupInterval = setInterval(() => {
31
- this.cleanup();
32
- }, cleanupIntervalMs);
33
- if (this.cleanupInterval.unref) {
34
- this.cleanupInterval.unref();
35
- }
36
- }
37
- async get(key) {
38
- const result = this.results.get(key);
39
- if (!result) return void 0;
40
- if (/* @__PURE__ */ new Date() > result.expiresAt) {
41
- this.results.delete(key);
42
- return void 0;
43
- }
44
- return result;
45
- }
46
- async set(key, result) {
47
- if (this.results.size >= this.maxEntries) {
48
- this.evictOldest();
49
- }
50
- this.results.set(key, { ...result, key });
51
- }
52
- async tryLock(key, requestId, ttlMs) {
53
- const existing = this.locks.get(key);
54
- if (existing) {
55
- if (/* @__PURE__ */ new Date() > existing.expiresAt) {
56
- this.locks.delete(key);
57
- } else {
58
- return false;
59
- }
60
- }
61
- this.locks.set(key, {
62
- key,
63
- requestId,
64
- lockedAt: /* @__PURE__ */ new Date(),
65
- expiresAt: new Date(Date.now() + ttlMs)
66
- });
67
- return true;
68
- }
69
- async unlock(key, requestId) {
70
- const lock = this.locks.get(key);
71
- if (lock && lock.requestId === requestId) {
72
- this.locks.delete(key);
73
- }
74
- }
75
- async isLocked(key) {
76
- const lock = this.locks.get(key);
77
- if (!lock) return false;
78
- if (/* @__PURE__ */ new Date() > lock.expiresAt) {
79
- this.locks.delete(key);
80
- return false;
81
- }
82
- return true;
83
- }
84
- async delete(key) {
85
- this.results.delete(key);
86
- this.locks.delete(key);
87
- }
88
- async close() {
89
- if (this.cleanupInterval) {
90
- clearInterval(this.cleanupInterval);
91
- this.cleanupInterval = null;
92
- }
93
- this.results.clear();
94
- this.locks.clear();
95
- }
96
- /**
97
- * Get current stats (for debugging/monitoring)
98
- */
99
- getStats() {
100
- return {
101
- results: this.results.size,
102
- locks: this.locks.size
103
- };
104
- }
105
- /**
106
- * Remove expired entries
107
- */
108
- cleanup() {
109
- const now = /* @__PURE__ */ new Date();
110
- for (const [key, result] of this.results) {
111
- if (now > result.expiresAt) {
112
- this.results.delete(key);
113
- }
114
- }
115
- for (const [key, lock] of this.locks) {
116
- if (now > lock.expiresAt) {
117
- this.locks.delete(key);
118
- }
119
- }
120
- }
121
- /**
122
- * Evict oldest entries when at capacity
123
- */
124
- evictOldest() {
125
- const entries = Array.from(this.results.entries()).sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime());
126
- const toRemove = Math.max(1, Math.floor(entries.length * 0.1));
127
- for (let i = 0; i < toRemove; i++) {
128
- const entry = entries[i];
129
- if (entry) {
130
- this.results.delete(entry[0]);
131
- }
132
- }
133
- }
134
- };
135
-
136
- // src/idempotency/idempotencyPlugin.ts
137
- var HEADER_IDEMPOTENCY_REPLAYED = "x-idempotency-replayed";
138
- var HEADER_IDEMPOTENCY_KEY = "x-idempotency-key";
139
- var idempotencyPlugin = async (fastify, opts = {}) => {
140
- const {
141
- enabled = false,
142
- headerName = "idempotency-key",
143
- ttlMs = 864e5,
144
- // 24 hours
145
- lockTimeoutMs = 3e4,
146
- // 30 seconds
147
- methods = ["POST", "PUT", "PATCH"],
148
- include,
149
- exclude,
150
- store = new MemoryIdempotencyStore({ ttlMs }),
151
- retryAfterSeconds = 1
152
- } = opts;
153
- if (!enabled) {
154
- fastify.decorate("idempotency", {
155
- invalidate: async () => {
156
- },
157
- has: async () => false
158
- });
159
- fastify.decorateRequest("idempotencyKey", void 0);
160
- fastify.decorateRequest("idempotencyReplayed", false);
161
- fastify.log?.debug?.("Idempotency plugin disabled");
162
- return;
163
- }
164
- const methodSet = new Set(methods.map((m) => m.toUpperCase()));
165
- fastify.decorate("idempotency", {
166
- invalidate: async (key) => {
167
- await store.delete(key);
168
- },
169
- has: async (key) => {
170
- const result = await store.get(key);
171
- return !!result;
172
- }
173
- });
174
- fastify.decorateRequest("idempotencyKey", void 0);
175
- fastify.decorateRequest("idempotencyReplayed", false);
176
- function shouldApplyIdempotency(request) {
177
- if (!methodSet.has(request.method)) {
178
- return false;
179
- }
180
- const url = request.url;
181
- if (exclude?.some((pattern) => pattern.test(url))) {
182
- return false;
183
- }
184
- if (include && !include.some((pattern) => pattern.test(url))) {
185
- return false;
186
- }
187
- return true;
188
- }
189
- function normalizeBody(obj) {
190
- if (obj === null || typeof obj !== "object") {
191
- return obj;
192
- }
193
- if (Array.isArray(obj)) {
194
- return obj.map(normalizeBody);
195
- }
196
- const sorted = {};
197
- const keys = Object.keys(obj).sort();
198
- for (const key of keys) {
199
- sorted[key] = normalizeBody(obj[key]);
200
- }
201
- return sorted;
202
- }
203
- function getRequestFingerprint(request) {
204
- let bodyHash = "nobody";
205
- if (request.body && typeof request.body === "object") {
206
- const normalized = normalizeBody(request.body);
207
- const bodyString = JSON.stringify(normalized);
208
- bodyHash = createHash("sha256").update(bodyString).digest("hex").substring(0, 16);
209
- if (request.log && request.log.debug) {
210
- request.log.debug({ bodyHash }, "Generated body hash");
211
- }
212
- }
213
- const fingerprint = `${request.method}:${request.url}:${bodyHash}`;
214
- return fingerprint;
215
- }
216
- fastify.addHook("preHandler", async (request, reply) => {
217
- if (!shouldApplyIdempotency(request)) {
218
- return;
219
- }
220
- const keyHeader = request.headers[headerName.toLowerCase()];
221
- const idempotencyKey = typeof keyHeader === "string" ? keyHeader.trim() : void 0;
222
- if (!idempotencyKey) {
223
- return;
224
- }
225
- request.idempotencyKey = idempotencyKey;
226
- const fullKey = `${idempotencyKey}:${getRequestFingerprint(request)}`;
227
- const cached = await store.get(fullKey);
228
- if (cached) {
229
- request.idempotencyReplayed = true;
230
- reply.header(HEADER_IDEMPOTENCY_REPLAYED, "true");
231
- reply.header(HEADER_IDEMPOTENCY_KEY, idempotencyKey);
232
- for (const [key, value] of Object.entries(cached.headers)) {
233
- if (!key.startsWith("x-idempotency")) {
234
- reply.header(key, value);
235
- }
236
- }
237
- reply.code(cached.statusCode).send(cached.body);
238
- return;
239
- }
240
- const lockAcquired = await store.tryLock(fullKey, request.id, lockTimeoutMs);
241
- if (!lockAcquired) {
242
- reply.code(409).header("Retry-After", retryAfterSeconds.toString()).send({
243
- error: "Request with this idempotency key is already in progress",
244
- code: "IDEMPOTENCY_CONFLICT",
245
- retryAfter: retryAfterSeconds
246
- });
247
- return;
248
- }
249
- request._idempotencyFullKey = fullKey;
250
- });
251
- fastify.addHook("onSend", async (request, reply, payload) => {
252
- if (request.idempotencyReplayed) {
253
- return payload;
254
- }
255
- const fullKey = request._idempotencyFullKey;
256
- if (!fullKey) {
257
- return payload;
258
- }
259
- const statusCode = reply.statusCode;
260
- if (statusCode < 200 || statusCode >= 300) {
261
- await store.unlock(fullKey, request.id);
262
- return payload;
263
- }
264
- const headersToCache = {};
265
- const excludeHeaders = /* @__PURE__ */ new Set([
266
- "content-length",
267
- "transfer-encoding",
268
- "connection",
269
- "keep-alive",
270
- "date",
271
- "set-cookie"
272
- ]);
273
- const rawHeaders = reply.getHeaders();
274
- for (const [key, value] of Object.entries(rawHeaders)) {
275
- if (!excludeHeaders.has(key.toLowerCase()) && typeof value === "string") {
276
- headersToCache[key] = value;
277
- }
278
- }
279
- let body;
280
- try {
281
- body = typeof payload === "string" ? JSON.parse(payload) : payload;
282
- } catch {
283
- body = payload;
284
- }
285
- const result = createIdempotencyResult(statusCode, body, headersToCache, ttlMs);
286
- await store.set(fullKey, result);
287
- await store.unlock(fullKey, request.id);
288
- reply.header(HEADER_IDEMPOTENCY_KEY, request.idempotencyKey);
289
- return payload;
290
- });
291
- fastify.addHook("onError", async (request) => {
292
- const fullKey = request._idempotencyFullKey;
293
- if (fullKey) {
294
- await store.unlock(fullKey, request.id);
295
- }
296
- });
297
- fastify.addHook("onClose", async () => {
298
- await store.close?.();
299
- });
300
- fastify.log?.info?.({ headerName, ttlMs, methods }, "Idempotency plugin enabled");
301
- };
302
- var idempotencyPlugin_default = fp(idempotencyPlugin, {
303
- name: "arc-idempotency",
304
- fastify: "5.x"
305
- });
306
-
307
- // src/idempotency/stores/redis.ts
308
- var RedisIdempotencyStore = class {
309
- name = "redis";
310
- client;
311
- prefix;
312
- lockPrefix;
313
- ttlMs;
314
- constructor(options) {
315
- this.client = options.client;
316
- this.prefix = options.prefix ?? "idem:";
317
- this.lockPrefix = options.lockPrefix ?? "idem:lock:";
318
- this.ttlMs = options.ttlMs ?? 864e5;
319
- }
320
- resultKey(key) {
321
- return `${this.prefix}${key}`;
322
- }
323
- lockKey(key) {
324
- return `${this.lockPrefix}${key}`;
325
- }
326
- async get(key) {
327
- const data = await this.client.get(this.resultKey(key));
328
- if (!data) return void 0;
329
- try {
330
- const result = JSON.parse(data);
331
- if (new Date(result.expiresAt) < /* @__PURE__ */ new Date()) {
332
- await this.delete(key);
333
- return void 0;
334
- }
335
- return {
336
- ...result,
337
- createdAt: new Date(result.createdAt),
338
- expiresAt: new Date(result.expiresAt)
339
- };
340
- } catch {
341
- return void 0;
342
- }
343
- }
344
- async set(key, result) {
345
- const data = { key, ...result };
346
- const ttlSeconds = Math.ceil(
347
- (new Date(result.expiresAt).getTime() - Date.now()) / 1e3
348
- );
349
- if (ttlSeconds > 0) {
350
- await this.client.set(this.resultKey(key), JSON.stringify(data), {
351
- EX: ttlSeconds
352
- });
353
- }
354
- }
355
- async tryLock(key, requestId, ttlMs) {
356
- const ttlSeconds = Math.ceil(ttlMs / 1e3);
357
- const result = await this.client.set(
358
- this.lockKey(key),
359
- requestId,
360
- { EX: ttlSeconds, NX: true }
361
- );
362
- return result === "OK";
363
- }
364
- async unlock(key, requestId) {
365
- const currentHolder = await this.client.get(this.lockKey(key));
366
- if (currentHolder === requestId) {
367
- await this.client.del(this.lockKey(key));
368
- }
369
- }
370
- async isLocked(key) {
371
- const exists = await this.client.exists(this.lockKey(key));
372
- return exists > 0;
373
- }
374
- async delete(key) {
375
- await this.client.del([this.resultKey(key), this.lockKey(key)]);
376
- }
377
- async close() {
378
- }
379
- };
380
-
381
- // src/idempotency/stores/mongodb.ts
382
- var MongoIdempotencyStore = class {
383
- name = "mongodb";
384
- connection;
385
- collectionName;
386
- ttlMs;
387
- indexCreated = false;
388
- constructor(options) {
389
- this.connection = options.connection;
390
- this.collectionName = options.collection ?? "arc_idempotency";
391
- this.ttlMs = options.ttlMs ?? 864e5;
392
- if (options.createIndex !== false) {
393
- this.ensureIndex().catch((err) => {
394
- console.warn("[MongoIdempotencyStore] Failed to create index:", err);
395
- });
396
- }
397
- }
398
- get collection() {
399
- return this.connection.db.collection(this.collectionName);
400
- }
401
- async ensureIndex() {
402
- if (this.indexCreated) return;
403
- try {
404
- await this.collection.createIndex(
405
- { expiresAt: 1 },
406
- { expireAfterSeconds: 0 }
407
- );
408
- this.indexCreated = true;
409
- } catch {
410
- this.indexCreated = true;
411
- }
412
- }
413
- async get(key) {
414
- const doc = await this.collection.findOne({ _id: key });
415
- if (!doc || !doc.result) return void 0;
416
- if (new Date(doc.expiresAt) < /* @__PURE__ */ new Date()) {
417
- return void 0;
418
- }
419
- return {
420
- key,
421
- statusCode: doc.result.statusCode,
422
- headers: doc.result.headers,
423
- body: doc.result.body,
424
- createdAt: new Date(doc.createdAt),
425
- expiresAt: new Date(doc.expiresAt)
426
- };
427
- }
428
- async set(key, result) {
429
- await this.collection.updateOne(
430
- { _id: key },
431
- {
432
- $set: {
433
- result: {
434
- statusCode: result.statusCode,
435
- headers: result.headers,
436
- body: result.body
437
- },
438
- createdAt: result.createdAt,
439
- expiresAt: result.expiresAt
440
- },
441
- $unset: { lock: "" }
442
- },
443
- { upsert: true }
444
- );
445
- }
446
- async tryLock(key, requestId, ttlMs) {
447
- const now = /* @__PURE__ */ new Date();
448
- const expiresAt = new Date(now.getTime() + ttlMs);
449
- try {
450
- const existingDoc = await this.collection.findOne({ _id: key });
451
- if (existingDoc) {
452
- if (existingDoc.lock && new Date(existingDoc.lock.expiresAt) > now) {
453
- return false;
454
- }
455
- const updateResult = await this.collection.updateOne(
456
- {
457
- _id: key,
458
- $or: [
459
- { lock: { $exists: false } },
460
- { "lock.expiresAt": { $lt: now } }
461
- ]
462
- },
463
- {
464
- $set: {
465
- lock: { requestId, expiresAt }
466
- }
467
- }
468
- );
469
- return updateResult.acknowledged;
470
- }
471
- const insertResult = await this.collection.insertOne({
472
- _id: key,
473
- lock: { requestId, expiresAt },
474
- createdAt: now,
475
- expiresAt: new Date(now.getTime() + this.ttlMs)
476
- });
477
- return insertResult.acknowledged;
478
- } catch {
479
- return false;
480
- }
481
- }
482
- async unlock(key, requestId) {
483
- await this.collection.updateOne(
484
- { _id: key, "lock.requestId": requestId },
485
- { $unset: { lock: "" } }
486
- );
487
- }
488
- async isLocked(key) {
489
- const doc = await this.collection.findOne({ _id: key });
490
- if (!doc || !doc.lock) return false;
491
- return new Date(doc.lock.expiresAt) > /* @__PURE__ */ new Date();
492
- }
493
- async delete(key) {
494
- await this.collection.deleteOne({ _id: key });
495
- }
496
- async close() {
497
- }
498
- };
499
-
500
- export { MemoryIdempotencyStore, MongoIdempotencyStore, RedisIdempotencyStore, createIdempotencyResult, idempotencyPlugin_default as idempotencyPlugin, idempotencyPlugin as idempotencyPluginFn };