@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/idempotency/stores/interface.ts","../../src/idempotency/stores/memory.ts","../../src/idempotency/idempotencyPlugin.ts"],"sourcesContent":["/**\n * Idempotency Store Interface\n *\n * Defines the contract for idempotency key storage backends.\n * Implement this interface for custom stores (Redis, DynamoDB, etc.)\n */\n\nexport interface IdempotencyResult {\n /** The idempotency key */\n key: string;\n /** HTTP status code of the cached response */\n statusCode: number;\n /** Response headers to replay */\n headers: Record<string, string>;\n /** Response body */\n body: unknown;\n /** When this entry was created */\n createdAt: Date;\n /** When this entry expires */\n expiresAt: Date;\n}\n\nexport interface IdempotencyLock {\n /** The idempotency key being locked */\n key: string;\n /** Request ID that holds the lock */\n requestId: string;\n /** When the lock was acquired */\n lockedAt: Date;\n /** When the lock expires (auto-release) */\n expiresAt: Date;\n}\n\nexport interface IdempotencyStore {\n /** Store name for logging */\n readonly name: string;\n\n /**\n * Get a cached result for an idempotency key.\n * Returns undefined if not found or expired.\n */\n get(key: string): Promise<IdempotencyResult | undefined>;\n\n /**\n * Store a result for an idempotency key.\n * TTL is handled by the store implementation.\n */\n set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void>;\n\n /**\n * Try to acquire a lock for processing a key.\n * Returns true if lock acquired, false if already locked.\n */\n tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;\n\n /** Release a lock after processing complete */\n unlock(key: string, requestId: string): Promise<void>;\n\n /** Check if a key is currently locked */\n isLocked(key: string): Promise<boolean>;\n\n /** Delete a cached result by exact key */\n delete(key: string): Promise<void>;\n\n /**\n * Delete all cached results whose key starts with the given prefix.\n * Used by invalidate() to clear entries by raw idempotency key\n * regardless of fingerprint.\n * Returns the number of entries deleted.\n */\n deleteByPrefix(prefix: string): Promise<number>;\n\n /**\n * Find the first cached result whose key starts with the given prefix.\n * Used by has() to check if any entry exists for a raw idempotency key.\n * Returns undefined if no matching entry found.\n */\n findByPrefix(prefix: string): Promise<IdempotencyResult | undefined>;\n\n /** Close the store (cleanup connections) */\n close?(): Promise<void>;\n}\n\n/**\n * Helper to create a result object\n */\nexport function createIdempotencyResult(\n statusCode: number,\n body: unknown,\n headers: Record<string, string>,\n ttlMs: number\n): Omit<IdempotencyResult, 'key'> {\n const now = new Date();\n return {\n statusCode,\n headers,\n body,\n createdAt: now,\n expiresAt: new Date(now.getTime() + ttlMs),\n };\n}\n","/**\n * In-Memory Idempotency Store\n *\n * Default store for development and small deployments.\n * NOT suitable for multi-instance deployments - use Redis or similar.\n *\n * Features:\n * - Automatic TTL expiration\n * - Lock support for concurrent request handling\n * - Periodic cleanup of expired entries\n * - Prefix-based operations for raw key invalidation\n */\n\nimport type { IdempotencyResult, IdempotencyStore, IdempotencyLock } from './interface.js';\n\nexport interface MemoryIdempotencyStoreOptions {\n /** Default TTL in milliseconds (default: 86400000 = 24h) */\n ttlMs?: number;\n /** Cleanup interval in milliseconds (default: 60000 = 1 min) */\n cleanupIntervalMs?: number;\n /** Maximum entries before oldest are evicted (default: 10000) */\n maxEntries?: number;\n}\n\nexport class MemoryIdempotencyStore implements IdempotencyStore {\n readonly name = 'memory';\n private results: Map<string, IdempotencyResult> = new Map();\n private locks: Map<string, IdempotencyLock> = new Map();\n private ttlMs: number;\n private maxEntries: number;\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: MemoryIdempotencyStoreOptions = {}) {\n this.ttlMs = options.ttlMs ?? 86400000; // 24 hours\n this.maxEntries = options.maxEntries ?? 10000;\n\n // Start cleanup timer\n const cleanupIntervalMs = options.cleanupIntervalMs ?? 60000;\n this.cleanupInterval = setInterval(() => {\n this.cleanup();\n }, cleanupIntervalMs);\n\n // Don't keep Node process alive just for cleanup\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref();\n }\n }\n\n async get(key: string): Promise<IdempotencyResult | undefined> {\n const result = this.results.get(key);\n if (!result) return undefined;\n\n // Check expiration\n if (new Date() > result.expiresAt) {\n this.results.delete(key);\n return undefined;\n }\n\n return result;\n }\n\n async set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void> {\n // Evict oldest if at capacity\n if (this.results.size >= this.maxEntries) {\n this.evictOldest();\n }\n\n this.results.set(key, { ...result, key });\n }\n\n async tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean> {\n const existing = this.locks.get(key);\n\n if (existing) {\n // Check if lock expired\n if (new Date() > existing.expiresAt) {\n this.locks.delete(key);\n } else {\n // Lock held by someone else\n return false;\n }\n }\n\n // Acquire lock\n this.locks.set(key, {\n key,\n requestId,\n lockedAt: new Date(),\n expiresAt: new Date(Date.now() + ttlMs),\n });\n\n return true;\n }\n\n async unlock(key: string, requestId: string): Promise<void> {\n const lock = this.locks.get(key);\n if (lock && lock.requestId === requestId) {\n this.locks.delete(key);\n }\n }\n\n async isLocked(key: string): Promise<boolean> {\n const lock = this.locks.get(key);\n if (!lock) return false;\n\n // Check if expired\n if (new Date() > lock.expiresAt) {\n this.locks.delete(key);\n return false;\n }\n\n return true;\n }\n\n async delete(key: string): Promise<void> {\n this.results.delete(key);\n this.locks.delete(key);\n }\n\n async deleteByPrefix(prefix: string): Promise<number> {\n let count = 0;\n for (const key of this.results.keys()) {\n if (key.startsWith(prefix)) {\n this.results.delete(key);\n count++;\n }\n }\n for (const key of this.locks.keys()) {\n if (key.startsWith(prefix)) {\n this.locks.delete(key);\n }\n }\n return count;\n }\n\n async findByPrefix(prefix: string): Promise<IdempotencyResult | undefined> {\n const now = new Date();\n for (const [key, result] of this.results) {\n if (key.startsWith(prefix)) {\n if (now > result.expiresAt) {\n this.results.delete(key);\n continue;\n }\n return result;\n }\n }\n return undefined;\n }\n\n async close(): Promise<void> {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n this.results.clear();\n this.locks.clear();\n }\n\n /** Get current stats (for debugging/monitoring) */\n getStats(): { results: number; locks: number } {\n return {\n results: this.results.size,\n locks: this.locks.size,\n };\n }\n\n private cleanup(): void {\n const now = new Date();\n\n for (const [key, result] of this.results) {\n if (now > result.expiresAt) {\n this.results.delete(key);\n }\n }\n\n for (const [key, lock] of this.locks) {\n if (now > lock.expiresAt) {\n this.locks.delete(key);\n }\n }\n }\n\n private evictOldest(): void {\n const entries = Array.from(this.results.entries())\n .sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime());\n\n const toRemove = Math.max(1, Math.floor(entries.length * 0.1));\n for (let i = 0; i < toRemove; i++) {\n const entry = entries[i];\n if (entry) {\n this.results.delete(entry[0]);\n }\n }\n }\n}\n\nexport default MemoryIdempotencyStore;\n","/**\n * Idempotency Plugin\n *\n * Duplicate request protection for mutating operations.\n * Uses idempotency keys to ensure safe retries.\n *\n * ## Auth Safety\n *\n * The idempotency check runs as a **route-level middleware**\n * (`idempotency.middleware`) that must be wired AFTER authentication in the\n * preHandler chain. This ensures the fingerprint includes the real caller\n * identity, preventing cross-user replay attacks.\n *\n * Arc's `createCrudRouter` does this automatically for mutation routes.\n * For custom routes, wire it manually:\n *\n * ```typescript\n * fastify.post('/orders', {\n * preHandler: [fastify.authenticate, fastify.idempotency.middleware],\n * }, handler);\n * ```\n *\n * @example\n * import { idempotencyPlugin } from '@classytic/arc/idempotency';\n *\n * await fastify.register(idempotencyPlugin, {\n * enabled: true,\n * headerName: 'idempotency-key',\n * ttlMs: 86400000, // 24 hours\n * });\n *\n * // Client sends:\n * // POST /api/orders\n * // Idempotency-Key: order-123-abc\n *\n * // If same key sent again within TTL, returns cached response\n */\n\nimport fp from 'fastify-plugin';\nimport { createHash } from 'crypto';\nimport type { FastifyInstance, FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';\nimport type { IdempotencyStore } from './stores/interface.js';\nimport { createIdempotencyResult } from './stores/interface.js';\nimport { MemoryIdempotencyStore } from './stores/memory.js';\n\nexport interface IdempotencyPluginOptions {\n /** Enable idempotency (default: false) */\n enabled?: boolean;\n /** Header name for idempotency key (default: 'idempotency-key') */\n headerName?: string;\n /** TTL for cached responses in ms (default: 86400000 = 24h) */\n ttlMs?: number;\n /** Lock timeout in ms (default: 30000 = 30s) */\n lockTimeoutMs?: number;\n /** HTTP methods to apply idempotency to (default: ['POST', 'PUT', 'PATCH']) */\n methods?: string[];\n /** URL patterns to include (regex). If set, only matching URLs use idempotency */\n include?: RegExp[];\n /** URL patterns to exclude (regex). Excluded patterns take precedence */\n exclude?: RegExp[];\n /** Custom store (default: MemoryIdempotencyStore) */\n store?: IdempotencyStore;\n /** Retry-After header value in seconds when request is in-flight (default: 1) */\n retryAfterSeconds?: number;\n}\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n /** The idempotency key for this request (if present) */\n idempotencyKey?: string;\n /** Whether this response was replayed from cache */\n idempotencyReplayed?: boolean;\n /** @internal Full key with fingerprint for store lookups */\n _idempotencyFullKey?: string;\n }\n\n interface FastifyInstance {\n /** Idempotency utilities */\n idempotency: {\n /** Manually invalidate an idempotency key */\n invalidate: (key: string) => Promise<void>;\n /** Check if a key has a cached response */\n has: (key: string) => Promise<boolean>;\n /**\n * Route-level preHandler for idempotency check + lock.\n * Wire AFTER authenticate in the preHandler chain so that\n * `request.user` is populated before the fingerprint is computed.\n *\n * `createCrudRouter` injects this automatically for mutation routes.\n * For custom routes, add it manually:\n * ```typescript\n * fastify.post('/orders', {\n * preHandler: [fastify.authenticate, fastify.idempotency.middleware],\n * }, handler);\n * ```\n */\n middleware: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;\n };\n }\n}\n\nconst HEADER_IDEMPOTENCY_REPLAYED = 'x-idempotency-replayed';\nconst HEADER_IDEMPOTENCY_KEY = 'x-idempotency-key';\n\nconst idempotencyPlugin: FastifyPluginAsync<IdempotencyPluginOptions> = async (\n fastify: FastifyInstance,\n opts: IdempotencyPluginOptions = {}\n) => {\n const {\n enabled = false,\n headerName = 'idempotency-key',\n ttlMs = 86400000, // 24 hours\n lockTimeoutMs = 30000, // 30 seconds\n methods = ['POST', 'PUT', 'PATCH'],\n include,\n exclude,\n store = new MemoryIdempotencyStore({ ttlMs }),\n retryAfterSeconds = 1,\n } = opts;\n\n // Skip if not enabled\n if (!enabled) {\n // Provide no-op utilities\n fastify.decorate('idempotency', {\n invalidate: async () => {},\n has: async () => false,\n middleware: async () => {},\n });\n fastify.decorateRequest('idempotencyKey', undefined);\n fastify.decorateRequest('idempotencyReplayed', false);\n fastify.log?.debug?.('Idempotency plugin disabled');\n return;\n }\n\n const methodSet = new Set(methods.map((m) => m.toUpperCase()));\n\n fastify.decorateRequest('idempotencyKey', undefined);\n fastify.decorateRequest('idempotencyReplayed', false);\n\n /**\n * Check if this request should use idempotency\n */\n function shouldApplyIdempotency(request: FastifyRequest): boolean {\n // Check method\n if (!methodSet.has(request.method)) {\n return false;\n }\n\n const url = request.url;\n\n // Check exclusions first (take precedence)\n if (exclude?.some((pattern) => pattern.test(url))) {\n return false;\n }\n\n // Check inclusions (if specified, only matching URLs apply)\n if (include && !include.some((pattern) => pattern.test(url))) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Normalize body for consistent hashing (sort keys recursively)\n */\n function normalizeBody(obj: unknown): unknown {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(normalizeBody);\n }\n\n // Sort object keys\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n sorted[key] = normalizeBody((obj as Record<string, unknown>)[key]);\n }\n return sorted;\n }\n\n /**\n * Generate a fingerprint for the request (for key generation).\n * Includes caller identity so the same idempotency key from different\n * users doesn't replay one user's response to another.\n *\n * IMPORTANT: This must be called AFTER auth has populated request.user,\n * otherwise userId falls back to 'anon' and cross-user replay is possible.\n */\n function getRequestFingerprint(request: FastifyRequest): string {\n // Combine method + URL + body hash + user identity for uniqueness\n let bodyHash = 'nobody';\n\n if (request.body && typeof request.body === 'object') {\n // Normalize body (sort keys) for consistent hashing\n const normalized = normalizeBody(request.body);\n const bodyString = JSON.stringify(normalized);\n bodyHash = createHash('sha256').update(bodyString).digest('hex').substring(0, 16);\n // SECURITY: Only log hash, never log full body (can contain secrets)\n if (request.log && request.log.debug) {\n request.log.debug({ bodyHash }, 'Generated body hash');\n }\n }\n\n // Scope to caller identity to prevent cross-user replay\n const user = request.user as { id?: string; _id?: string } | undefined;\n const userId = user?.id ?? user?._id ?? 'anon';\n\n const fingerprint = `${request.method}:${request.url}:${bodyHash}:u=${userId}`;\n return fingerprint;\n }\n\n // ---- Route-level middleware: check + lock (AFTER auth) ----\n const idempotencyMiddleware = async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n if (!shouldApplyIdempotency(request)) {\n return;\n }\n\n // Get idempotency key from header\n const keyHeader = request.headers[headerName.toLowerCase()];\n const idempotencyKey = typeof keyHeader === 'string' ? keyHeader.trim() : undefined;\n\n if (!idempotencyKey) {\n // No key provided - proceed normally\n return;\n }\n\n // Store key on request for later use\n request.idempotencyKey = idempotencyKey;\n\n // Create full key with request fingerprint (user is now populated by auth)\n const fullKey = `${idempotencyKey}:${getRequestFingerprint(request)}`;\n\n // Check for cached result\n const cached = await store.get(fullKey);\n if (cached) {\n // Replay cached response\n request.idempotencyReplayed = true;\n\n // Set response headers\n reply.header(HEADER_IDEMPOTENCY_REPLAYED, 'true');\n reply.header(HEADER_IDEMPOTENCY_KEY, idempotencyKey);\n\n // Replay original headers\n for (const [key, value] of Object.entries(cached.headers)) {\n if (!key.startsWith('x-idempotency')) {\n reply.header(key, value);\n }\n }\n\n reply.code(cached.statusCode).send(cached.body);\n return;\n }\n\n // Try to acquire lock\n const lockAcquired = await store.tryLock(fullKey, request.id, lockTimeoutMs);\n if (!lockAcquired) {\n // Another request is processing this key\n reply\n .code(409)\n .header('Retry-After', retryAfterSeconds.toString())\n .send({\n error: 'Request with this idempotency key is already in progress',\n code: 'IDEMPOTENCY_CONFLICT',\n retryAfter: retryAfterSeconds,\n });\n return;\n }\n\n // Store full key for onSend hook\n request._idempotencyFullKey = fullKey;\n };\n\n // Decorate with utilities + middleware\n fastify.decorate('idempotency', {\n invalidate: async (key: string) => {\n // Delete all entries for this raw idempotency key regardless of fingerprint\n await store.deleteByPrefix(`${key}:`);\n },\n has: async (key: string) => {\n // Check if any entry exists for this raw idempotency key\n const result = await store.findByPrefix(`${key}:`);\n return !!result;\n },\n middleware: idempotencyMiddleware,\n });\n\n // Store response after successful request\n fastify.addHook('onSend', async (request, reply, payload) => {\n // Skip if this was a replayed response\n if (request.idempotencyReplayed) {\n return payload;\n }\n\n const fullKey = request._idempotencyFullKey;\n if (!fullKey) {\n return payload;\n }\n\n // Only cache successful responses (2xx)\n const statusCode = reply.statusCode;\n if (statusCode < 200 || statusCode >= 300) {\n // Unlock without caching\n await store.unlock(fullKey, request.id);\n return payload;\n }\n\n // Extract headers to cache (exclude certain headers)\n const headersToCache: Record<string, string> = {};\n const excludeHeaders = new Set([\n 'content-length',\n 'transfer-encoding',\n 'connection',\n 'keep-alive',\n 'date',\n 'set-cookie',\n ]);\n\n const rawHeaders = reply.getHeaders();\n for (const [key, value] of Object.entries(rawHeaders)) {\n if (!excludeHeaders.has(key.toLowerCase()) && typeof value === 'string') {\n headersToCache[key] = value;\n }\n }\n\n // Parse body if it's a string\n let body: unknown;\n try {\n body = typeof payload === 'string' ? JSON.parse(payload) : payload;\n } catch {\n body = payload;\n }\n\n // Store the result\n const result = createIdempotencyResult(statusCode, body, headersToCache, ttlMs);\n await store.set(fullKey, result);\n\n // Unlock (result is now cached)\n await store.unlock(fullKey, request.id);\n\n // Add idempotency key header to response\n reply.header(HEADER_IDEMPOTENCY_KEY, request.idempotencyKey);\n\n return payload;\n });\n\n // Handle errors - ensure lock is released\n fastify.addHook('onError', async (request) => {\n const fullKey = request._idempotencyFullKey;\n if (fullKey) {\n await store.unlock(fullKey, request.id);\n }\n });\n\n // Cleanup on close\n fastify.addHook('onClose', async () => {\n await store.close?.();\n });\n\n fastify.log?.debug?.({ headerName, ttlMs, methods }, 'Idempotency plugin enabled');\n};\n\nexport default fp(idempotencyPlugin, {\n name: 'arc-idempotency',\n fastify: '5.x',\n});\n\nexport { idempotencyPlugin };\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as MongoIdempotencyStoreOptions, t as MongoIdempotencyStore } from "../mongodb-JN-9JA7K.mjs";
2
+ export { MongoIdempotencyStore, type MongoIdempotencyStoreOptions };
@@ -0,0 +1,115 @@
1
+ //#region src/idempotency/stores/mongodb.ts
2
+ var MongoIdempotencyStore = class {
3
+ name = "mongodb";
4
+ connection;
5
+ collectionName;
6
+ ttlMs;
7
+ indexCreated = false;
8
+ constructor(options) {
9
+ this.connection = options.connection;
10
+ this.collectionName = options.collection ?? "arc_idempotency";
11
+ this.ttlMs = options.ttlMs ?? 864e5;
12
+ if (options.createIndex !== false) this.ensureIndex().catch((err) => {
13
+ console.warn("[MongoIdempotencyStore] Failed to create index:", err);
14
+ });
15
+ }
16
+ get collection() {
17
+ return this.connection.db.collection(this.collectionName);
18
+ }
19
+ async ensureIndex() {
20
+ if (this.indexCreated) return;
21
+ try {
22
+ await this.collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 });
23
+ this.indexCreated = true;
24
+ } catch {
25
+ this.indexCreated = true;
26
+ }
27
+ }
28
+ async get(key) {
29
+ const doc = await this.collection.findOne({ _id: key });
30
+ if (!doc || !doc.result) return void 0;
31
+ if (new Date(doc.expiresAt) < /* @__PURE__ */ new Date()) return;
32
+ return {
33
+ key,
34
+ statusCode: doc.result.statusCode,
35
+ headers: doc.result.headers,
36
+ body: doc.result.body,
37
+ createdAt: new Date(doc.createdAt),
38
+ expiresAt: new Date(doc.expiresAt)
39
+ };
40
+ }
41
+ async set(key, result) {
42
+ await this.collection.updateOne({ _id: key }, {
43
+ $set: {
44
+ result: {
45
+ statusCode: result.statusCode,
46
+ headers: result.headers,
47
+ body: result.body
48
+ },
49
+ createdAt: result.createdAt,
50
+ expiresAt: result.expiresAt
51
+ },
52
+ $unset: { lock: "" }
53
+ }, { upsert: true });
54
+ }
55
+ async tryLock(key, requestId, ttlMs) {
56
+ const now = /* @__PURE__ */ new Date();
57
+ const expiresAt = new Date(now.getTime() + ttlMs);
58
+ try {
59
+ const result = await this.collection.updateOne({
60
+ _id: key,
61
+ $or: [{ lock: { $exists: false } }, { "lock.expiresAt": { $lt: now } }]
62
+ }, {
63
+ $set: { lock: {
64
+ requestId,
65
+ expiresAt
66
+ } },
67
+ $setOnInsert: {
68
+ createdAt: now,
69
+ expiresAt: new Date(now.getTime() + this.ttlMs)
70
+ }
71
+ }, { upsert: true });
72
+ return result.matchedCount === 1 || result.modifiedCount === 1;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ async unlock(key, requestId) {
78
+ await this.collection.updateOne({
79
+ _id: key,
80
+ "lock.requestId": requestId
81
+ }, { $unset: { lock: "" } });
82
+ }
83
+ async isLocked(key) {
84
+ const doc = await this.collection.findOne({ _id: key });
85
+ if (!doc || !doc.lock) return false;
86
+ return new Date(doc.lock.expiresAt) > /* @__PURE__ */ new Date();
87
+ }
88
+ async delete(key) {
89
+ await this.collection.deleteOne({ _id: key });
90
+ }
91
+ async deleteByPrefix(prefix) {
92
+ return (await this.collection.deleteMany({ _id: { $regex: `^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}` } })).deletedCount;
93
+ }
94
+ async findByPrefix(prefix) {
95
+ const doc = await this.collection.findOne({
96
+ _id: { $regex: `^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}` },
97
+ result: { $exists: true },
98
+ expiresAt: { $gt: /* @__PURE__ */ new Date() }
99
+ });
100
+ if (!doc || !doc.result) return void 0;
101
+ return {
102
+ key: doc._id,
103
+ statusCode: doc.result.statusCode,
104
+ headers: doc.result.headers,
105
+ body: doc.result.body,
106
+ createdAt: new Date(doc.createdAt),
107
+ expiresAt: new Date(doc.expiresAt)
108
+ };
109
+ }
110
+ async close() {}
111
+ };
112
+
113
+ //#endregion
114
+ export { MongoIdempotencyStore };
115
+ //# sourceMappingURL=mongodb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb.mjs","names":[],"sources":["../../src/idempotency/stores/mongodb.ts"],"sourcesContent":["/**\n * MongoDB Idempotency Store\n *\n * Durable idempotency store using MongoDB.\n * Suitable for multi-instance deployments.\n *\n * @example\n * import mongoose from 'mongoose';\n * import { MongoIdempotencyStore } from '@classytic/arc/idempotency';\n *\n * await fastify.register(idempotencyPlugin, {\n * store: new MongoIdempotencyStore({\n * connection: mongoose.connection,\n * collection: 'idempotency_keys',\n * }),\n * });\n */\n\nimport type { IdempotencyStore, IdempotencyResult } from './interface.js';\n\nexport interface MongoConnection {\n db: {\n collection(name: string): MongoCollection;\n };\n}\n\ninterface MongoCollection {\n findOne(filter: object): Promise<IdempotencyDocument | null>;\n insertOne(doc: object): Promise<{ acknowledged: boolean }>;\n updateOne(filter: object, update: object, options?: object): Promise<{ acknowledged: boolean; matchedCount: number; modifiedCount: number }>;\n deleteOne(filter: object): Promise<{ deletedCount: number }>;\n deleteMany(filter: object): Promise<{ deletedCount: number }>;\n createIndex(spec: object, options?: object): Promise<string>;\n}\n\ninterface IdempotencyDocument {\n _id: string;\n result?: {\n statusCode: number;\n headers: Record<string, string>;\n body: unknown;\n };\n lock?: {\n requestId: string;\n expiresAt: Date;\n };\n createdAt: Date;\n expiresAt: Date;\n}\n\nexport interface MongoIdempotencyStoreOptions {\n /** Mongoose connection or MongoDB connection object */\n connection: MongoConnection;\n /** Collection name (default: 'arc_idempotency') */\n collection?: string;\n /** Create TTL index on startup (default: true) */\n createIndex?: boolean;\n /** Default TTL in ms (default: 86400000 = 24 hours) */\n ttlMs?: number;\n}\n\nexport class MongoIdempotencyStore implements IdempotencyStore {\n readonly name = 'mongodb';\n private connection: MongoConnection;\n private collectionName: string;\n private ttlMs: number;\n private indexCreated = false;\n\n constructor(options: MongoIdempotencyStoreOptions) {\n this.connection = options.connection;\n this.collectionName = options.collection ?? 'arc_idempotency';\n this.ttlMs = options.ttlMs ?? 86400000;\n\n if (options.createIndex !== false) {\n this.ensureIndex().catch((err) => {\n console.warn('[MongoIdempotencyStore] Failed to create index:', err);\n });\n }\n }\n\n private get collection(): MongoCollection {\n return this.connection.db.collection(this.collectionName);\n }\n\n private async ensureIndex(): Promise<void> {\n if (this.indexCreated) return;\n try {\n // TTL index for automatic cleanup\n await this.collection.createIndex(\n { expiresAt: 1 },\n { expireAfterSeconds: 0 }\n );\n this.indexCreated = true;\n } catch {\n // Index might already exist\n this.indexCreated = true;\n }\n }\n\n async get(key: string): Promise<IdempotencyResult | undefined> {\n const doc = await this.collection.findOne({ _id: key });\n if (!doc || !doc.result) return undefined;\n\n // Check expiration\n if (new Date(doc.expiresAt) < new Date()) {\n return undefined;\n }\n\n return {\n key,\n statusCode: doc.result.statusCode,\n headers: doc.result.headers,\n body: doc.result.body,\n createdAt: new Date(doc.createdAt),\n expiresAt: new Date(doc.expiresAt),\n };\n }\n\n async set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void> {\n await this.collection.updateOne(\n { _id: key },\n {\n $set: {\n result: {\n statusCode: result.statusCode,\n headers: result.headers,\n body: result.body,\n },\n createdAt: result.createdAt,\n expiresAt: result.expiresAt,\n },\n $unset: { lock: '' },\n },\n { upsert: true }\n );\n }\n\n async tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean> {\n const now = new Date();\n const expiresAt = new Date(now.getTime() + ttlMs);\n\n try {\n // Atomic upsert: acquire lock only when no active lock exists.\n // Uses a single updateOne with upsert to avoid TOCTOU races —\n // matchedCount/upsertedCount tells us if WE acquired the lock\n // (not just that MongoDB acknowledged the command).\n const result = await this.collection.updateOne(\n {\n _id: key,\n $or: [\n { lock: { $exists: false } },\n { 'lock.expiresAt': { $lt: now } },\n ],\n },\n {\n $set: {\n lock: { requestId, expiresAt },\n },\n $setOnInsert: {\n createdAt: now,\n expiresAt: new Date(now.getTime() + this.ttlMs),\n },\n },\n { upsert: true },\n );\n\n // matchedCount === 1: existing doc matched (lock was free/expired)\n // modifiedCount can be 0 if the $set value is identical — use matchedCount\n // upsertedCount === 1 (implied by acknowledged + matchedCount === 0):\n // new doc created with our lock\n return result.matchedCount === 1 || result.modifiedCount === 1;\n } catch {\n // Duplicate key on upsert race or other error → someone else got the lock\n return false;\n }\n }\n\n async unlock(key: string, requestId: string): Promise<void> {\n // Only unlock if we hold the lock\n await this.collection.updateOne(\n { _id: key, 'lock.requestId': requestId },\n { $unset: { lock: '' } }\n );\n }\n\n async isLocked(key: string): Promise<boolean> {\n const doc = await this.collection.findOne({ _id: key });\n if (!doc || !doc.lock) return false;\n return new Date(doc.lock.expiresAt) > new Date();\n }\n\n async delete(key: string): Promise<void> {\n await this.collection.deleteOne({ _id: key });\n }\n\n async deleteByPrefix(prefix: string): Promise<number> {\n const result = await this.collection.deleteMany({\n _id: { $regex: `^${prefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}` },\n });\n return result.deletedCount;\n }\n\n async findByPrefix(prefix: string): Promise<IdempotencyResult | undefined> {\n // Filter expired docs at the query level so MongoDB doesn't return a stale\n // entry when a valid one exists further down the index.\n const doc = await this.collection.findOne({\n _id: { $regex: `^${prefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}` },\n result: { $exists: true },\n expiresAt: { $gt: new Date() },\n });\n if (!doc || !doc.result) return undefined;\n\n return {\n key: doc._id,\n statusCode: doc.result.statusCode,\n headers: doc.result.headers,\n body: doc.result.body,\n createdAt: new Date(doc.createdAt),\n expiresAt: new Date(doc.expiresAt),\n };\n }\n\n async close(): Promise<void> {\n // Don't close the connection - it's passed in and may be shared\n }\n}\n\nexport default MongoIdempotencyStore;\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as RedisIdempotencyStore, r as RedisIdempotencyStoreOptions, t as RedisClient } from "../redis-D-JAeLtm.mjs";
2
+ export { type RedisClient, RedisIdempotencyStore, type RedisIdempotencyStoreOptions };
@@ -0,0 +1,104 @@
1
+ //#region src/idempotency/stores/redis.ts
2
+ var RedisIdempotencyStore = class {
3
+ name = "redis";
4
+ client;
5
+ prefix;
6
+ lockPrefix;
7
+ ttlMs;
8
+ constructor(options) {
9
+ this.client = options.client;
10
+ this.prefix = options.prefix ?? "idem:";
11
+ this.lockPrefix = options.lockPrefix ?? "idem:lock:";
12
+ this.ttlMs = options.ttlMs ?? 864e5;
13
+ }
14
+ resultKey(key) {
15
+ return `${this.prefix}${key}`;
16
+ }
17
+ lockKey(key) {
18
+ return `${this.lockPrefix}${key}`;
19
+ }
20
+ async get(key) {
21
+ const data = await this.client.get(this.resultKey(key));
22
+ if (!data) return void 0;
23
+ try {
24
+ const result = JSON.parse(data);
25
+ if (new Date(result.expiresAt) < /* @__PURE__ */ new Date()) {
26
+ await this.delete(key);
27
+ return;
28
+ }
29
+ return {
30
+ ...result,
31
+ createdAt: new Date(result.createdAt),
32
+ expiresAt: new Date(result.expiresAt)
33
+ };
34
+ } catch {
35
+ return;
36
+ }
37
+ }
38
+ async set(key, result) {
39
+ const data = {
40
+ key,
41
+ ...result
42
+ };
43
+ const ttlSeconds = Math.ceil((new Date(result.expiresAt).getTime() - Date.now()) / 1e3);
44
+ if (ttlSeconds > 0) await this.client.set(this.resultKey(key), JSON.stringify(data), { EX: ttlSeconds });
45
+ }
46
+ async tryLock(key, requestId, ttlMs) {
47
+ const ttlSeconds = Math.ceil(ttlMs / 1e3);
48
+ return await this.client.set(this.lockKey(key), requestId, {
49
+ EX: ttlSeconds,
50
+ NX: true
51
+ }) === "OK";
52
+ }
53
+ async unlock(key, requestId) {
54
+ if (await this.client.get(this.lockKey(key)) === requestId) await this.client.del(this.lockKey(key));
55
+ }
56
+ async isLocked(key) {
57
+ return await this.client.exists(this.lockKey(key)) > 0;
58
+ }
59
+ async delete(key) {
60
+ await this.client.del([this.resultKey(key), this.lockKey(key)]);
61
+ }
62
+ async deleteByPrefix(prefix) {
63
+ const resultKeys = await this.scanByPrefix(this.resultKey(prefix));
64
+ const lockKeys = await this.scanByPrefix(this.lockKey(prefix));
65
+ const allKeys = [...resultKeys, ...lockKeys];
66
+ if (allKeys.length === 0) return 0;
67
+ return this.client.del(allKeys);
68
+ }
69
+ async findByPrefix(prefix) {
70
+ const keys = await this.scanByPrefix(this.resultKey(prefix));
71
+ for (const key of keys) {
72
+ const data = await this.client.get(key);
73
+ if (!data) continue;
74
+ try {
75
+ const result = JSON.parse(data);
76
+ if (new Date(result.expiresAt) < /* @__PURE__ */ new Date()) continue;
77
+ return {
78
+ ...result,
79
+ createdAt: new Date(result.createdAt),
80
+ expiresAt: new Date(result.expiresAt)
81
+ };
82
+ } catch {
83
+ continue;
84
+ }
85
+ }
86
+ }
87
+ /** Scan Redis keys matching a prefix pattern. Falls back to empty if SCAN unavailable. */
88
+ async scanByPrefix(prefix) {
89
+ if (!this.client.scan) return [];
90
+ const keys = [];
91
+ let cursor = "0";
92
+ do {
93
+ const [nextCursor, batch] = await this.client.scan(cursor, "MATCH", `${prefix}*`, "COUNT", 100);
94
+ cursor = nextCursor;
95
+ keys.push(...batch);
96
+ } while (String(cursor) !== "0");
97
+ return keys;
98
+ }
99
+ async close() {}
100
+ };
101
+
102
+ //#endregion
103
+ export { RedisIdempotencyStore };
104
+ //# sourceMappingURL=redis.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.mjs","names":[],"sources":["../../src/idempotency/stores/redis.ts"],"sourcesContent":["/**\n * Redis Idempotency Store\n *\n * Durable idempotency store using Redis.\n * Suitable for multi-instance deployments.\n *\n * @example\n * import { createClient } from 'redis';\n * import { RedisIdempotencyStore } from '@classytic/arc/idempotency';\n *\n * const redis = createClient({ url: process.env.REDIS_URL });\n * await redis.connect();\n *\n * await fastify.register(idempotencyPlugin, {\n * store: new RedisIdempotencyStore({ client: redis }),\n * });\n */\n\nimport type { IdempotencyStore, IdempotencyResult } from './interface.js';\n\nexport interface RedisClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, options?: { EX?: number; NX?: boolean }): Promise<string | null>;\n del(key: string | string[]): Promise<number>;\n exists(key: string | string[]): Promise<number>;\n /** SCAN command — compatible with node-redis and ioredis varargs signatures. */\n scan?(cursor: string | number, ...args: (string | number)[]): Promise<[string | number, string[]]>;\n quit?(): Promise<string>;\n disconnect?(): Promise<void>;\n}\n\nexport interface RedisIdempotencyStoreOptions {\n /** Redis client instance */\n client: RedisClient;\n /** Key prefix (default: 'idem:') */\n prefix?: string;\n /** Lock key prefix (default: 'idem:lock:') */\n lockPrefix?: string;\n /** Default TTL in ms (default: 86400000 = 24 hours) */\n ttlMs?: number;\n}\n\nexport class RedisIdempotencyStore implements IdempotencyStore {\n readonly name = 'redis';\n private client: RedisClient;\n private prefix: string;\n private lockPrefix: string;\n private ttlMs: number;\n\n constructor(options: RedisIdempotencyStoreOptions) {\n this.client = options.client;\n this.prefix = options.prefix ?? 'idem:';\n this.lockPrefix = options.lockPrefix ?? 'idem:lock:';\n this.ttlMs = options.ttlMs ?? 86400000;\n }\n\n private resultKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n private lockKey(key: string): string {\n return `${this.lockPrefix}${key}`;\n }\n\n async get(key: string): Promise<IdempotencyResult | undefined> {\n const data = await this.client.get(this.resultKey(key));\n if (!data) return undefined;\n\n try {\n const result = JSON.parse(data) as IdempotencyResult;\n // Check if expired (Redis TTL should handle this, but double-check)\n if (new Date(result.expiresAt) < new Date()) {\n await this.delete(key);\n return undefined;\n }\n return {\n ...result,\n createdAt: new Date(result.createdAt),\n expiresAt: new Date(result.expiresAt),\n };\n } catch {\n return undefined;\n }\n }\n\n async set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void> {\n const data: IdempotencyResult = { key, ...result };\n const ttlSeconds = Math.ceil(\n (new Date(result.expiresAt).getTime() - Date.now()) / 1000\n );\n\n if (ttlSeconds > 0) {\n await this.client.set(this.resultKey(key), JSON.stringify(data), {\n EX: ttlSeconds,\n });\n }\n }\n\n async tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean> {\n const ttlSeconds = Math.ceil(ttlMs / 1000);\n const result = await this.client.set(\n this.lockKey(key),\n requestId,\n { EX: ttlSeconds, NX: true }\n );\n return result === 'OK';\n }\n\n async unlock(key: string, requestId: string): Promise<void> {\n // Only unlock if we hold the lock (check requestId)\n const currentHolder = await this.client.get(this.lockKey(key));\n if (currentHolder === requestId) {\n await this.client.del(this.lockKey(key));\n }\n }\n\n async isLocked(key: string): Promise<boolean> {\n const exists = await this.client.exists(this.lockKey(key));\n return exists > 0;\n }\n\n async delete(key: string): Promise<void> {\n await this.client.del([this.resultKey(key), this.lockKey(key)]);\n }\n\n async deleteByPrefix(prefix: string): Promise<number> {\n const resultKeys = await this.scanByPrefix(this.resultKey(prefix));\n const lockKeys = await this.scanByPrefix(this.lockKey(prefix));\n const allKeys = [...resultKeys, ...lockKeys];\n if (allKeys.length === 0) return 0;\n return this.client.del(allKeys);\n }\n\n async findByPrefix(prefix: string): Promise<IdempotencyResult | undefined> {\n const keys = await this.scanByPrefix(this.resultKey(prefix));\n for (const key of keys) {\n const data = await this.client.get(key);\n if (!data) continue;\n try {\n const result = JSON.parse(data) as IdempotencyResult;\n if (new Date(result.expiresAt) < new Date()) continue;\n return {\n ...result,\n createdAt: new Date(result.createdAt),\n expiresAt: new Date(result.expiresAt),\n };\n } catch {\n continue;\n }\n }\n return undefined;\n }\n\n /** Scan Redis keys matching a prefix pattern. Falls back to empty if SCAN unavailable. */\n private async scanByPrefix(prefix: string): Promise<string[]> {\n if (!this.client.scan) return [];\n const keys: string[] = [];\n let cursor: string | number = '0';\n do {\n const [nextCursor, batch] = await this.client.scan(\n cursor, 'MATCH', `${prefix}*`, 'COUNT', 100,\n );\n cursor = nextCursor;\n keys.push(...batch);\n } while (String(cursor) !== '0');\n return keys;\n }\n\n async close(): Promise<void> {\n // Don't close the client - it's passed in and may be shared\n // The caller is responsible for closing it\n }\n}\n\nexport default RedisIdempotencyStore;\n"],"mappings":"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"}
@@ -0,0 +1,261 @@
1
+ import "./elevation-B_2dRLVP.mjs";
2
+ import { D as CrudRepository, E as defineResource, F as OperationFilter, I as PipelineConfig, L as PipelineContext, M as Guard, N as Interceptor, P as NextFunction, R as PipelineStep, S as RouteHandler, T as ResourceDefinition, _ as ControllerLike, a as RepositoryLike, b as IControllerResponse, c as BaseController, i as RelationMetadata, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, x as IRequestContext, y as IController, z as Transform } from "./interface-Ch8HU9uM.mjs";
3
+ import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-DyaDVX4J.mjs";
4
+ import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-aYB4V7uN.mjs";
5
+ import { AdditionalRoute, AnyRecord, ApiResponse, ArcInternalMetadata, AuthPluginOptions, ConfigError, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, EventDefinition, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, MiddlewareConfig, MiddlewareHandler, OwnershipCheck, PresetFunction, PresetResult, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestWithExtras, ResourceConfig, ResourceMetadata, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TypedController, TypedRepository, TypedResourceConfig, UserOrganization, ValidateOptions, ValidationResult as ValidationResult$1 } from "./types/index.mjs";
6
+ import { c as MongooseAdapterOptions, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./prisma-Dg9GoVdj.mjs";
7
+ import "./adapters/index.mjs";
8
+ import { C as MutationOperation, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, p as DEFAULT_TENANT_FIELD, s as CRUD_OPERATIONS, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "./fastifyAdapter-BkrGrlFi.mjs";
9
+ import "./core/index.mjs";
10
+ import { a as NotFoundError, d as ValidationError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-ChKiFz62.mjs";
11
+ import { a as presets_d_exports, c as readOnly, i as ownerWithAdminBypass, n as authenticated, o as publicRead, r as fullPublic, s as publicReadAdminWrite, t as adminOnly } from "./presets-DzSMwlKj.mjs";
12
+ import { DynamicPermissionMatrix, DynamicPermissionMatrixConfig, allOf, allowPublic, anyOf, createDynamicPermissionMatrix, createOrgPermissions, denyAll, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, when } from "./permissions/index.mjs";
13
+ import { AsyncLocalStorage } from "node:async_hooks";
14
+
15
+ //#region src/core/validateResourceConfig.d.ts
16
+ interface ConfigError$1 {
17
+ field: string;
18
+ message: string;
19
+ suggestion?: string;
20
+ }
21
+ interface ValidationResult$2 {
22
+ valid: boolean;
23
+ errors: ConfigError$1[];
24
+ warnings: ConfigError$1[];
25
+ }
26
+ interface ValidateOptions$1 {
27
+ /** Skip controller method validation (for testing) */
28
+ skipControllerCheck?: boolean;
29
+ /** Allow unknown preset names */
30
+ allowUnknownPresets?: boolean;
31
+ /** Custom valid permission keys beyond CRUD */
32
+ additionalPermissionKeys?: string[];
33
+ }
34
+ /**
35
+ * Validate a resource configuration
36
+ */
37
+ declare function validateResourceConfig(config: ResourceConfig, options?: ValidateOptions$1): ValidationResult$2;
38
+ /**
39
+ * Format validation errors for display
40
+ */
41
+ declare function formatValidationErrors(resourceName: string, result: ValidationResult$2): string;
42
+ /**
43
+ * Validate and throw if invalid
44
+ */
45
+ declare function assertValidConfig(config: ResourceConfig, options?: ValidateOptions$1): void;
46
+ //#endregion
47
+ //#region src/pipeline/guard.d.ts
48
+ interface GuardOptions {
49
+ operations?: OperationFilter;
50
+ handler: (ctx: PipelineContext) => boolean | Promise<boolean>;
51
+ }
52
+ /**
53
+ * Create a named guard.
54
+ *
55
+ * @param name - Guard name (for debugging/introspection)
56
+ * @param handlerOrOptions - Handler function or options object
57
+ */
58
+ declare function guard(name: string, handlerOrOptions: ((ctx: PipelineContext) => boolean | Promise<boolean>) | GuardOptions): Guard;
59
+ //#endregion
60
+ //#region src/pipeline/transform.d.ts
61
+ interface TransformOptions {
62
+ operations?: OperationFilter;
63
+ handler: (ctx: PipelineContext) => PipelineContext | void | Promise<PipelineContext | void>;
64
+ }
65
+ /**
66
+ * Create a named transform.
67
+ *
68
+ * @param name - Transform name (for debugging/introspection)
69
+ * @param handlerOrOptions - Handler function or options object
70
+ */
71
+ declare function transform(name: string, handlerOrOptions: ((ctx: PipelineContext) => PipelineContext | void | Promise<PipelineContext | void>) | TransformOptions): Transform;
72
+ //#endregion
73
+ //#region src/pipeline/intercept.d.ts
74
+ interface InterceptOptions {
75
+ operations?: OperationFilter;
76
+ handler: (ctx: PipelineContext, next: NextFunction) => Promise<IControllerResponse<unknown>>;
77
+ }
78
+ /**
79
+ * Create a named interceptor.
80
+ *
81
+ * @param name - Interceptor name (for debugging/introspection)
82
+ * @param handlerOrOptions - Handler function or options object
83
+ */
84
+ declare function intercept(name: string, handlerOrOptions: ((ctx: PipelineContext, next: NextFunction) => Promise<IControllerResponse<unknown>>) | InterceptOptions): Interceptor;
85
+ //#endregion
86
+ //#region src/pipeline/pipe.d.ts
87
+ /**
88
+ * Compose pipeline steps into an ordered array.
89
+ * Accepts guards, transforms, and interceptors in any order.
90
+ */
91
+ declare function pipe(...steps: PipelineStep[]): PipelineStep[];
92
+ //#endregion
93
+ //#region src/middleware/middleware.d.ts
94
+ interface NamedMiddleware {
95
+ /** Unique name for debugging/introspection */
96
+ readonly name: string;
97
+ /** Operations this middleware applies to (default: all) */
98
+ readonly operations?: Array<'list' | 'get' | 'create' | 'update' | 'delete' | string>;
99
+ /** Priority — lower numbers run first (default: 10) */
100
+ readonly priority: number;
101
+ /** Conditional execution — return true to run, false to skip */
102
+ readonly when?: (request: RequestWithExtras) => boolean | Promise<boolean>;
103
+ /** The middleware handler */
104
+ readonly handler: MiddlewareHandler;
105
+ }
106
+ interface MiddlewareOptions {
107
+ operations?: NamedMiddleware['operations'];
108
+ priority?: number;
109
+ when?: NamedMiddleware['when'];
110
+ handler: MiddlewareHandler;
111
+ }
112
+ /**
113
+ * Create a named middleware with priority and conditions.
114
+ */
115
+ declare function middleware(name: string, options: MiddlewareOptions): NamedMiddleware;
116
+ /**
117
+ * Sort named middlewares by priority (ascending — lower runs first).
118
+ * Returns a MiddlewareConfig map keyed by operation, ready to pass to `defineResource()`.
119
+ */
120
+ declare function sortMiddlewares(middlewares: NamedMiddleware[]): MiddlewareConfig;
121
+ //#endregion
122
+ //#region src/context/requestContext.d.ts
123
+ /**
124
+ * Shape of the request-scoped context store.
125
+ * Populated by Arc's onRequest hook in arcCorePlugin.
126
+ */
127
+ interface RequestStore {
128
+ /** Unique request identifier */
129
+ requestId?: string;
130
+ /** Authenticated user (if any) */
131
+ user?: {
132
+ id?: string;
133
+ _id?: string;
134
+ roles?: string[];
135
+ [key: string]: unknown;
136
+ } | null;
137
+ /** Active organization ID (multi-tenant) */
138
+ organizationId?: string;
139
+ /** Active team ID (team-scoped resources) */
140
+ teamId?: string;
141
+ /** Current resource name (set by arcDecorator in CRUD routes) */
142
+ resourceName?: string;
143
+ /** Request start time (for timing) */
144
+ startTime: number;
145
+ /** Additional context — extensible by app */
146
+ [key: string]: unknown;
147
+ }
148
+ /**
149
+ * Request context API.
150
+ *
151
+ * - `get()` — returns current store or undefined if outside request scope
152
+ * - `run(store, fn)` — run a function with a specific store (used by Arc internals)
153
+ * - `getStore()` — alias for get() (matches Node.js API naming)
154
+ */
155
+ declare const requestContext: {
156
+ /**
157
+ * Get the current request context.
158
+ * Returns undefined if called outside a request lifecycle.
159
+ */
160
+ get(): RequestStore | undefined;
161
+ /**
162
+ * Alias for get() — matches Node.js AsyncLocalStorage API naming.
163
+ */
164
+ getStore(): RequestStore | undefined;
165
+ /**
166
+ * Run a function within a specific request context.
167
+ * Used internally by Arc's onRequest hook.
168
+ */
169
+ run<T>(store: RequestStore, fn: () => T): T;
170
+ /**
171
+ * The underlying AsyncLocalStorage instance.
172
+ * Exposed for advanced use cases (testing, custom integrations).
173
+ */
174
+ storage: AsyncLocalStorage<RequestStore>;
175
+ };
176
+ //#endregion
177
+ //#region src/logger/index.d.ts
178
+ /**
179
+ * Arc Logger — Centralized debug & warning system
180
+ *
181
+ * Lightweight, zero-dependency logger for Arc framework internals.
182
+ * Inspired by the `debug` npm package — disabled by default, opt-in via
183
+ * environment variable or `createApp({ debug })` option.
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * // Enable via env var
188
+ * ARC_DEBUG=1 node server.js // all modules
189
+ * ARC_DEBUG=scope,elevation node server.js // specific modules
190
+ *
191
+ * // Enable via createApp
192
+ * const app = await createApp({ debug: true });
193
+ * const app = await createApp({ debug: 'scope,elevation' });
194
+ *
195
+ * // Suppress warnings (not recommended)
196
+ * ARC_SUPPRESS_WARNINGS=1 node server.js
197
+ *
198
+ * // Framework internals use:
199
+ * import { arcLog } from '../logger/index.js';
200
+ * const log = arcLog('elevation');
201
+ * log.debug('Elevation applied', { userId });
202
+ * log.warn('Something unexpected');
203
+ * ```
204
+ */
205
+ interface ArcLoggerOptions {
206
+ /**
207
+ * Enable debug output.
208
+ * - `true` or `'*'` — all modules
209
+ * - `string` — comma-separated module names (e.g., `'scope,elevation'`)
210
+ * - `false` — disabled (default)
211
+ */
212
+ debug?: boolean | string;
213
+ /**
214
+ * Custom log writer. Defaults to `console`.
215
+ * Useful for routing Arc logs into Fastify's pino logger or test fixtures.
216
+ */
217
+ writer?: ArcLogWriter;
218
+ }
219
+ interface ArcLogWriter {
220
+ debug: (...args: unknown[]) => void;
221
+ info: (...args: unknown[]) => void;
222
+ warn: (...args: unknown[]) => void;
223
+ error: (...args: unknown[]) => void;
224
+ }
225
+ interface ArcLogger {
226
+ debug: (...args: unknown[]) => void;
227
+ info: (...args: unknown[]) => void;
228
+ warn: (...args: unknown[]) => void;
229
+ error: (...args: unknown[]) => void;
230
+ }
231
+ /**
232
+ * Configure the Arc logger globally.
233
+ *
234
+ * Called automatically by `createApp({ debug })`, but can also be
235
+ * called manually for standalone usage outside of `createApp`.
236
+ */
237
+ declare function configureArcLogger(options: ArcLoggerOptions): void;
238
+ /**
239
+ * Create a module-scoped logger.
240
+ *
241
+ * Debug and info messages are gated by the `ARC_DEBUG` env var or
242
+ * `createApp({ debug })` option. Warnings always show (unless
243
+ * `ARC_SUPPRESS_WARNINGS=1`). Errors always show.
244
+ *
245
+ * @param module - Module name (e.g., 'scope', 'elevation', 'sse', 'preset')
246
+ * @returns Logger instance for that module
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const log = arcLog('elevation');
251
+ * log.debug('Checking elevation header');
252
+ * log.warn('No authenticate decorator found');
253
+ * ```
254
+ */
255
+ declare function arcLog(module: string): ArcLogger;
256
+ //#endregion
257
+ //#region src/index.d.ts
258
+ declare const version: string;
259
+ //#endregion
260
+ export { type ValidationResult as AdapterValidationResult, type AdditionalRoute, type AnyRecord, type ApiResponse, ArcError, type ArcInternalMetadata, type ArcLogWriter, type ArcLogger, type ArcLoggerOptions, type AuthPluginOptions, BaseController, type BaseControllerOptions, CRUD_OPERATIONS, type ConfigError, type ControllerLike, type CrudController, CrudOperation, type CrudRepository, type CrudRouteKey, type CrudRouterOptions, type CrudSchemas, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DataAdapter, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type EventDefinition, type FastifyRequestExtras, type FastifyWithAuth, type FastifyWithDecorators, type FieldMetadata, type FieldPermission, type FieldPermissionMap, type FieldRule, ForbiddenError, type GracefulShutdownOptions, type Guard, HOOK_OPERATIONS, HOOK_PHASES, type HealthCheck, type HealthOptions, HookOperation, HookPhase, type IController, type IControllerResponse, type IRequestContext, type InferAdapterDoc, type InferDocType, type InferResourceDoc, type Interceptor, type IntrospectionData, type IntrospectionPluginOptions, type JWTPayload, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, type MiddlewareConfig, MongooseAdapter, type MongooseAdapterOptions, MutationOperation, type NamedMiddleware, NotFoundError, type OwnershipCheck, type PaginatedResult, type PermissionCheck, type PermissionContext, type PermissionResult, type PipelineConfig, type PipelineContext, type PipelineStep, type PresetFunction, type PresetResult, PrismaAdapter, type PrismaAdapterOptions, type QueryOptions, RESERVED_QUERY_PARAMS, type RateLimitConfig, type RegistryEntry, type RegistryStats, type RelationMetadata, type RepositoryLike, type RequestContext, type RequestIdOptions, type RequestStore, type RequestWithExtras, type ResourceConfig, ResourceDefinition, type ResourceMetadata, type RouteHandler, type RouteHandlerMethod, type RouteSchemaOptions, SYSTEM_FIELDS, type SchemaMetadata, type ServiceContext, type Transform, type TypedController, type TypedRepository, type TypedResourceConfig, UnauthorizedError, type UserBase, type UserOrganization, type ValidateOptions, ValidationError, type ValidationResult$1 as ValidationResult, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, arcLog, assertValidConfig, authenticated, configureArcLogger, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, denyAll, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_d_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
261
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/validateResourceConfig.ts","../src/pipeline/guard.ts","../src/pipeline/transform.ts","../src/pipeline/intercept.ts","../src/pipeline/pipe.ts","../src/middleware/middleware.ts","../src/context/requestContext.ts","../src/logger/index.ts","../src/index.ts"],"mappings":"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"}