@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":"prisma-DJbMt3yf.mjs","names":[],"sources":["../src/adapters/types.ts","../src/adapters/mongoose.ts","../src/adapters/prisma.ts"],"sourcesContent":["/**\r\n * Type Utilities for Adapters\r\n *\r\n * Type-safe helpers for working with database adapters.\r\n * Eliminates the need for 'as any' casts in application code.\r\n */\r\n\r\nimport type { Model, Document } from 'mongoose';\r\nimport type { CrudRepository } from '../types/index.js';\r\n\r\n// ============================================================================\r\n// Type Inference Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Infer document type from Mongoose model\r\n *\r\n * @example\r\n * const ProductModel = mongoose.model('Product', productSchema);\r\n * type ProductDoc = InferMongooseDoc<typeof ProductModel>;\r\n * // Result: ProductDocument with all fields typed\r\n */\r\nexport type InferMongooseDoc<M> = M extends Model<infer D> ? D : never;\r\n\r\n/**\r\n * Infer document type from repository\r\n *\r\n * @example\r\n * const productRepo = new ProductRepository();\r\n * type ProductDoc = InferRepoDoc<typeof productRepo>;\r\n */\r\nexport type InferRepoDoc<R> = R extends CrudRepository<infer D> ? D : never;\r\n\r\n/**\r\n * Infer document type from data adapter\r\n *\r\n * @example\r\n * const adapter = createMongooseAdapter({ model, repository });\r\n * type Doc = InferAdapterDoc<typeof adapter>;\r\n */\r\nexport type InferAdapterDoc<A> = A extends { repository: CrudRepository<infer D> } ? D : never;\r\n\r\n/**\r\n * Extract clean document type (removes Mongoose-specific fields)\r\n *\r\n * @example\r\n * type CleanProduct = CleanDoc<ProductDocument>;\r\n * // Result: Product without _id, __v, save(), etc.\r\n */\r\nexport type CleanDoc<T> = T extends Document\r\n ? Omit<T, keyof Document | '_id' | '__v' | '$__' | '$isNew' | 'save' | 'remove'>\r\n : T;\r\n\r\n// ============================================================================\r\n// Adapter Constraint Types\r\n// ============================================================================\r\n\r\n/**\r\n * Ensures type is a valid Mongoose document\r\n */\r\nexport type MongooseDocument = Document & Record<string, unknown>;\r\n\r\n/**\r\n * Ensures type is a valid repository\r\n */\r\nexport type ValidRepository<TDoc> = CrudRepository<TDoc> & {\r\n getAll: CrudRepository<TDoc>['getAll'];\r\n getById: CrudRepository<TDoc>['getById'];\r\n create: CrudRepository<TDoc>['create'];\r\n update: CrudRepository<TDoc>['update'];\r\n delete: CrudRepository<TDoc>['delete'];\r\n};\r\n\r\n/**\r\n * Ensures model matches repository document type\r\n */\r\nexport type MatchingModel<TDoc> = Model<TDoc & Document>;\r\n\r\n// ============================================================================\r\n// Type Guards\r\n// ============================================================================\r\n\r\n/**\r\n * Check if value is a Mongoose model\r\n */\r\nexport function isMongooseModel(value: unknown): value is Model<Document> {\r\n return (\r\n typeof value === 'function' &&\r\n value.prototype &&\r\n 'modelName' in value &&\r\n 'schema' in value\r\n );\r\n}\r\n\r\n/**\r\n * Check if value is a repository\r\n */\r\nexport function isRepository(value: unknown): value is CrudRepository<unknown> {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n 'getAll' in value &&\r\n 'getById' in value &&\r\n 'create' in value &&\r\n 'update' in value &&\r\n 'delete' in value\r\n );\r\n}\r\n\r\n// Types are already exported at declaration\r\n// Functions (isMongooseModel, isRepository) are already exported at declaration\r\n","/**\r\n * Mongoose Adapter - Type-Safe Database Adapter\r\n *\r\n * Bridges Mongoose models with Arc's resource system.\r\n * Proper generics eliminate the need for 'as any' casts.\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport type { DataAdapter, SchemaMetadata, RepositoryLike } from './interface.js';\r\nimport type { CrudRepository, RouteSchemaOptions, OpenApiSchemas, AnyRecord } from '../types/index.js';\r\nimport { SYSTEM_FIELDS } from '../constants.js';\r\nimport { isMongooseModel, isRepository } from './types.js';\r\n\r\n/**\r\n * Mongoose SchemaType internal shape (not fully exposed by @types/mongoose)\r\n * Used to extract field metadata from schema paths\r\n */\r\ninterface MongooseSchemaType {\r\n instance: string;\r\n isRequired?: boolean;\r\n options?: {\r\n ref?: string;\r\n enum?: Array<string | number>;\r\n minlength?: number;\r\n maxlength?: number;\r\n min?: number;\r\n max?: number;\r\n [key: string]: unknown;\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\n// ============================================================================\r\n// Mongoose Adapter Options\r\n// ============================================================================\r\n\r\n/**\r\n * Options for creating a Mongoose adapter\r\n *\r\n * @typeParam TDoc - The document type (inferred or explicit)\r\n */\r\n/**\r\n * Options for creating a Mongoose adapter\r\n *\r\n * @typeParam TDoc - The document type (inferred or explicit)\r\n */\r\nexport interface MongooseAdapterOptions<TDoc = unknown> {\r\n /** Mongoose model instance — preserves document type for type safety */\r\n model: Model<TDoc>;\r\n /** Repository implementing CRUD operations - accepts any repository-like object */\r\n repository: CrudRepository<TDoc> | RepositoryLike;\r\n /**\r\n * External schema generator plugin for OpenAPI docs.\r\n * When provided, replaces the built-in basic type conversion.\r\n * Receives the Mongoose model and schema options, must return OpenApiSchemas.\r\n *\r\n * @example MongoKit integration\r\n * ```typescript\r\n * import { buildCrudSchemasFromModel } from '@classytic/mongokit';\r\n *\r\n * createMongooseAdapter({\r\n * model: JobModel,\r\n * repository: jobRepository,\r\n * schemaGenerator: (model, options) => buildCrudSchemasFromModel(model, options),\r\n * });\r\n * ```\r\n */\r\n schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas;\r\n}\r\n\r\n// ============================================================================\r\n// Mongoose Adapter\r\n// ============================================================================\r\n\r\n/**\r\n * Mongoose data adapter with proper type safety\r\n *\r\n * @typeParam TDoc - The document type\r\n */\r\nexport class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {\r\n readonly type = 'mongoose' as const;\r\n readonly name: string;\r\n readonly model: Model<TDoc>;\r\n readonly repository: CrudRepository<TDoc> | RepositoryLike;\r\n private readonly schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas;\r\n\r\n constructor(options: MongooseAdapterOptions<TDoc>) {\r\n // Runtime validation\r\n if (!isMongooseModel(options.model)) {\r\n throw new TypeError(\r\n 'MongooseAdapter: Invalid model. Expected Mongoose Model instance.\\n' +\r\n 'Usage: createMongooseAdapter({ model: YourModel, repository: yourRepo })'\r\n );\r\n }\r\n\r\n if (!isRepository(options.repository)) {\r\n throw new TypeError(\r\n 'MongooseAdapter: Invalid repository. Expected CrudRepository instance.\\n' +\r\n 'Usage: createMongooseAdapter({ model: YourModel, repository: yourRepo })'\r\n );\r\n }\r\n\r\n this.model = options.model;\r\n this.repository = options.repository;\r\n this.schemaGenerator = options.schemaGenerator;\r\n this.name = `MongooseAdapter<${options.model.modelName}>`;\r\n }\r\n\r\n /**\r\n * Get schema metadata from Mongoose model\r\n */\r\n getSchemaMetadata(): SchemaMetadata {\r\n const schema = this.model.schema;\r\n const paths = schema.paths;\r\n const fields: SchemaMetadata['fields'] = {};\r\n\r\n for (const [fieldName, schemaType] of Object.entries(paths)) {\r\n // Skip internal fields\r\n if (fieldName.startsWith('_') && fieldName !== '_id') continue;\r\n\r\n const typeInfo = schemaType as MongooseSchemaType;\r\n const mongooseType = typeInfo.instance || 'Mixed';\r\n\r\n // Map Mongoose types to our FieldMetadata types\r\n const typeMap: Record<string, 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array' | 'objectId' | 'enum'> = {\r\n String: 'string',\r\n Number: 'number',\r\n Boolean: 'boolean',\r\n Date: 'date',\r\n ObjectID: 'objectId',\r\n ObjectId: 'objectId',\r\n Array: 'array',\r\n Mixed: 'object',\r\n Buffer: 'object',\r\n Embedded: 'object',\r\n };\r\n\r\n fields[fieldName] = {\r\n type: typeMap[mongooseType] ?? 'object',\r\n required: !!typeInfo.isRequired,\r\n ref: typeInfo.options?.ref,\r\n };\r\n }\r\n\r\n return {\r\n name: this.model.modelName,\r\n fields,\r\n relations: this.extractRelations(paths),\r\n };\r\n }\r\n\r\n /**\r\n * Generate OpenAPI schemas from Mongoose model.\r\n *\r\n * If a `schemaGenerator` plugin was provided (e.g. MongoKit's buildCrudSchemasFromModel),\r\n * it is used instead of the built-in basic conversion.\r\n */\r\n generateSchemas(schemaOptions?: RouteSchemaOptions): OpenApiSchemas | null {\r\n try {\r\n // Delegate to external schema generator plugin when available\r\n if (this.schemaGenerator) {\r\n return this.schemaGenerator(this.model, schemaOptions);\r\n }\r\n\r\n // Built-in basic conversion (fallback)\r\n const schema = this.model.schema;\r\n const paths = schema.paths;\r\n const properties: AnyRecord = {};\r\n const required: string[] = [];\r\n\r\n // Extract field rules from schema options\r\n const fieldRules = schemaOptions?.fieldRules || {};\r\n const blockedFields = new Set<string>(\r\n Object.entries(fieldRules)\r\n .filter(([, rules]) => rules.systemManaged || rules.hidden)\r\n .map(([field]) => field)\r\n );\r\n\r\n for (const [fieldName, schemaType] of Object.entries(paths)) {\r\n // Skip internal and blocked fields\r\n if (fieldName.startsWith('__')) continue;\r\n if (blockedFields.has(fieldName)) continue;\r\n\r\n const typeInfo = schemaType as MongooseSchemaType;\r\n properties[fieldName] = this.mongooseTypeToOpenApi(typeInfo);\r\n\r\n if (typeInfo.isRequired) {\r\n required.push(fieldName);\r\n }\r\n }\r\n\r\n // Filter out system-managed fields for input schemas.\r\n // Uses SYSTEM_FIELDS from constants (excludes __v which is Mongoose-internal\r\n // and already absent from input schemas).\r\n const systemFieldSet = new Set<string>(SYSTEM_FIELDS);\r\n const inputProperties = Object.fromEntries(\r\n Object.entries(properties).filter(\r\n ([field]) => !systemFieldSet.has(field)\r\n )\r\n );\r\n\r\n const inputRequired = required.filter(\r\n (field) => !systemFieldSet.has(field)\r\n );\r\n\r\n return {\r\n createBody: {\r\n type: 'object',\r\n properties: inputProperties,\r\n required: inputRequired.length > 0 ? inputRequired : undefined,\r\n },\r\n updateBody: {\r\n type: 'object',\r\n properties: inputProperties,\r\n // All fields optional for PATCH\r\n },\r\n response: {\r\n type: 'object',\r\n properties,\r\n },\r\n };\r\n } catch {\r\n // Schema generation is optional - fail silently\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Extract relation metadata\r\n */\r\n private extractRelations(paths: Record<string, unknown>): SchemaMetadata['relations'] {\r\n const relations: Record<string, { type: 'one-to-one' | 'one-to-many' | 'many-to-many'; target: string; foreignKey?: string }> = {};\r\n\r\n for (const [fieldName, schemaType] of Object.entries(paths)) {\r\n const ref = (schemaType as MongooseSchemaType).options?.ref;\r\n if (ref) {\r\n relations[fieldName] = {\r\n type: 'one-to-one', // Mongoose refs are typically one-to-one\r\n target: ref,\r\n foreignKey: fieldName,\r\n };\r\n }\r\n }\r\n\r\n return Object.keys(relations).length > 0 ? relations : undefined;\r\n }\r\n\r\n /**\r\n * Convert Mongoose type to OpenAPI type\r\n */\r\n private mongooseTypeToOpenApi(typeInfo: MongooseSchemaType): AnyRecord {\r\n const instance = typeInfo.instance;\r\n const options = typeInfo.options || {};\r\n\r\n const baseType: AnyRecord = {};\r\n\r\n switch (instance) {\r\n case 'String':\r\n baseType.type = 'string';\r\n if (options.enum) baseType.enum = options.enum;\r\n if (options.minlength) baseType.minLength = options.minlength;\r\n if (options.maxlength) baseType.maxLength = options.maxlength;\r\n break;\r\n case 'Number':\r\n baseType.type = 'number';\r\n if (options.min !== undefined) baseType.minimum = options.min;\r\n if (options.max !== undefined) baseType.maximum = options.max;\r\n break;\r\n case 'Boolean':\r\n baseType.type = 'boolean';\r\n break;\r\n case 'Date':\r\n baseType.type = 'string';\r\n baseType.format = 'date-time';\r\n break;\r\n case 'ObjectID':\r\n case 'ObjectId':\r\n baseType.type = 'string';\r\n baseType.pattern = '^[a-f\\\\d]{24}$';\r\n break;\r\n case 'Array':\r\n baseType.type = 'array';\r\n baseType.items = { type: 'string' }; // Default, can be improved\r\n break;\r\n default:\r\n baseType.type = 'object';\r\n }\r\n\r\n return baseType;\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Factory Function with Type Inference\r\n// ============================================================================\r\n\r\n/**\r\n * Create Mongoose adapter with flexible type acceptance.\r\n * Accepts any repository with CRUD methods — no `as any` needed.\r\n *\r\n * @example\r\n * ```typescript\r\n * // Object form (explicit)\r\n * const adapter = createMongooseAdapter({\r\n * model: ProductModel,\r\n * repository: productRepository,\r\n * });\r\n *\r\n * // Shorthand form (2-arg) — most common path\r\n * const adapter = createMongooseAdapter(ProductModel, productRepository);\r\n * ```\r\n */\r\nexport function createMongooseAdapter<TDoc = unknown>(\r\n model: Model<TDoc>,\r\n repository: CrudRepository<TDoc> | RepositoryLike,\r\n): DataAdapter<TDoc>;\r\nexport function createMongooseAdapter<TDoc = unknown>(\r\n options: MongooseAdapterOptions<TDoc>,\r\n): DataAdapter<TDoc>;\r\nexport function createMongooseAdapter<TDoc = unknown>(\r\n modelOrOptions: Model<TDoc> | MongooseAdapterOptions<TDoc>,\r\n repository?: CrudRepository<TDoc> | RepositoryLike,\r\n): DataAdapter<TDoc> {\r\n if (isMongooseModel(modelOrOptions)) {\r\n if (!repository) {\r\n throw new TypeError(\r\n 'createMongooseAdapter: repository is required when using 2-arg form.\\n' +\r\n 'Usage: createMongooseAdapter(Model, repository)'\r\n );\r\n }\r\n return new MongooseAdapter<TDoc>({ model: modelOrOptions as Model<TDoc>, repository });\r\n }\r\n return new MongooseAdapter<TDoc>(modelOrOptions as MongooseAdapterOptions<TDoc>);\r\n}\r\n\r\n// ============================================================================\r\n// Exports\r\n// ============================================================================\r\n\r\nexport default createMongooseAdapter;\r\n","/**\n * Prisma Adapter - PostgreSQL/MySQL/SQLite Implementation\n *\n * @experimental This adapter is implemented but has no integration tests yet.\n * Use in production at your own risk. The Mongoose adapter is the recommended\n * and battle-tested path.\n *\n * Bridges Prisma Client with Arc's DataAdapter interface.\n * Supports Prisma 5+ with all database providers.\n *\n * Implemented features:\n * - Schema generation (OpenAPI docs from DMMF)\n * - Health checks (database connectivity)\n * - Query parsing (URL params → Prisma where/orderBy)\n * - Policy filter translation\n * - Soft delete preset support\n *\n * Known gaps:\n * - No integration test coverage\n * - Multi-tenant isolation relies on caller-provided policyFilters (no auto-enforcement)\n *\n * @example\n * ```typescript\n * import { PrismaClient, Prisma } from '@prisma/client';\n * import { createPrismaAdapter, PrismaQueryParser } from '@classytic/arc/adapters';\n *\n * const prisma = new PrismaClient();\n *\n * const userAdapter = createPrismaAdapter({\n * client: prisma,\n * modelName: 'user',\n * repository: new UserRepository(prisma),\n * dmmf: Prisma.dmmf, // For schema generation\n * queryParser: new PrismaQueryParser(), // Optional: custom parser\n * });\n * ```\n */\n\nimport type { DataAdapter, SchemaMetadata, FieldMetadata, ValidationResult } from './interface.js';\nimport type { CrudRepository, OpenApiSchemas, RouteSchemaOptions, ParsedQuery, QueryParserInterface, AnyRecord } from '../types/index.js';\nimport { DEFAULT_MAX_LIMIT, DEFAULT_LIMIT, RESERVED_QUERY_PARAMS } from '../constants.js';\n\n// ============================================================================\n// Prisma DMMF Types (runtime shapes from @prisma/client)\n// ============================================================================\n\n/** Prisma DMMF field shape */\ninterface DmmfField {\n name: string;\n type: string;\n kind: string;\n isList: boolean;\n isRequired: boolean;\n isUnique?: boolean;\n isId?: boolean;\n isGenerated?: boolean;\n hasDefaultValue?: boolean;\n default?: unknown;\n documentation?: string;\n relationName?: string;\n}\n\n/** Prisma DMMF enum value */\ninterface DmmfEnumValue {\n name: string;\n}\n\n/** Prisma DMMF enum */\ninterface DmmfEnum {\n name: string;\n values: DmmfEnumValue[];\n}\n\n/** Prisma DMMF model shape */\ninterface DmmfModel {\n name: string;\n fields: DmmfField[];\n uniqueIndexes?: Array<{ fields: string[] }>;\n}\n\n/** Prisma DMMF datamodel */\ninterface DmmfDatamodel {\n models: DmmfModel[];\n enums?: DmmfEnum[];\n}\n\n/** Prisma DMMF root shape */\ninterface PrismaDmmf {\n datamodel?: DmmfDatamodel;\n}\n\n/** Prisma client delegate (model accessor) */\ninterface PrismaDelegate {\n findMany(args?: unknown): Promise<unknown[]>;\n}\n\n/** Prisma client shape */\ninterface PrismaClientLike {\n $disconnect(): Promise<void>;\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Prisma Query Parser\n// ============================================================================\n\n/**\n * Options for PrismaQueryParser\n */\nexport interface PrismaQueryParserOptions {\n /** Maximum allowed limit value (default: 1000) */\n maxLimit?: number;\n /** Default limit for pagination (default: 20) */\n defaultLimit?: number;\n /** Enable soft delete filtering by default (default: true) */\n softDeleteEnabled?: boolean;\n /** Field name for soft delete (default: 'deletedAt') */\n softDeleteField?: string;\n}\n\n/**\n * Prisma Query Parser - Converts URL parameters to Prisma query format\n *\n * Translates Arc's query format to Prisma's where/orderBy/take/skip structure.\n *\n * @example\n * ```typescript\n * const parser = new PrismaQueryParser();\n *\n * // URL: ?status=active&price[gte]=100&sort=-createdAt&page=2&limit=10\n * const prismaQuery = parser.toPrismaQuery(parsedQuery);\n * // Returns:\n * // {\n * // where: { status: 'active', price: { gte: 100 }, deletedAt: null },\n * // orderBy: { createdAt: 'desc' },\n * // take: 10,\n * // skip: 10,\n * // }\n * ```\n */\nexport class PrismaQueryParser implements QueryParserInterface {\n private readonly maxLimit: number;\n private readonly defaultLimit: number;\n private readonly softDeleteEnabled: boolean;\n private readonly softDeleteField: string;\n\n /** Map Arc operators to Prisma operators */\n private readonly operatorMap: Record<string, string> = {\n $eq: 'equals',\n $ne: 'not',\n $gt: 'gt',\n $gte: 'gte',\n $lt: 'lt',\n $lte: 'lte',\n $in: 'in',\n $nin: 'notIn',\n $regex: 'contains',\n $exists: undefined as unknown as string, // Handled specially in translateFilters\n };\n\n constructor(options: PrismaQueryParserOptions = {}) {\n this.maxLimit = options.maxLimit ?? DEFAULT_MAX_LIMIT;\n this.defaultLimit = options.defaultLimit ?? DEFAULT_LIMIT;\n this.softDeleteEnabled = options.softDeleteEnabled ?? true;\n this.softDeleteField = options.softDeleteField ?? 'deletedAt';\n }\n\n /**\n * Parse URL query parameters (delegates to ArcQueryParser format)\n */\n parse(query: Record<string, unknown> | null | undefined): ParsedQuery {\n const q = query ?? {};\n\n const page = this.parseNumber(q.page, 1);\n const limit = Math.min(this.parseNumber(q.limit, this.defaultLimit), this.maxLimit);\n\n return {\n filters: this.parseFilters(q),\n limit,\n page,\n sort: this.parseSort(q.sort),\n search: q.search as string | undefined,\n select: this.parseSelect(q.select),\n };\n }\n\n /**\n * Convert ParsedQuery to Prisma query options\n */\n toPrismaQuery(parsed: ParsedQuery, policyFilters?: Record<string, unknown>): PrismaQueryOptions {\n const where: Record<string, unknown> = {};\n\n // Apply filters\n if (parsed.filters) {\n Object.assign(where, this.translateFilters(parsed.filters));\n }\n\n // Apply policy filters (multi-tenant, ownership, etc.)\n if (policyFilters) {\n Object.assign(where, this.translateFilters(policyFilters));\n }\n\n // Apply soft delete filter\n if (this.softDeleteEnabled) {\n where[this.softDeleteField] = null;\n }\n\n // Build orderBy\n const orderBy: Array<Record<string, 'asc' | 'desc'>> | undefined = parsed.sort\n ? Object.entries(parsed.sort).map(([field, dir]) => ({\n [field]: (dir === 1 ? 'asc' : 'desc') as 'asc' | 'desc',\n }))\n : undefined;\n\n // Build pagination\n const take = parsed.limit ?? this.defaultLimit;\n const skip = parsed.page ? (parsed.page - 1) * take : 0;\n\n // Build select\n const select = parsed.select\n ? Object.fromEntries(\n Object.entries(parsed.select)\n .filter(([, v]) => v === 1)\n .map(([k]) => [k, true])\n )\n : undefined;\n\n return {\n where: Object.keys(where).length > 0 ? where : undefined,\n orderBy: orderBy && orderBy.length > 0 ? orderBy : undefined,\n take,\n skip,\n select: select && Object.keys(select).length > 0 ? select : undefined,\n };\n }\n\n /**\n * Translate Arc/MongoDB-style filters to Prisma where clause\n */\n private translateFilters(filters: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [field, value] of Object.entries(filters)) {\n if (value === null || value === undefined) continue;\n\n // Handle nested operator objects: { status: { $ne: 'deleted' } }\n if (typeof value === 'object' && !Array.isArray(value)) {\n const prismaCondition: Record<string, unknown> = {};\n\n for (const [op, opValue] of Object.entries(value as Record<string, unknown>)) {\n if (op === '$exists') {\n // $exists: true → { not: null }, $exists: false → null\n result[field] = opValue ? { not: null } : null;\n continue;\n }\n\n const prismaOp = this.operatorMap[op];\n if (prismaOp) {\n prismaCondition[prismaOp] = opValue;\n }\n }\n\n if (Object.keys(prismaCondition).length > 0) {\n result[field] = prismaCondition;\n }\n } else {\n // Direct equality\n result[field] = value;\n }\n }\n\n return result;\n }\n\n private parseNumber(value: unknown, defaultValue: number): number {\n if (value === undefined || value === null) return defaultValue;\n const num = parseInt(String(value), 10);\n return Number.isNaN(num) ? defaultValue : Math.max(1, num);\n }\n\n private parseSort(value: unknown): Record<string, 1 | -1> | undefined {\n if (!value) return undefined;\n\n const sortStr = String(value);\n const result: Record<string, 1 | -1> = {};\n\n for (const field of sortStr.split(',')) {\n const trimmed = field.trim();\n if (!trimmed || !/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;\n\n if (trimmed.startsWith('-')) {\n result[trimmed.slice(1)] = -1;\n } else {\n result[trimmed] = 1;\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n\n private parseSelect(value: unknown): Record<string, 0 | 1> | undefined {\n if (!value) return undefined;\n\n const result: Record<string, 0 | 1> = {};\n\n for (const field of String(value).split(',')) {\n const trimmed = field.trim();\n if (!trimmed || !/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;\n\n result[trimmed.startsWith('-') ? trimmed.slice(1) : trimmed] = trimmed.startsWith('-') ? 0 : 1;\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n\n private parseFilters(query: Record<string, unknown>): Record<string, unknown> {\n const filters: Record<string, unknown> = {};\n\n const operators: Record<string, string> = {\n eq: '$eq', ne: '$ne', gt: '$gt', gte: '$gte',\n lt: '$lt', lte: '$lte', in: '$in', nin: '$nin',\n like: '$regex', contains: '$regex', exists: '$exists',\n };\n\n for (const [key, value] of Object.entries(query)) {\n if (RESERVED_QUERY_PARAMS.has(key) || value === undefined || value === null) continue;\n\n const match = key.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)(?:\\[([a-z]+)\\])?$/);\n if (!match) continue;\n\n const [, fieldName, operator] = match;\n if (!fieldName) continue;\n\n if (operator && operators[operator]) {\n if (!filters[fieldName]) filters[fieldName] = {};\n (filters[fieldName] as Record<string, unknown>)[operators[operator]] = this.coerceValue(value, operator);\n } else if (!operator) {\n filters[fieldName] = this.coerceValue(value);\n }\n }\n\n return filters;\n }\n\n private coerceValue(value: unknown, operator?: string): unknown {\n if (operator === 'in' || operator === 'nin') {\n if (Array.isArray(value)) return value.map(v => this.coerceValue(v));\n if (typeof value === 'string' && value.includes(',')) {\n return value.split(',').map(v => this.coerceValue(v.trim()));\n }\n return [this.coerceValue(value)];\n }\n\n if (operator === 'exists') {\n return String(value).toLowerCase() === 'true' || value === '1';\n }\n\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null') return null;\n\n if (typeof value === 'string') {\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== '') return num;\n }\n\n return value;\n }\n}\n\n/**\n * Prisma query options returned by toPrismaQuery\n */\nexport interface PrismaQueryOptions {\n where?: Record<string, unknown>;\n orderBy?: Array<Record<string, 'asc' | 'desc'>>;\n take?: number;\n skip?: number;\n select?: Record<string, boolean>;\n include?: Record<string, boolean>;\n}\n\n// ============================================================================\n// Prisma Adapter Options\n// ============================================================================\n\nexport interface PrismaAdapterOptions<TModel> {\n /** Prisma client instance */\n client: PrismaClientLike;\n /** Model name (e.g., 'user', 'product') */\n modelName: string;\n /** Repository instance implementing CRUD operations */\n repository: CrudRepository<TModel>;\n /** Optional: Prisma DMMF (Data Model Meta Format) for schema extraction */\n dmmf?: PrismaDmmf;\n /** Optional: Custom query parser (default: PrismaQueryParser) */\n queryParser?: PrismaQueryParser;\n /** Enable soft delete filtering (default: true) */\n softDeleteEnabled?: boolean;\n /** Field name for soft delete (default: 'deletedAt') */\n softDeleteField?: string;\n}\n\nexport class PrismaAdapter<TModel = unknown> implements DataAdapter<TModel> {\n readonly type = 'prisma' as const;\n readonly name: string;\n readonly repository: CrudRepository<TModel>;\n readonly queryParser: PrismaQueryParser;\n\n private client: PrismaClientLike;\n private modelName: string;\n private dmmf?: PrismaDmmf;\n private softDeleteEnabled: boolean;\n private softDeleteField: string;\n\n constructor(options: PrismaAdapterOptions<TModel>) {\n this.client = options.client;\n this.modelName = options.modelName;\n this.repository = options.repository;\n this.dmmf = options.dmmf;\n this.name = `prisma:${options.modelName}`;\n this.softDeleteEnabled = options.softDeleteEnabled ?? true;\n this.softDeleteField = options.softDeleteField ?? 'deletedAt';\n\n // Initialize query parser\n this.queryParser = options.queryParser ?? new PrismaQueryParser({\n softDeleteEnabled: this.softDeleteEnabled,\n softDeleteField: this.softDeleteField,\n });\n }\n\n /**\n * Parse URL query parameters and convert to Prisma query options\n */\n parseQuery(query: Record<string, unknown>, policyFilters?: Record<string, unknown>): PrismaQueryOptions {\n const parsed = this.queryParser.parse(query);\n return this.queryParser.toPrismaQuery(parsed, policyFilters);\n }\n\n /**\n * Apply policy filters to existing Prisma where clause\n * Used for multi-tenant, ownership, and other security filters\n */\n applyPolicyFilters(\n where: Record<string, unknown>,\n policyFilters: Record<string, unknown>\n ): Record<string, unknown> {\n return { ...where, ...policyFilters };\n }\n\n generateSchemas(options?: RouteSchemaOptions): OpenApiSchemas | null {\n // Extract schema from Prisma DMMF if available\n if (!this.dmmf) return null;\n\n try {\n const model = this.dmmf.datamodel?.models?.find(\n (m: DmmfModel) => m.name.toLowerCase() === this.modelName.toLowerCase()\n );\n\n if (!model) return null;\n\n const entitySchema = this.buildEntitySchema(model, options);\n const createBodySchema = this.buildCreateSchema(model, options);\n const updateBodySchema = this.buildUpdateSchema(model, options);\n\n return {\n entity: entitySchema,\n createBody: createBodySchema,\n updateBody: updateBodySchema,\n params: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n },\n required: ['id'],\n },\n listQuery: {\n type: 'object',\n properties: {\n page: { type: 'number', minimum: 1, description: 'Page number for pagination' },\n limit: { type: 'number', minimum: 1, maximum: 100, description: 'Items per page' },\n sort: { type: 'string', description: 'Sort field (e.g., \"name\", \"-createdAt\")' },\n // Note: Actual filtering requires custom query parser implementation\n // This is placeholder documentation only\n },\n },\n };\n } catch {\n // Schema generation is optional - fail silently\n return null;\n }\n }\n\n getSchemaMetadata(): SchemaMetadata | null {\n if (!this.dmmf) return null;\n\n try {\n const model = this.dmmf.datamodel?.models?.find(\n (m: DmmfModel) => m.name.toLowerCase() === this.modelName.toLowerCase()\n );\n\n if (!model) return null;\n\n const fields: Record<string, FieldMetadata> = {};\n\n for (const field of model.fields) {\n fields[field.name] = this.convertPrismaFieldToMetadata(field);\n }\n\n return {\n name: model.name,\n fields,\n indexes: model.uniqueIndexes?.map((idx: { fields: string[] }) => ({\n fields: idx.fields,\n unique: true,\n })),\n };\n } catch (err) {\n return null;\n }\n }\n\n async validate(data: unknown): Promise<ValidationResult> {\n // Prisma validates on write, so we do basic type checking here\n if (!data || typeof data !== 'object') {\n return {\n valid: false,\n errors: [{ field: 'root', message: 'Data must be an object' }],\n };\n }\n\n // Get required fields from DMMF\n if (this.dmmf) {\n try {\n const model = this.dmmf.datamodel?.models?.find(\n (m: DmmfModel) => m.name.toLowerCase() === this.modelName.toLowerCase()\n );\n\n if (model) {\n const requiredFields = model.fields.filter(\n (f: DmmfField) => f.isRequired && !f.hasDefaultValue && !f.isGenerated\n );\n\n const errors: Array<{ field: string; message: string }> = [];\n\n for (const field of requiredFields) {\n if (!(field.name in (data as Record<string, unknown>))) {\n errors.push({\n field: field.name,\n message: `${field.name} is required`,\n });\n }\n }\n\n if (errors.length > 0) {\n return { valid: false, errors };\n }\n }\n } catch (err) {\n // Validation failed, but we'll let Prisma handle it on write\n }\n }\n\n return { valid: true };\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n // Use findMany with take: 1 for database-agnostic health check\n // This works across all Prisma providers (SQL, MongoDB, etc.)\n // Prisma client delegates use camelCase (e.g., prisma.userProfile, not prisma.UserProfile)\n const delegateName = this.modelName.charAt(0).toLowerCase() + this.modelName.slice(1);\n const delegate = this.client[delegateName] as PrismaDelegate | undefined;\n if (!delegate) {\n return false;\n }\n await delegate.findMany({ take: 1 });\n return true;\n } catch (err) {\n return false;\n }\n }\n\n async close(): Promise<void> {\n try {\n await this.client.$disconnect();\n } catch (err) {\n // Already disconnected or error - ignore\n }\n }\n\n // ============================================================================\n // Private Helper Methods\n // ============================================================================\n\n private buildEntitySchema(model: DmmfModel, options?: RouteSchemaOptions): AnyRecord {\n const properties: Record<string, AnyRecord> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n // Skip internal fields unless explicitly included\n if (this.shouldSkipField(field, options)) continue;\n\n properties[field.name] = this.convertPrismaFieldToJsonSchema(field);\n\n if (field.isRequired && !field.hasDefaultValue) {\n required.push(field.name);\n }\n }\n\n return {\n type: 'object',\n properties,\n ...(required.length > 0 && { required }),\n };\n }\n\n private buildCreateSchema(model: DmmfModel, options?: RouteSchemaOptions): AnyRecord {\n const properties: Record<string, AnyRecord> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n // Skip auto-generated and relation fields for create\n if (field.isGenerated || field.relationName) continue;\n if (this.shouldSkipField(field, options)) continue;\n\n properties[field.name] = this.convertPrismaFieldToJsonSchema(field);\n\n if (field.isRequired && !field.hasDefaultValue) {\n required.push(field.name);\n }\n }\n\n return {\n type: 'object',\n properties,\n ...(required.length > 0 && { required }),\n };\n }\n\n private buildUpdateSchema(model: DmmfModel, options?: RouteSchemaOptions): AnyRecord {\n const properties: Record<string, AnyRecord> = {};\n\n for (const field of model.fields) {\n // Skip auto-generated, ID, and relation fields for update\n if (field.isGenerated || field.isId || field.relationName) continue;\n if (this.shouldSkipField(field, options)) continue;\n\n properties[field.name] = this.convertPrismaFieldToJsonSchema(field);\n }\n\n return {\n type: 'object',\n properties,\n };\n }\n\n private shouldSkipField(field: DmmfField, options?: RouteSchemaOptions): boolean {\n // Check if field is in excludeFields\n if (options?.excludeFields?.includes(field.name)) {\n return true;\n }\n\n // Skip internal Prisma fields\n if (field.name.startsWith('_')) {\n return true;\n }\n\n return false;\n }\n\n private convertPrismaFieldToJsonSchema(field: DmmfField): AnyRecord {\n const schema: AnyRecord = {};\n\n // Map Prisma types to JSON Schema types\n switch (field.type) {\n case 'String':\n schema.type = 'string';\n break;\n case 'Int':\n case 'BigInt':\n schema.type = 'integer';\n break;\n case 'Float':\n case 'Decimal':\n schema.type = 'number';\n break;\n case 'Boolean':\n schema.type = 'boolean';\n break;\n case 'DateTime':\n schema.type = 'string';\n schema.format = 'date-time';\n break;\n case 'Json':\n schema.type = 'object';\n break;\n default:\n // Enums and other types\n if (field.kind === 'enum') {\n schema.type = 'string';\n // Extract enum values from DMMF if available\n if (this.dmmf?.datamodel?.enums) {\n const enumDef = this.dmmf.datamodel.enums.find((e: DmmfEnum) => e.name === field.type);\n if (enumDef) {\n schema.enum = enumDef.values.map((v: DmmfEnumValue) => v.name);\n }\n }\n } else {\n schema.type = 'string';\n }\n }\n\n // Handle arrays\n if (field.isList) {\n return {\n type: 'array',\n items: schema,\n };\n }\n\n // Add description if available\n if (field.documentation) {\n schema.description = field.documentation;\n }\n\n return schema;\n }\n\n private convertPrismaFieldToMetadata(field: DmmfField): FieldMetadata {\n const metadata: FieldMetadata = {\n type: this.mapPrismaTypeToMetadataType(field.type, field.kind),\n required: field.isRequired,\n array: field.isList,\n };\n\n if (field.isUnique) {\n metadata.unique = true;\n }\n\n if (field.hasDefaultValue) {\n metadata.default = field.default;\n }\n\n if (field.documentation) {\n metadata.description = field.documentation;\n }\n\n if (field.relationName) {\n metadata.ref = field.type;\n }\n\n return metadata;\n }\n\n private mapPrismaTypeToMetadataType(\n type: string,\n kind: string\n ): FieldMetadata['type'] {\n if (kind === 'enum') return 'enum';\n\n switch (type) {\n case 'String':\n return 'string';\n case 'Int':\n case 'BigInt':\n case 'Float':\n case 'Decimal':\n return 'number';\n case 'Boolean':\n return 'boolean';\n case 'DateTime':\n return 'date';\n case 'Json':\n return 'object';\n default:\n return 'string';\n }\n }\n}\n\n/**\n * Factory function to create Prisma adapter\n *\n * @example\n * import { PrismaClient } from '@prisma/client';\n * import { createPrismaAdapter } from '@classytic/arc';\n *\n * const prisma = new PrismaClient();\n *\n * const userAdapter = createPrismaAdapter({\n * client: prisma,\n * modelName: 'user',\n * repository: userRepository,\n * dmmf: Prisma.dmmf, // Optional: for schema generation\n * });\n */\nexport function createPrismaAdapter<TModel>(\n options: PrismaAdapterOptions<TModel>\n): PrismaAdapter<TModel> {\n return new PrismaAdapter(options);\n}\n"],"mappings":"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"}
@@ -0,0 +1,275 @@
1
+ import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-Ch8HU9uM.mjs";
2
+ import { OpenApiSchemas, ParsedQuery, QueryParserInterface, RouteSchemaOptions } from "./types/index.mjs";
3
+ import { Model } from "mongoose";
4
+
5
+ //#region src/adapters/mongoose.d.ts
6
+ /**
7
+ * Options for creating a Mongoose adapter
8
+ *
9
+ * @typeParam TDoc - The document type (inferred or explicit)
10
+ */
11
+ /**
12
+ * Options for creating a Mongoose adapter
13
+ *
14
+ * @typeParam TDoc - The document type (inferred or explicit)
15
+ */
16
+ interface MongooseAdapterOptions<TDoc = unknown> {
17
+ /** Mongoose model instance — preserves document type for type safety */
18
+ model: Model<TDoc>;
19
+ /** Repository implementing CRUD operations - accepts any repository-like object */
20
+ repository: CrudRepository<TDoc> | RepositoryLike;
21
+ /**
22
+ * External schema generator plugin for OpenAPI docs.
23
+ * When provided, replaces the built-in basic type conversion.
24
+ * Receives the Mongoose model and schema options, must return OpenApiSchemas.
25
+ *
26
+ * @example MongoKit integration
27
+ * ```typescript
28
+ * import { buildCrudSchemasFromModel } from '@classytic/mongokit';
29
+ *
30
+ * createMongooseAdapter({
31
+ * model: JobModel,
32
+ * repository: jobRepository,
33
+ * schemaGenerator: (model, options) => buildCrudSchemasFromModel(model, options),
34
+ * });
35
+ * ```
36
+ */
37
+ schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas;
38
+ }
39
+ /**
40
+ * Mongoose data adapter with proper type safety
41
+ *
42
+ * @typeParam TDoc - The document type
43
+ */
44
+ declare class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {
45
+ readonly type: "mongoose";
46
+ readonly name: string;
47
+ readonly model: Model<TDoc>;
48
+ readonly repository: CrudRepository<TDoc> | RepositoryLike;
49
+ private readonly schemaGenerator?;
50
+ constructor(options: MongooseAdapterOptions<TDoc>);
51
+ /**
52
+ * Get schema metadata from Mongoose model
53
+ */
54
+ getSchemaMetadata(): SchemaMetadata;
55
+ /**
56
+ * Generate OpenAPI schemas from Mongoose model.
57
+ *
58
+ * If a `schemaGenerator` plugin was provided (e.g. MongoKit's buildCrudSchemasFromModel),
59
+ * it is used instead of the built-in basic conversion.
60
+ */
61
+ generateSchemas(schemaOptions?: RouteSchemaOptions): OpenApiSchemas | null;
62
+ /**
63
+ * Extract relation metadata
64
+ */
65
+ private extractRelations;
66
+ /**
67
+ * Convert Mongoose type to OpenAPI type
68
+ */
69
+ private mongooseTypeToOpenApi;
70
+ }
71
+ /**
72
+ * Create Mongoose adapter with flexible type acceptance.
73
+ * Accepts any repository with CRUD methods — no `as any` needed.
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * // Object form (explicit)
78
+ * const adapter = createMongooseAdapter({
79
+ * model: ProductModel,
80
+ * repository: productRepository,
81
+ * });
82
+ *
83
+ * // Shorthand form (2-arg) — most common path
84
+ * const adapter = createMongooseAdapter(ProductModel, productRepository);
85
+ * ```
86
+ */
87
+ declare function createMongooseAdapter<TDoc = unknown>(model: Model<TDoc>, repository: CrudRepository<TDoc> | RepositoryLike): DataAdapter<TDoc>;
88
+ declare function createMongooseAdapter<TDoc = unknown>(options: MongooseAdapterOptions<TDoc>): DataAdapter<TDoc>;
89
+ //#endregion
90
+ //#region src/adapters/prisma.d.ts
91
+ /** Prisma DMMF field shape */
92
+ interface DmmfField {
93
+ name: string;
94
+ type: string;
95
+ kind: string;
96
+ isList: boolean;
97
+ isRequired: boolean;
98
+ isUnique?: boolean;
99
+ isId?: boolean;
100
+ isGenerated?: boolean;
101
+ hasDefaultValue?: boolean;
102
+ default?: unknown;
103
+ documentation?: string;
104
+ relationName?: string;
105
+ }
106
+ /** Prisma DMMF enum value */
107
+ interface DmmfEnumValue {
108
+ name: string;
109
+ }
110
+ /** Prisma DMMF enum */
111
+ interface DmmfEnum {
112
+ name: string;
113
+ values: DmmfEnumValue[];
114
+ }
115
+ /** Prisma DMMF model shape */
116
+ interface DmmfModel {
117
+ name: string;
118
+ fields: DmmfField[];
119
+ uniqueIndexes?: Array<{
120
+ fields: string[];
121
+ }>;
122
+ }
123
+ /** Prisma DMMF datamodel */
124
+ interface DmmfDatamodel {
125
+ models: DmmfModel[];
126
+ enums?: DmmfEnum[];
127
+ }
128
+ /** Prisma DMMF root shape */
129
+ interface PrismaDmmf {
130
+ datamodel?: DmmfDatamodel;
131
+ }
132
+ /** Prisma client shape */
133
+ interface PrismaClientLike {
134
+ $disconnect(): Promise<void>;
135
+ [key: string]: unknown;
136
+ }
137
+ /**
138
+ * Options for PrismaQueryParser
139
+ */
140
+ interface PrismaQueryParserOptions {
141
+ /** Maximum allowed limit value (default: 1000) */
142
+ maxLimit?: number;
143
+ /** Default limit for pagination (default: 20) */
144
+ defaultLimit?: number;
145
+ /** Enable soft delete filtering by default (default: true) */
146
+ softDeleteEnabled?: boolean;
147
+ /** Field name for soft delete (default: 'deletedAt') */
148
+ softDeleteField?: string;
149
+ }
150
+ /**
151
+ * Prisma Query Parser - Converts URL parameters to Prisma query format
152
+ *
153
+ * Translates Arc's query format to Prisma's where/orderBy/take/skip structure.
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const parser = new PrismaQueryParser();
158
+ *
159
+ * // URL: ?status=active&price[gte]=100&sort=-createdAt&page=2&limit=10
160
+ * const prismaQuery = parser.toPrismaQuery(parsedQuery);
161
+ * // Returns:
162
+ * // {
163
+ * // where: { status: 'active', price: { gte: 100 }, deletedAt: null },
164
+ * // orderBy: { createdAt: 'desc' },
165
+ * // take: 10,
166
+ * // skip: 10,
167
+ * // }
168
+ * ```
169
+ */
170
+ declare class PrismaQueryParser implements QueryParserInterface {
171
+ private readonly maxLimit;
172
+ private readonly defaultLimit;
173
+ private readonly softDeleteEnabled;
174
+ private readonly softDeleteField;
175
+ /** Map Arc operators to Prisma operators */
176
+ private readonly operatorMap;
177
+ constructor(options?: PrismaQueryParserOptions);
178
+ /**
179
+ * Parse URL query parameters (delegates to ArcQueryParser format)
180
+ */
181
+ parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
182
+ /**
183
+ * Convert ParsedQuery to Prisma query options
184
+ */
185
+ toPrismaQuery(parsed: ParsedQuery, policyFilters?: Record<string, unknown>): PrismaQueryOptions;
186
+ /**
187
+ * Translate Arc/MongoDB-style filters to Prisma where clause
188
+ */
189
+ private translateFilters;
190
+ private parseNumber;
191
+ private parseSort;
192
+ private parseSelect;
193
+ private parseFilters;
194
+ private coerceValue;
195
+ }
196
+ /**
197
+ * Prisma query options returned by toPrismaQuery
198
+ */
199
+ interface PrismaQueryOptions {
200
+ where?: Record<string, unknown>;
201
+ orderBy?: Array<Record<string, 'asc' | 'desc'>>;
202
+ take?: number;
203
+ skip?: number;
204
+ select?: Record<string, boolean>;
205
+ include?: Record<string, boolean>;
206
+ }
207
+ interface PrismaAdapterOptions<TModel> {
208
+ /** Prisma client instance */
209
+ client: PrismaClientLike;
210
+ /** Model name (e.g., 'user', 'product') */
211
+ modelName: string;
212
+ /** Repository instance implementing CRUD operations */
213
+ repository: CrudRepository<TModel>;
214
+ /** Optional: Prisma DMMF (Data Model Meta Format) for schema extraction */
215
+ dmmf?: PrismaDmmf;
216
+ /** Optional: Custom query parser (default: PrismaQueryParser) */
217
+ queryParser?: PrismaQueryParser;
218
+ /** Enable soft delete filtering (default: true) */
219
+ softDeleteEnabled?: boolean;
220
+ /** Field name for soft delete (default: 'deletedAt') */
221
+ softDeleteField?: string;
222
+ }
223
+ declare class PrismaAdapter<TModel = unknown> implements DataAdapter<TModel> {
224
+ readonly type: "prisma";
225
+ readonly name: string;
226
+ readonly repository: CrudRepository<TModel>;
227
+ readonly queryParser: PrismaQueryParser;
228
+ private client;
229
+ private modelName;
230
+ private dmmf?;
231
+ private softDeleteEnabled;
232
+ private softDeleteField;
233
+ constructor(options: PrismaAdapterOptions<TModel>);
234
+ /**
235
+ * Parse URL query parameters and convert to Prisma query options
236
+ */
237
+ parseQuery(query: Record<string, unknown>, policyFilters?: Record<string, unknown>): PrismaQueryOptions;
238
+ /**
239
+ * Apply policy filters to existing Prisma where clause
240
+ * Used for multi-tenant, ownership, and other security filters
241
+ */
242
+ applyPolicyFilters(where: Record<string, unknown>, policyFilters: Record<string, unknown>): Record<string, unknown>;
243
+ generateSchemas(options?: RouteSchemaOptions): OpenApiSchemas | null;
244
+ getSchemaMetadata(): SchemaMetadata | null;
245
+ validate(data: unknown): Promise<ValidationResult>;
246
+ healthCheck(): Promise<boolean>;
247
+ close(): Promise<void>;
248
+ private buildEntitySchema;
249
+ private buildCreateSchema;
250
+ private buildUpdateSchema;
251
+ private shouldSkipField;
252
+ private convertPrismaFieldToJsonSchema;
253
+ private convertPrismaFieldToMetadata;
254
+ private mapPrismaTypeToMetadataType;
255
+ }
256
+ /**
257
+ * Factory function to create Prisma adapter
258
+ *
259
+ * @example
260
+ * import { PrismaClient } from '@prisma/client';
261
+ * import { createPrismaAdapter } from '@classytic/arc';
262
+ *
263
+ * const prisma = new PrismaClient();
264
+ *
265
+ * const userAdapter = createPrismaAdapter({
266
+ * client: prisma,
267
+ * modelName: 'user',
268
+ * repository: userRepository,
269
+ * dmmf: Prisma.dmmf, // Optional: for schema generation
270
+ * });
271
+ */
272
+ declare function createPrismaAdapter<TModel>(options: PrismaAdapterOptions<TModel>): PrismaAdapter<TModel>;
273
+ //#endregion
274
+ export { PrismaQueryParserOptions as a, MongooseAdapterOptions as c, PrismaQueryParser as i, createMongooseAdapter as l, PrismaAdapterOptions as n, createPrismaAdapter as o, PrismaQueryOptions as r, MongooseAdapter as s, PrismaAdapter as t };
275
+ //# sourceMappingURL=prisma-Dg9GoVdj.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-Dg9GoVdj.d.mts","names":[],"sources":["../src/adapters/mongoose.ts","../src/adapters/prisma.ts"],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { i as CacheStore } from "./interface-CZe8IkMf.mjs";
2
+ import { FastifyPluginAsync } from "fastify";
3
+
4
+ //#region src/cache/QueryCache.d.ts
5
+ /** Metadata wrapper stored in CacheStore */
6
+ interface CacheEnvelope<T = unknown> {
7
+ data: T;
8
+ createdAt: number;
9
+ staleAfter: number;
10
+ expiresAt: number;
11
+ tags: string[];
12
+ }
13
+ interface QueryCacheConfig {
14
+ /** Seconds data is "fresh" (no revalidation). Default: 0 */
15
+ staleTime?: number;
16
+ /** Seconds stale data stays cached (SWR window). Default: 60 */
17
+ gcTime?: number;
18
+ /** Tags for group invalidation */
19
+ tags?: string[];
20
+ }
21
+ type CacheStatus = 'fresh' | 'stale' | 'miss';
22
+ interface CacheResult<T> {
23
+ data: T;
24
+ status: CacheStatus;
25
+ }
26
+ declare class QueryCache {
27
+ private readonly store;
28
+ constructor(store: CacheStore);
29
+ get<T>(key: string): Promise<CacheResult<T>>;
30
+ set<T>(key: string, data: T, config: QueryCacheConfig): Promise<void>;
31
+ invalidate(key: string): Promise<void>;
32
+ /** Get current version for a resource (defaults to 0 if not set) */
33
+ getResourceVersion(resource: string): Promise<number>;
34
+ /** Bump resource version — orphans all cached queries for this resource */
35
+ bumpResourceVersion(resource: string): Promise<void>;
36
+ /** Get current version for a tag */
37
+ getTagVersion(tag: string): Promise<number>;
38
+ /** Bump tag version — orphans all cached queries tagged with this tag */
39
+ bumpTagVersion(tag: string): Promise<void>;
40
+ }
41
+ //#endregion
42
+ //#region src/cache/queryCachePlugin.d.ts
43
+ interface QueryCachePluginOptions {
44
+ /** CacheStore instance. Default: MemoryCacheStore with default options. */
45
+ store?: CacheStore;
46
+ /** Global defaults for staleTime/gcTime (seconds) */
47
+ defaults?: {
48
+ staleTime?: number;
49
+ gcTime?: number;
50
+ };
51
+ }
52
+ interface QueryCacheDefaults {
53
+ staleTime: number;
54
+ gcTime: number;
55
+ }
56
+ /** Cross-resource invalidation rules collected from resource configs */
57
+ interface CrossResourceRule {
58
+ pattern: string;
59
+ tags: string[];
60
+ }
61
+ declare module 'fastify' {
62
+ interface FastifyInstance {
63
+ queryCache: QueryCache;
64
+ queryCacheConfig: QueryCacheDefaults;
65
+ /** Register cross-resource invalidation rules (called by defineResource) */
66
+ registerCacheInvalidationRule?(rule: CrossResourceRule): void;
67
+ }
68
+ }
69
+ declare const queryCachePlugin: FastifyPluginAsync<QueryCachePluginOptions>;
70
+ //#endregion
71
+ export { CacheEnvelope as a, QueryCache as c, queryCachePlugin as i, QueryCacheConfig as l, QueryCacheDefaults as n, CacheResult as o, QueryCachePluginOptions as r, CacheStatus as s, CrossResourceRule as t };
72
+ //# sourceMappingURL=queryCachePlugin-7THaI5mt.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryCachePlugin-7THaI5mt.d.mts","names":[],"sources":["../src/cache/QueryCache.ts","../src/cache/queryCachePlugin.ts"],"mappings":";;;;;UAciB,aAAA;EACf,IAAA,EAAM,CAAA;EACN,SAAA;EACA,UAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,UAGe,gBAAA;;EAEf,SAAA;EAAA;EAEA,MAAA;EAEA;EAAA,IAAA;AAAA;AAAA,KAGU,WAAA;AAAA,UAEK,WAAA;EACf,IAAA,EAAM,CAAA;EACN,MAAA,EAAQ,WAAA;AAAA;AAAA,cAGG,UAAA;EAAA,iBACM,KAAA;cAEL,KAAA,EAAO,UAAA;EAIb,GAAA,GAAA,CAAO,GAAA,WAAc,OAAA,CAAQ,WAAA,CAAY,CAAA;EAqBzC,GAAA,GAAA,CAAO,GAAA,UAAa,IAAA,EAAM,CAAA,EAAG,MAAA,EAAQ,gBAAA,GAAmB,OAAA;EAiBxD,UAAA,CAAW,GAAA,WAAc,OAAA;EAjDzB;EAsDA,kBAAA,CAAmB,QAAA,WAAmB,OAAA;EArDpC;EA2DF,mBAAA,CAAoB,QAAA,WAAmB,OAAA;EA3D1B;EAmEb,aAAA,CAAc,GAAA,WAAc,OAAA;EAhEb;EAsEf,cAAA,CAAe,GAAA,WAAc,OAAA;AAAA;;;UClFpB,uBAAA;EDAf;ECEA,KAAA,GAAQ,UAAA;EDAJ;ECEJ,QAAA;IACE,SAAA;IACA,MAAA;EAAA;AAAA;AAAA,UAIa,kBAAA;EACf,SAAA;EACA,MAAA;AAAA;;UAIe,iBAAA;EACf,OAAA;EACA,IAAA;AAAA;AAAA;EAAA,UAIU,eAAA;IACR,UAAA,EAAY,UAAA;IACZ,gBAAA,EAAkB,kBAAA;IDZC;ICcnB,6BAAA,EAA+B,IAAA,EAAM,iBAAA;EAAA;AAAA;AAAA,cA8D5B,gBAAA,EAAgB,kBAAA,CAAA,uBAAA"}
@@ -0,0 +1,139 @@
1
+ import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
2
+ import { i as versionKey, r as tagVersionKey } from "./keys-BqNejWup.mjs";
3
+ import { t as hasEvents } from "./typeGuards-DhMNLuvU.mjs";
4
+ import { t as MemoryCacheStore } from "./memory-cQgelFOj.mjs";
5
+ import fp from "fastify-plugin";
6
+
7
+ //#region src/cache/QueryCache.ts
8
+ var QueryCache = class {
9
+ store;
10
+ constructor(store) {
11
+ this.store = store;
12
+ }
13
+ async get(key) {
14
+ const envelope = await this.store.get(key);
15
+ if (!envelope || !envelope.createdAt) return {
16
+ data: void 0,
17
+ status: "miss"
18
+ };
19
+ const now = Date.now();
20
+ if (now >= envelope.expiresAt) {
21
+ await this.store.delete(key);
22
+ return {
23
+ data: void 0,
24
+ status: "miss"
25
+ };
26
+ }
27
+ if (now < envelope.staleAfter) return {
28
+ data: envelope.data,
29
+ status: "fresh"
30
+ };
31
+ return {
32
+ data: envelope.data,
33
+ status: "stale"
34
+ };
35
+ }
36
+ async set(key, data, config) {
37
+ const staleTimeMs = (config.staleTime ?? 0) * 1e3;
38
+ const totalTtl = staleTimeMs + (config.gcTime ?? 60) * 1e3;
39
+ const now = Date.now();
40
+ const envelope = {
41
+ data,
42
+ createdAt: now,
43
+ staleAfter: now + staleTimeMs,
44
+ expiresAt: now + totalTtl,
45
+ tags: config.tags ?? []
46
+ };
47
+ await this.store.set(key, envelope, { ttlMs: totalTtl });
48
+ }
49
+ async invalidate(key) {
50
+ await this.store.delete(key);
51
+ }
52
+ /** Get current version for a resource (defaults to 0 if not set) */
53
+ async getResourceVersion(resource) {
54
+ return await this.store.get(versionKey(resource)) ?? 0;
55
+ }
56
+ /** Bump resource version — orphans all cached queries for this resource */
57
+ async bumpResourceVersion(resource) {
58
+ const key = versionKey(resource);
59
+ const newVersion = Date.now();
60
+ await this.store.set(key, newVersion, { ttlMs: 1440 * 60 * 1e3 });
61
+ }
62
+ /** Get current version for a tag */
63
+ async getTagVersion(tag) {
64
+ return await this.store.get(tagVersionKey(tag)) ?? 0;
65
+ }
66
+ /** Bump tag version — orphans all cached queries tagged with this tag */
67
+ async bumpTagVersion(tag) {
68
+ const key = tagVersionKey(tag);
69
+ const newVersion = Date.now();
70
+ await this.store.set(key, newVersion, { ttlMs: 1440 * 60 * 1e3 });
71
+ }
72
+ };
73
+
74
+ //#endregion
75
+ //#region src/cache/queryCachePlugin.ts
76
+ /**
77
+ * QueryCache Fastify Plugin
78
+ *
79
+ * Registers QueryCache on `fastify.queryCache` and wires automatic
80
+ * cache invalidation via CRUD events. Zero config for memory mode.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // Memory mode (default)
85
+ * await fastify.register(queryCachePlugin);
86
+ *
87
+ * // With Redis store
88
+ * await fastify.register(queryCachePlugin, {
89
+ * store: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),
90
+ * defaults: { staleTime: 30, gcTime: 300 },
91
+ * });
92
+ * ```
93
+ */
94
+ var queryCachePlugin_exports = /* @__PURE__ */ __exportAll({ queryCachePlugin: () => queryCachePlugin });
95
+ const CRUD_SUFFIXES = new Set([
96
+ "created",
97
+ "updated",
98
+ "deleted"
99
+ ]);
100
+ const queryCachePluginImpl = async (fastify, opts = {}) => {
101
+ const store = opts.store ?? new MemoryCacheStore();
102
+ const queryCache = new QueryCache(store);
103
+ const defaults = {
104
+ staleTime: opts.defaults?.staleTime ?? 0,
105
+ gcTime: opts.defaults?.gcTime ?? 60
106
+ };
107
+ fastify.decorate("queryCache", queryCache);
108
+ fastify.decorate("queryCacheConfig", defaults);
109
+ const crossResourceRules = [];
110
+ fastify.decorate("registerCacheInvalidationRule", (rule) => {
111
+ crossResourceRules.push(rule);
112
+ });
113
+ fastify.addHook("onReady", async () => {
114
+ if (!hasEvents(fastify)) return;
115
+ await fastify.events.subscribe("*", async (event) => {
116
+ const type = event.type;
117
+ const dotIdx = type.lastIndexOf(".");
118
+ if (dotIdx === -1) return;
119
+ const suffix = type.slice(dotIdx + 1);
120
+ if (!CRUD_SUFFIXES.has(suffix)) return;
121
+ const resource = type.slice(0, dotIdx);
122
+ await queryCache.bumpResourceVersion(resource);
123
+ });
124
+ for (const rule of crossResourceRules) await fastify.events.subscribe(rule.pattern, async () => {
125
+ for (const tag of rule.tags) await queryCache.bumpTagVersion(tag);
126
+ });
127
+ });
128
+ fastify.addHook("onClose", async () => {
129
+ if ("close" in store && typeof store.close === "function") await store.close();
130
+ });
131
+ };
132
+ const queryCachePlugin = fp(queryCachePluginImpl, {
133
+ name: "arc-query-cache",
134
+ fastify: "5.x"
135
+ });
136
+
137
+ //#endregion
138
+ export { queryCachePlugin_exports as n, QueryCache as r, queryCachePlugin as t };
139
+ //# sourceMappingURL=queryCachePlugin-DMBnp2Q0.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryCachePlugin-DMBnp2Q0.mjs","names":[],"sources":["../src/cache/QueryCache.ts","../src/cache/queryCachePlugin.ts"],"sourcesContent":["/**\r\n * QueryCache — TanStack Query-inspired server cache\r\n *\r\n * Wraps any CacheStore with:\r\n * - Freshness metadata (staleTime / gcTime envelope)\r\n * - Stale-while-revalidate status detection\r\n * - Version-based O(1) invalidation (no key scanning)\r\n * - Tag-based cross-resource invalidation\r\n */\r\n\r\nimport type { CacheStore } from './interface.js';\r\nimport { tagVersionKey, versionKey } from './keys.js';\r\n\r\n/** Metadata wrapper stored in CacheStore */\r\nexport interface CacheEnvelope<T = unknown> {\r\n data: T;\r\n createdAt: number;\r\n staleAfter: number;\r\n expiresAt: number;\r\n tags: string[];\r\n}\r\n\r\nexport interface QueryCacheConfig {\r\n /** Seconds data is \"fresh\" (no revalidation). Default: 0 */\r\n staleTime?: number;\r\n /** Seconds stale data stays cached (SWR window). Default: 60 */\r\n gcTime?: number;\r\n /** Tags for group invalidation */\r\n tags?: string[];\r\n}\r\n\r\nexport type CacheStatus = 'fresh' | 'stale' | 'miss';\r\n\r\nexport interface CacheResult<T> {\r\n data: T;\r\n status: CacheStatus;\r\n}\r\n\r\nexport class QueryCache {\r\n private readonly store: CacheStore;\r\n\r\n constructor(store: CacheStore) {\r\n this.store = store;\r\n }\r\n\r\n async get<T>(key: string): Promise<CacheResult<T>> {\r\n const envelope = await this.store.get(key) as CacheEnvelope<T> | undefined;\r\n\r\n if (!envelope || !envelope.createdAt) {\r\n return { data: undefined as T, status: 'miss' };\r\n }\r\n\r\n const now = Date.now();\r\n\r\n if (now >= envelope.expiresAt) {\r\n await this.store.delete(key);\r\n return { data: undefined as T, status: 'miss' };\r\n }\r\n\r\n if (now < envelope.staleAfter) {\r\n return { data: envelope.data, status: 'fresh' };\r\n }\r\n\r\n return { data: envelope.data, status: 'stale' };\r\n }\r\n\r\n async set<T>(key: string, data: T, config: QueryCacheConfig): Promise<void> {\r\n const staleTimeMs = (config.staleTime ?? 0) * 1000;\r\n const gcTimeMs = (config.gcTime ?? 60) * 1000;\r\n const totalTtl = staleTimeMs + gcTimeMs;\r\n const now = Date.now();\r\n\r\n const envelope: CacheEnvelope<T> = {\r\n data,\r\n createdAt: now,\r\n staleAfter: now + staleTimeMs,\r\n expiresAt: now + totalTtl,\r\n tags: config.tags ?? [],\r\n };\r\n\r\n await this.store.set(key, envelope, { ttlMs: totalTtl });\r\n }\r\n\r\n async invalidate(key: string): Promise<void> {\r\n await this.store.delete(key);\r\n }\r\n\r\n /** Get current version for a resource (defaults to 0 if not set) */\r\n async getResourceVersion(resource: string): Promise<number> {\r\n const ver = await this.store.get(versionKey(resource)) as number | undefined;\r\n return ver ?? 0;\r\n }\r\n\r\n /** Bump resource version — orphans all cached queries for this resource */\r\n async bumpResourceVersion(resource: string): Promise<void> {\r\n const key = versionKey(resource);\r\n const newVersion = Date.now();\r\n // Store version with a very long TTL (24h) — it's tiny data\r\n await this.store.set(key, newVersion, { ttlMs: 24 * 60 * 60 * 1000 });\r\n }\r\n\r\n /** Get current version for a tag */\r\n async getTagVersion(tag: string): Promise<number> {\r\n const ver = await this.store.get(tagVersionKey(tag)) as number | undefined;\r\n return ver ?? 0;\r\n }\r\n\r\n /** Bump tag version — orphans all cached queries tagged with this tag */\r\n async bumpTagVersion(tag: string): Promise<void> {\r\n const key = tagVersionKey(tag);\r\n const newVersion = Date.now();\r\n await this.store.set(key, newVersion, { ttlMs: 24 * 60 * 60 * 1000 });\r\n }\r\n}\r\n","/**\r\n * QueryCache Fastify Plugin\r\n *\r\n * Registers QueryCache on `fastify.queryCache` and wires automatic\r\n * cache invalidation via CRUD events. Zero config for memory mode.\r\n *\r\n * @example\r\n * ```typescript\r\n * // Memory mode (default)\r\n * await fastify.register(queryCachePlugin);\r\n *\r\n * // With Redis store\r\n * await fastify.register(queryCachePlugin, {\r\n * store: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),\r\n * defaults: { staleTime: 30, gcTime: 300 },\r\n * });\r\n * ```\r\n */\r\n\r\nimport fp from 'fastify-plugin';\r\nimport type { FastifyInstance, FastifyPluginAsync } from 'fastify';\r\nimport { QueryCache, type QueryCacheConfig } from './QueryCache.js';\r\nimport type { CacheStore } from './interface.js';\r\nimport { MemoryCacheStore } from './memory.js';\r\nimport { hasEvents } from '../utils/typeGuards.js';\r\n\r\nexport interface QueryCachePluginOptions {\r\n /** CacheStore instance. Default: MemoryCacheStore with default options. */\r\n store?: CacheStore;\r\n /** Global defaults for staleTime/gcTime (seconds) */\r\n defaults?: {\r\n staleTime?: number;\r\n gcTime?: number;\r\n };\r\n}\r\n\r\nexport interface QueryCacheDefaults {\r\n staleTime: number;\r\n gcTime: number;\r\n}\r\n\r\n/** Cross-resource invalidation rules collected from resource configs */\r\nexport interface CrossResourceRule {\r\n pattern: string;\r\n tags: string[];\r\n}\r\n\r\ndeclare module 'fastify' {\r\n interface FastifyInstance {\r\n queryCache: QueryCache;\r\n queryCacheConfig: QueryCacheDefaults;\r\n /** Register cross-resource invalidation rules (called by defineResource) */\r\n registerCacheInvalidationRule?(rule: CrossResourceRule): void;\r\n }\r\n}\r\n\r\nconst CRUD_SUFFIXES = new Set(['created', 'updated', 'deleted']);\r\n\r\nconst queryCachePluginImpl: FastifyPluginAsync<QueryCachePluginOptions> = async (\r\n fastify: FastifyInstance,\r\n opts: QueryCachePluginOptions = {},\r\n) => {\r\n const store = opts.store ?? new MemoryCacheStore();\r\n const queryCache = new QueryCache(store);\r\n\r\n const defaults: QueryCacheDefaults = {\r\n staleTime: opts.defaults?.staleTime ?? 0,\r\n gcTime: opts.defaults?.gcTime ?? 60,\r\n };\r\n\r\n fastify.decorate('queryCache', queryCache);\r\n fastify.decorate('queryCacheConfig', defaults);\r\n\r\n // Collect cross-resource rules from defineResource calls\r\n const crossResourceRules: CrossResourceRule[] = [];\r\n fastify.decorate('registerCacheInvalidationRule', (rule: CrossResourceRule) => {\r\n crossResourceRules.push(rule);\r\n });\r\n\r\n // Wire event-driven invalidation after all resources are registered\r\n fastify.addHook('onReady', async () => {\r\n if (!hasEvents(fastify)) return;\r\n\r\n // Auto-invalidate on CRUD events (product.created → bump product version)\r\n await fastify.events.subscribe('*', async (event) => {\r\n const type = (event as { type: string }).type;\r\n const dotIdx = type.lastIndexOf('.');\r\n if (dotIdx === -1) return;\r\n\r\n const suffix = type.slice(dotIdx + 1);\r\n if (!CRUD_SUFFIXES.has(suffix)) return;\r\n\r\n const resource = type.slice(0, dotIdx);\r\n await queryCache.bumpResourceVersion(resource);\r\n });\r\n\r\n // Wire cross-resource tag invalidation\r\n for (const rule of crossResourceRules) {\r\n await fastify.events.subscribe(rule.pattern, async () => {\r\n for (const tag of rule.tags) {\r\n await queryCache.bumpTagVersion(tag);\r\n }\r\n });\r\n }\r\n });\r\n\r\n // Cleanup on close\r\n fastify.addHook('onClose', async () => {\r\n if ('close' in store && typeof store.close === 'function') {\r\n await store.close();\r\n }\r\n });\r\n};\r\n\r\nexport const queryCachePlugin = fp(queryCachePluginImpl, {\r\n name: 'arc-query-cache',\r\n fastify: '5.x',\r\n});\r\n"],"mappings":"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"}