alepha 0.21.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/README.md +0 -1
  2. package/dist/api/audits/index.browser.js.map +1 -1
  3. package/dist/api/audits/index.d.ts +393 -403
  4. package/dist/api/audits/index.d.ts.map +1 -1
  5. package/dist/api/audits/index.js +25 -56
  6. package/dist/api/audits/index.js.map +1 -1
  7. package/dist/api/files/index.browser.js +31 -1
  8. package/dist/api/files/index.browser.js.map +1 -1
  9. package/dist/api/files/index.d.ts +313 -208
  10. package/dist/api/files/index.d.ts.map +1 -1
  11. package/dist/api/files/index.js +152 -42
  12. package/dist/api/files/index.js.map +1 -1
  13. package/dist/api/jobs/index.browser.js +2 -2
  14. package/dist/api/jobs/index.browser.js.map +1 -1
  15. package/dist/api/jobs/index.d.ts +282 -285
  16. package/dist/api/jobs/index.d.ts.map +1 -1
  17. package/dist/api/jobs/index.js +39 -33
  18. package/dist/api/jobs/index.js.map +1 -1
  19. package/dist/api/keys/index.d.ts +217 -222
  20. package/dist/api/keys/index.d.ts.map +1 -1
  21. package/dist/api/keys/index.js.map +1 -1
  22. package/dist/api/notifications/index.browser.js.map +1 -1
  23. package/dist/api/notifications/index.d.ts +188 -195
  24. package/dist/api/notifications/index.d.ts.map +1 -1
  25. package/dist/api/notifications/index.js.map +1 -1
  26. package/dist/api/oauth/index.d.ts +71 -76
  27. package/dist/api/oauth/index.d.ts.map +1 -1
  28. package/dist/api/oauth/index.js.map +1 -1
  29. package/dist/api/organizations/index.browser.js.map +1 -1
  30. package/dist/api/organizations/index.d.ts +104 -109
  31. package/dist/api/organizations/index.d.ts.map +1 -1
  32. package/dist/api/organizations/index.js.map +1 -1
  33. package/dist/api/parameters/index.browser.js +43 -16
  34. package/dist/api/parameters/index.browser.js.map +1 -1
  35. package/dist/api/parameters/index.d.ts +488 -344
  36. package/dist/api/parameters/index.d.ts.map +1 -1
  37. package/dist/api/parameters/index.js +175 -35
  38. package/dist/api/parameters/index.js.map +1 -1
  39. package/dist/api/payments/index.d.ts +396 -402
  40. package/dist/api/payments/index.d.ts.map +1 -1
  41. package/dist/api/payments/index.js.map +1 -1
  42. package/dist/api/subscriptions/index.d.ts +644 -652
  43. package/dist/api/subscriptions/index.d.ts.map +1 -1
  44. package/dist/api/subscriptions/index.js +1 -1
  45. package/dist/api/subscriptions/index.js.map +1 -1
  46. package/dist/api/users/index.browser.js +7 -0
  47. package/dist/api/users/index.browser.js.map +1 -1
  48. package/dist/api/users/index.d.ts +1106 -1005
  49. package/dist/api/users/index.d.ts.map +1 -1
  50. package/dist/api/users/index.js +307 -64
  51. package/dist/api/users/index.js.map +1 -1
  52. package/dist/api/verifications/index.browser.js.map +1 -1
  53. package/dist/api/verifications/index.d.ts +137 -143
  54. package/dist/api/verifications/index.d.ts.map +1 -1
  55. package/dist/api/verifications/index.js.map +1 -1
  56. package/dist/background/index.d.ts +95 -0
  57. package/dist/background/index.d.ts.map +1 -0
  58. package/dist/background/index.js +121 -0
  59. package/dist/background/index.js.map +1 -0
  60. package/dist/background/index.workerd.js +110 -0
  61. package/dist/background/index.workerd.js.map +1 -0
  62. package/dist/batch/index.d.ts +5 -7
  63. package/dist/batch/index.d.ts.map +1 -1
  64. package/dist/batch/index.js.map +1 -1
  65. package/dist/bin/index.js.map +1 -1
  66. package/dist/bucket/index.d.ts +76 -54
  67. package/dist/bucket/index.d.ts.map +1 -1
  68. package/dist/bucket/index.js +58 -11
  69. package/dist/bucket/index.js.map +1 -1
  70. package/dist/bucket/index.workerd.js +200 -5
  71. package/dist/bucket/index.workerd.js.map +1 -1
  72. package/dist/cache/core/index.d.ts +7 -10
  73. package/dist/cache/core/index.d.ts.map +1 -1
  74. package/dist/cache/core/index.js.map +1 -1
  75. package/dist/cache/core/index.workerd.js.map +1 -1
  76. package/dist/cache/database/index.d.ts +22 -26
  77. package/dist/cache/database/index.d.ts.map +1 -1
  78. package/dist/cache/database/index.js.map +1 -1
  79. package/dist/cache/redis/index.d.ts +4 -7
  80. package/dist/cache/redis/index.d.ts.map +1 -1
  81. package/dist/cache/redis/index.js.map +1 -1
  82. package/dist/captcha/index.d.ts +3 -6
  83. package/dist/captcha/index.d.ts.map +1 -1
  84. package/dist/captcha/index.js.map +1 -1
  85. package/dist/cli/config/index.d.ts.map +1 -1
  86. package/dist/cli/config/index.js.map +1 -1
  87. package/dist/cli/core/index.d.ts +458 -249
  88. package/dist/cli/core/index.d.ts.map +1 -1
  89. package/dist/cli/core/index.js +372 -660
  90. package/dist/cli/core/index.js.map +1 -1
  91. package/dist/cli/devtools/index.d.ts +3 -5
  92. package/dist/cli/devtools/index.d.ts.map +1 -1
  93. package/dist/cli/devtools/index.js.map +1 -1
  94. package/dist/cli/i18n/index.d.ts +20 -17
  95. package/dist/cli/i18n/index.d.ts.map +1 -1
  96. package/dist/cli/i18n/index.js +45 -11
  97. package/dist/cli/i18n/index.js.map +1 -1
  98. package/dist/cli/platform/index.d.ts +126 -1342
  99. package/dist/cli/platform/index.d.ts.map +1 -1
  100. package/dist/cli/platform/index.js +136 -2374
  101. package/dist/cli/platform/index.js.map +1 -1
  102. package/dist/cli/platform-lib/index.d.ts +1472 -0
  103. package/dist/cli/platform-lib/index.d.ts.map +1 -0
  104. package/dist/cli/platform-lib/index.js +2660 -0
  105. package/dist/cli/platform-lib/index.js.map +1 -0
  106. package/dist/cli/vendor/index.d.ts +17 -21
  107. package/dist/cli/vendor/index.d.ts.map +1 -1
  108. package/dist/cli/vendor/index.js.map +1 -1
  109. package/dist/command/index.d.ts +20 -19
  110. package/dist/command/index.d.ts.map +1 -1
  111. package/dist/command/index.js +39 -10
  112. package/dist/command/index.js.map +1 -1
  113. package/dist/{containers → container}/core/index.d.ts +13 -15
  114. package/dist/container/core/index.d.ts.map +1 -0
  115. package/dist/{containers → container}/core/index.js +23 -14
  116. package/dist/container/core/index.js.map +1 -0
  117. package/dist/{containers → container}/core/index.workerd.js +37 -22
  118. package/dist/container/core/index.workerd.js.map +1 -0
  119. package/dist/core/index.browser.js +27 -1
  120. package/dist/core/index.browser.js.map +1 -1
  121. package/dist/core/index.d.ts +48 -24
  122. package/dist/core/index.d.ts.map +1 -1
  123. package/dist/core/index.js +27 -1
  124. package/dist/core/index.js.map +1 -1
  125. package/dist/core/index.native.js +27 -1
  126. package/dist/core/index.native.js.map +1 -1
  127. package/dist/core/index.workerd.js +27 -1
  128. package/dist/core/index.workerd.js.map +1 -1
  129. package/dist/crypto/index.browser.js.map +1 -1
  130. package/dist/crypto/index.d.ts +5 -8
  131. package/dist/crypto/index.d.ts.map +1 -1
  132. package/dist/crypto/index.js.map +1 -1
  133. package/dist/datetime/index.d.ts +3 -4
  134. package/dist/datetime/index.d.ts.map +1 -1
  135. package/dist/datetime/index.js.map +1 -1
  136. package/dist/email/brevo/index.d.ts +2 -4
  137. package/dist/email/brevo/index.d.ts.map +1 -1
  138. package/dist/email/brevo/index.js.map +1 -1
  139. package/dist/email/cloudflare/index.d.ts +20 -7
  140. package/dist/email/cloudflare/index.d.ts.map +1 -1
  141. package/dist/email/cloudflare/index.js +46 -9
  142. package/dist/email/cloudflare/index.js.map +1 -1
  143. package/dist/email/core/index.d.ts +6 -9
  144. package/dist/email/core/index.d.ts.map +1 -1
  145. package/dist/email/core/index.js.map +1 -1
  146. package/dist/email/core/index.workerd.js.map +1 -1
  147. package/dist/email/smtp/index.d.ts +10 -13
  148. package/dist/email/smtp/index.d.ts.map +1 -1
  149. package/dist/email/smtp/index.js +107 -32
  150. package/dist/email/smtp/index.js.map +1 -1
  151. package/dist/fake/index.d.ts +1 -2
  152. package/dist/fake/index.d.ts.map +1 -1
  153. package/dist/fake/index.js.map +1 -1
  154. package/dist/lock/core/index.d.ts +9 -14
  155. package/dist/lock/core/index.d.ts.map +1 -1
  156. package/dist/lock/core/index.js.map +1 -1
  157. package/dist/lock/redis/index.d.ts +2 -4
  158. package/dist/lock/redis/index.d.ts.map +1 -1
  159. package/dist/lock/redis/index.js.map +1 -1
  160. package/dist/logger/index.d.ts +105 -76
  161. package/dist/logger/index.d.ts.map +1 -1
  162. package/dist/logger/index.js +196 -174
  163. package/dist/logger/index.js.map +1 -1
  164. package/dist/mcp/index.d.ts +25 -20
  165. package/dist/mcp/index.d.ts.map +1 -1
  166. package/dist/mcp/index.js +23 -0
  167. package/dist/mcp/index.js.map +1 -1
  168. package/dist/orm/core/index.browser.js.map +1 -1
  169. package/dist/orm/core/index.bun.js +19 -1
  170. package/dist/orm/core/index.bun.js.map +1 -1
  171. package/dist/orm/core/index.d.ts +76 -62
  172. package/dist/orm/core/index.d.ts.map +1 -1
  173. package/dist/orm/core/index.js +20 -2
  174. package/dist/orm/core/index.js.map +1 -1
  175. package/dist/orm/postgres/index.bun.js.map +1 -1
  176. package/dist/orm/postgres/index.d.ts +28 -20
  177. package/dist/orm/postgres/index.d.ts.map +1 -1
  178. package/dist/orm/postgres/index.js.map +1 -1
  179. package/dist/queue/core/index.d.ts +12 -15
  180. package/dist/queue/core/index.d.ts.map +1 -1
  181. package/dist/queue/core/index.js.map +1 -1
  182. package/dist/queue/core/index.workerd.js.map +1 -1
  183. package/dist/queue/redis/index.d.ts +3 -5
  184. package/dist/queue/redis/index.d.ts.map +1 -1
  185. package/dist/queue/redis/index.js.map +1 -1
  186. package/dist/react/auth/index.browser.js +9 -2
  187. package/dist/react/auth/index.browser.js.map +1 -1
  188. package/dist/react/auth/index.d.ts +14 -9
  189. package/dist/react/auth/index.d.ts.map +1 -1
  190. package/dist/react/auth/index.js +9 -2
  191. package/dist/react/auth/index.js.map +1 -1
  192. package/dist/react/core/index.d.ts +7 -8
  193. package/dist/react/core/index.d.ts.map +1 -1
  194. package/dist/react/core/index.js +6 -3
  195. package/dist/react/core/index.js.map +1 -1
  196. package/dist/react/form/index.d.ts +2 -5
  197. package/dist/react/form/index.d.ts.map +1 -1
  198. package/dist/react/form/index.js +16 -15
  199. package/dist/react/form/index.js.map +1 -1
  200. package/dist/react/head/index.browser.js.map +1 -1
  201. package/dist/react/head/index.d.ts +2 -4
  202. package/dist/react/head/index.d.ts.map +1 -1
  203. package/dist/react/head/index.js.map +1 -1
  204. package/dist/react/i18n/index.d.ts +90 -11
  205. package/dist/react/i18n/index.d.ts.map +1 -1
  206. package/dist/react/i18n/index.js +147 -11
  207. package/dist/react/i18n/index.js.map +1 -1
  208. package/dist/react/intro/index.d.ts +1 -2
  209. package/dist/react/intro/index.d.ts.map +1 -1
  210. package/dist/react/intro/index.js +2 -2
  211. package/dist/react/intro/index.js.map +1 -1
  212. package/dist/react/router/index.browser.js +193 -24
  213. package/dist/react/router/index.browser.js.map +1 -1
  214. package/dist/react/router/index.d.ts +434 -222
  215. package/dist/react/router/index.d.ts.map +1 -1
  216. package/dist/react/router/index.js +249 -35
  217. package/dist/react/router/index.js.map +1 -1
  218. package/dist/react/sitemap/index.browser.js +35 -0
  219. package/dist/react/sitemap/index.browser.js.map +1 -0
  220. package/dist/react/sitemap/index.d.ts +92 -0
  221. package/dist/react/sitemap/index.d.ts.map +1 -0
  222. package/dist/react/sitemap/index.js +131 -0
  223. package/dist/react/sitemap/index.js.map +1 -0
  224. package/dist/react/testing/index.d.ts +1 -2
  225. package/dist/react/testing/index.d.ts.map +1 -1
  226. package/dist/react/testing/index.js +16 -17
  227. package/dist/react/testing/index.js.map +1 -1
  228. package/dist/react/ui/index.d.ts +20 -25
  229. package/dist/react/ui/index.d.ts.map +1 -1
  230. package/dist/react/ui/index.js.map +1 -1
  231. package/dist/redis/index.bun.js.map +1 -1
  232. package/dist/redis/index.d.ts +17 -19
  233. package/dist/redis/index.d.ts.map +1 -1
  234. package/dist/redis/index.js.map +1 -1
  235. package/dist/retry/index.d.ts +2 -4
  236. package/dist/retry/index.d.ts.map +1 -1
  237. package/dist/retry/index.js.map +1 -1
  238. package/dist/router/index.d.ts.map +1 -1
  239. package/dist/router/index.js.map +1 -1
  240. package/dist/scheduler/index.d.ts +10 -13
  241. package/dist/scheduler/index.d.ts.map +1 -1
  242. package/dist/scheduler/index.js.map +1 -1
  243. package/dist/scheduler/index.workerd.js.map +1 -1
  244. package/dist/security/index.browser.js.map +1 -1
  245. package/dist/security/index.d.ts +45 -48
  246. package/dist/security/index.d.ts.map +1 -1
  247. package/dist/security/index.js.map +1 -1
  248. package/dist/server/auth/index.browser.js.map +1 -1
  249. package/dist/server/auth/index.d.ts +272 -173
  250. package/dist/server/auth/index.d.ts.map +1 -1
  251. package/dist/server/auth/index.js +1608 -15
  252. package/dist/server/auth/index.js.map +1 -1
  253. package/dist/server/cookies/index.browser.js.map +1 -1
  254. package/dist/server/cookies/index.d.ts +20 -7
  255. package/dist/server/cookies/index.d.ts.map +1 -1
  256. package/dist/server/cookies/index.js +22 -3
  257. package/dist/server/cookies/index.js.map +1 -1
  258. package/dist/server/core/index.browser.js.map +1 -1
  259. package/dist/server/core/index.d.ts +106 -73
  260. package/dist/server/core/index.d.ts.map +1 -1
  261. package/dist/server/core/index.js +44 -0
  262. package/dist/server/core/index.js.map +1 -1
  263. package/dist/server/cors/index.d.ts +11 -14
  264. package/dist/server/cors/index.d.ts.map +1 -1
  265. package/dist/server/cors/index.js.map +1 -1
  266. package/dist/server/etag/index.d.ts +6 -9
  267. package/dist/server/etag/index.d.ts.map +1 -1
  268. package/dist/server/etag/index.js.map +1 -1
  269. package/dist/server/health/index.d.ts +18 -21
  270. package/dist/server/health/index.d.ts.map +1 -1
  271. package/dist/server/health/index.js.map +1 -1
  272. package/dist/server/links/index.browser.js +2 -0
  273. package/dist/server/links/index.browser.js.map +1 -1
  274. package/dist/server/links/index.d.ts +63 -67
  275. package/dist/server/links/index.d.ts.map +1 -1
  276. package/dist/server/links/index.js +2 -0
  277. package/dist/server/links/index.js.map +1 -1
  278. package/dist/server/metrics/index.d.ts +5 -7
  279. package/dist/server/metrics/index.d.ts.map +1 -1
  280. package/dist/server/metrics/index.js.map +1 -1
  281. package/dist/server/proxy/index.d.ts +3 -5
  282. package/dist/server/proxy/index.d.ts.map +1 -1
  283. package/dist/server/proxy/index.js.map +1 -1
  284. package/dist/server/rate-limit/index.d.ts +10 -13
  285. package/dist/server/rate-limit/index.d.ts.map +1 -1
  286. package/dist/server/rate-limit/index.js.map +1 -1
  287. package/dist/server/static/index.d.ts +3 -5
  288. package/dist/server/static/index.d.ts.map +1 -1
  289. package/dist/server/static/index.js.map +1 -1
  290. package/dist/server/swagger/index.d.ts +5 -8
  291. package/dist/server/swagger/index.d.ts.map +1 -1
  292. package/dist/server/swagger/index.js.map +1 -1
  293. package/dist/sms/index.d.ts +3 -5
  294. package/dist/sms/index.d.ts.map +1 -1
  295. package/dist/sms/index.js.map +1 -1
  296. package/dist/system/index.browser.js.map +1 -1
  297. package/dist/system/index.d.ts +2 -4
  298. package/dist/system/index.d.ts.map +1 -1
  299. package/dist/system/index.js.map +1 -1
  300. package/dist/system/index.workerd.js.map +1 -1
  301. package/dist/topic/core/index.d.ts +4 -6
  302. package/dist/topic/core/index.d.ts.map +1 -1
  303. package/dist/topic/core/index.js.map +1 -1
  304. package/dist/topic/redis/index.d.ts +5 -8
  305. package/dist/topic/redis/index.d.ts.map +1 -1
  306. package/dist/topic/redis/index.js.map +1 -1
  307. package/package.json +59 -23
  308. package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
  309. package/src/api/audits/controllers/AdminAuditController.ts +14 -0
  310. package/src/api/audits/services/AuditService.ts +21 -88
  311. package/src/api/files/__tests__/FileService.spec.ts +207 -2
  312. package/src/api/files/index.ts +3 -0
  313. package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
  314. package/src/api/files/schemas/fileResourceSchema.ts +10 -1
  315. package/src/api/files/services/FileService.ts +170 -72
  316. package/src/api/jobs/__tests__/$job.spec.ts +24 -1
  317. package/src/api/jobs/index.ts +4 -3
  318. package/src/api/jobs/primitives/$job.ts +7 -3
  319. package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
  320. package/src/api/jobs/providers/JobProvider.ts +53 -24
  321. package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
  322. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
  323. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
  324. package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
  325. package/src/api/parameters/audits/ParameterAudits.ts +17 -0
  326. package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
  327. package/src/api/parameters/index.ts +3 -0
  328. package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
  329. package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
  330. package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
  331. package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
  332. package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
  333. package/src/api/parameters/services/ParameterProvider.ts +69 -6
  334. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
  335. package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
  336. package/src/api/users/audits/SessionAudits.ts +33 -0
  337. package/src/api/users/audits/UserAudits.ts +19 -43
  338. package/src/api/users/controllers/AdminUserController.ts +66 -1
  339. package/src/api/users/controllers/RealmController.ts +1 -0
  340. package/src/api/users/entities/sessions.ts +6 -0
  341. package/src/api/users/entities/users.ts +2 -0
  342. package/src/api/users/index.ts +9 -1
  343. package/src/api/users/primitives/$realm.ts +29 -0
  344. package/src/api/users/providers/RealmProvider.ts +15 -0
  345. package/src/api/users/schemas/realmConfigSchema.ts +14 -0
  346. package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
  347. package/src/api/users/schemas/updateUserSchema.ts +1 -8
  348. package/src/api/users/schemas/userQuerySchema.ts +7 -0
  349. package/src/api/users/services/CredentialService.ts +15 -6
  350. package/src/api/users/services/IdentityService.ts +2 -1
  351. package/src/api/users/services/RegistrationService.ts +2 -1
  352. package/src/api/users/services/SessionCrudService.ts +19 -2
  353. package/src/api/users/services/SessionService.ts +39 -19
  354. package/src/api/users/services/UserService.ts +106 -8
  355. package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
  356. package/src/background/index.ts +37 -0
  357. package/src/background/index.workerd.ts +28 -0
  358. package/src/background/providers/BackgroundTaskProvider.ts +70 -0
  359. package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
  360. package/src/bucket/__tests__/$bucket.spec.ts +18 -0
  361. package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
  362. package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
  363. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
  364. package/src/bucket/__tests__/shared.ts +30 -0
  365. package/src/bucket/index.ts +5 -5
  366. package/src/bucket/index.workerd.ts +11 -4
  367. package/src/bucket/primitives/$bucket.ts +27 -0
  368. package/src/bucket/providers/FileStorageProvider.ts +13 -0
  369. package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
  370. package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
  371. package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
  372. package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
  373. package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
  374. package/src/cli/core/__tests__/init.spec.ts +0 -219
  375. package/src/cli/core/atoms/buildOptions.ts +0 -12
  376. package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
  377. package/src/cli/core/commands/build.ts +105 -37
  378. package/src/cli/core/commands/init.ts +0 -12
  379. package/src/cli/core/commands/pack.ts +133 -0
  380. package/src/cli/core/index.ts +3 -3
  381. package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
  382. package/src/cli/core/services/PackageManagerUtils.ts +0 -16
  383. package/src/cli/core/services/ProjectScaffolder.ts +29 -291
  384. package/src/cli/core/tasks/BuildCloudflareTask.ts +382 -56
  385. package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
  386. package/src/cli/core/tasks/BuildPrerenderTask.ts +44 -7
  387. package/src/cli/core/tasks/BuildTask.ts +34 -0
  388. package/src/cli/core/templates/apiIndexTs.ts +1 -22
  389. package/src/cli/core/templates/mainCss.ts +0 -1
  390. package/src/cli/core/templates/webAppRouterTs.ts +0 -99
  391. package/src/cli/core/templates/webIndexTs.ts +1 -22
  392. package/src/cli/i18n/__tests__/I18nCheckService.spec.ts +48 -0
  393. package/src/cli/i18n/services/I18nCheckService.ts +65 -11
  394. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
  395. package/src/cli/platform/commands/SecretsCommand.ts +8 -6
  396. package/src/cli/platform/commands/platform.ts +192 -46
  397. package/src/cli/platform/index.ts +12 -52
  398. package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
  399. package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
  400. package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
  401. package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +519 -190
  402. package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
  403. package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
  404. package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
  405. package/src/cli/platform-lib/index.ts +67 -0
  406. package/src/cli/platform-lib/services/NamingService.ts +136 -0
  407. package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
  408. package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
  409. package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
  410. package/src/command/__tests__/Runner.spec.ts +20 -0
  411. package/src/command/helpers/EnvUtils.ts +19 -3
  412. package/src/command/helpers/Runner.ts +12 -2
  413. package/src/command/providers/CliProvider.ts +34 -1
  414. package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
  415. package/src/{containers → container}/core/index.ts +4 -4
  416. package/src/{containers → container}/core/index.workerd.ts +19 -3
  417. package/src/{containers → container}/core/primitives/$container.ts +1 -1
  418. package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
  419. package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
  420. package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
  421. package/src/core/Alepha.ts +49 -1
  422. package/src/core/__tests__/$env.spec.ts +42 -0
  423. package/src/core/__tests__/dump.spec.ts +47 -0
  424. package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
  425. package/src/email/cloudflare/index.ts +14 -5
  426. package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
  427. package/src/logger/__tests__/Logger.spec.ts +55 -0
  428. package/src/logger/index.ts +13 -0
  429. package/src/logger/services/Logger.ts +31 -1
  430. package/src/mcp/__tests__/McpServerProvider.spec.ts +71 -0
  431. package/src/mcp/providers/McpServerProvider.ts +55 -0
  432. package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
  433. package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
  434. package/src/orm/core/interfaces/PgQuery.ts +4 -1
  435. package/src/orm/core/services/Repository.ts +27 -11
  436. package/src/react/auth/hooks/useAuth.ts +10 -5
  437. package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
  438. package/src/react/core/hooks/useAction.ts +14 -3
  439. package/src/react/core/hooks/useQuery.ts +24 -4
  440. package/src/react/form/__tests__/FormModel-submit-loading.spec.ts +71 -0
  441. package/src/react/form/__tests__/form-submitting-reactive.browser.spec.tsx +96 -0
  442. package/src/react/form/services/FormModel.ts +57 -39
  443. package/src/react/i18n/__tests__/I18nProvider.spec.ts +89 -0
  444. package/src/react/i18n/__tests__/locale-routing.spec.ts +107 -0
  445. package/src/react/i18n/components/Translate.tsx +47 -0
  446. package/src/react/i18n/index.ts +2 -0
  447. package/src/react/i18n/providers/I18nProvider.ts +171 -12
  448. package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
  449. package/src/react/router/__tests__/$page.spec.tsx +3 -2
  450. package/src/react/router/__tests__/RouterLocaleProvider.spec.ts +127 -0
  451. package/src/react/router/__tests__/page-can.spec.ts +18 -13
  452. package/src/react/router/hooks/useQueryParams.ts +114 -14
  453. package/src/react/router/index.browser.ts +4 -0
  454. package/src/react/router/index.shared.ts +1 -0
  455. package/src/react/router/index.ts +9 -0
  456. package/src/react/router/primitives/$page.ts +85 -4
  457. package/src/react/router/providers/ReactBrowserRouterProvider.ts +18 -8
  458. package/src/react/router/providers/ReactPageProvider.ts +12 -1
  459. package/src/react/router/providers/ReactServerProvider.ts +96 -14
  460. package/src/react/router/providers/RootComponentsProvider.ts +13 -0
  461. package/src/react/router/providers/RouterLocaleProvider.ts +125 -0
  462. package/src/react/router/providers/__tests__/RootComponentsProvider.spec.ts +15 -0
  463. package/src/react/router/providers/__tests__/rootComponents.ssr.browser.spec.tsx +67 -0
  464. package/src/react/sitemap/__tests__/$sitemap.spec.ts +131 -0
  465. package/src/react/sitemap/index.browser.ts +21 -0
  466. package/src/react/sitemap/index.ts +25 -0
  467. package/src/react/sitemap/primitives/$sitemap.browser.ts +26 -0
  468. package/src/react/sitemap/primitives/$sitemap.ts +196 -0
  469. package/src/react/ui/services/SchemaControl.ts +3 -4
  470. package/src/server/auth/__tests__/appleClientSecret.spec.ts +34 -0
  471. package/src/server/auth/__tests__/authFederationClient.spec.ts +40 -0
  472. package/src/server/auth/__tests__/federationAssertion.spec.ts +146 -0
  473. package/src/server/auth/__tests__/federationRedirectReplay.spec.ts +44 -0
  474. package/src/server/auth/helpers/appleClientSecret.ts +24 -0
  475. package/src/server/auth/helpers/federationAssertion.ts +74 -0
  476. package/src/server/auth/helpers/jtiReplayGuard.ts +41 -0
  477. package/src/server/auth/helpers/safeRedirectPath.ts +19 -0
  478. package/src/server/auth/index.ts +4 -0
  479. package/src/server/auth/primitives/$authFederationBroker.ts +273 -0
  480. package/src/server/auth/primitives/$authFederationClient.ts +89 -0
  481. package/src/server/auth/providers/ServerAuthProvider.ts +18 -4
  482. package/src/server/cookies/__tests__/ServerCookiesProvider.spec.ts +70 -0
  483. package/src/server/cookies/providers/ServerCookiesProvider.ts +23 -3
  484. package/src/server/core/interfaces/ServerRequest.ts +8 -0
  485. package/src/server/core/primitives/$route.ts +27 -0
  486. package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
  487. package/src/server/links/providers/LinkProvider.ts +10 -0
  488. package/dist/containers/core/index.d.ts.map +0 -1
  489. package/dist/containers/core/index.js.map +0 -1
  490. package/dist/containers/core/index.workerd.js.map +0 -1
  491. package/src/cli/core/tasks/BuildSitemapTask.ts +0 -130
  492. package/src/cli/core/templates/componentsJsonTs.ts +0 -39
  493. package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
  494. package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
  495. package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
  496. package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
  497. package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
  498. package/src/cli/platform/services/NamingService.ts +0 -54
  499. /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
  500. /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
  501. /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
  502. /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
  503. /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
  504. /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
  505. /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
  506. /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
  507. /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
  508. /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
  509. /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
  510. /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
  511. /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
  512. /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
  513. /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
  514. /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
  515. /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
  516. /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
  517. /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
  518. /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
  519. /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.workerd.js","names":[],"sources":["../../../src/cache/core/errors/CacheError.ts","../../../src/cache/core/providers/CacheProvider.ts","../../../src/cache/core/providers/MemoryCacheProvider.ts","../../../src/cache/core/primitives/$cache.ts","../../../src/cache/core/providers/CloudflareKVProvider.ts","../../../src/cache/core/index.workerd.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class CacheError extends AlephaError {}\n","import { CacheError } from \"../errors/CacheError.ts\";\n\n/**\n * Cache provider interface.\n *\n * All methods are asynchronous and return promises.\n * Values are stored as Uint8Array.\n */\nexport abstract class CacheProvider {\n protected encoder: TextEncoder = new TextEncoder();\n protected decoder: TextDecoder = new TextDecoder();\n protected codes = {\n BINARY: 0x01,\n JSON: 0x02,\n STRING: 0x03,\n COMPRESSED: 0x04,\n };\n\n /**\n * Get the value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to get.\n *\n * @return The value of the key, or undefined if the key does not exist.\n */\n public abstract get(\n name: string,\n key: string,\n ): Promise<Uint8Array | undefined>;\n\n /**\n * Set the string value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to set.\n * @param value The value to set.\n * @param ttl The time-to-live of the key, in milliseconds.\n *\n * @return The value of the key.\n */\n public abstract set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array>;\n\n /**\n * Remove the specified keys.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param keys The keys to delete.\n */\n public abstract del(name: string, ...keys: string[]): Promise<void>;\n\n public abstract has(name: string, key: string): Promise<boolean>;\n\n public abstract keys(name: string, filter?: string): Promise<string[]>;\n\n /**\n * Remove all keys from all cache names.\n */\n public abstract clear(): Promise<void>;\n\n /**\n * Increment the integer value of a key by the given amount.\n *\n * If the key does not exist, it is set to 0 before performing the operation.\n * This operation is atomic when using Redis.\n *\n * @param name Cache name, used to group keys.\n * @param key The key to increment.\n * @param amount The amount to increment by.\n * @returns The new value after incrementing.\n */\n public abstract incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number>;\n\n // ---------------------------------------------------------------------------\n // High-level methods — serialize/compress layer used by CachePrimitive\n // ---------------------------------------------------------------------------\n\n /**\n * Set a typed value with automatic serialization and optional compression.\n */\n public async setTyped(\n name: string,\n key: string,\n value: unknown,\n options?: { ttl?: number; compress?: boolean },\n ): Promise<void> {\n let data = this.serialize(value);\n if (options?.compress) {\n data = await this.compress(data);\n }\n await this.set(name, key, data, options?.ttl);\n }\n\n /**\n * Get a typed value with automatic deserialization and optional decompression.\n */\n public async getTyped<T>(name: string, key: string): Promise<T | undefined> {\n const data = await this.get(name, key);\n if (data) {\n if (data[0] === this.codes.COMPRESSED) {\n const decompressed = await this.decompress(data.subarray(1));\n return this.deserialize<T>(decompressed);\n }\n return this.deserialize<T>(data);\n }\n return undefined;\n }\n\n /**\n * Invalidate cache keys with wildcard support.\n *\n * Keys ending in `*` are expanded via `this.keys()`.\n * Called with no keys, delegates to `this.del(name)` which clears the entire container.\n */\n public async invalidateKeys(name: string, keys: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const result = await this.keys(name, key.slice(0, -1));\n keysToDelete.push(...result);\n } else {\n keysToDelete.push(key);\n }\n }\n\n await this.del(name, ...keysToDelete);\n }\n\n /**\n * Serialize a value to a typed Uint8Array with a leading type marker byte.\n */\n protected serialize(value: unknown): Uint8Array {\n if (value instanceof Uint8Array) {\n const result = new Uint8Array(1 + value.length);\n result[0] = this.codes.BINARY;\n result.set(value, 1);\n return result;\n }\n\n const encoded = this.encoder.encode(\n typeof value === \"string\" ? value : JSON.stringify(value),\n );\n const code =\n typeof value === \"string\" ? this.codes.STRING : this.codes.JSON;\n const result = new Uint8Array(1 + encoded.length);\n result[0] = code;\n result.set(encoded, 1);\n return result;\n }\n\n /**\n * Deserialize a typed Uint8Array back to the original value.\n */\n protected deserialize<T>(uint8Array: Uint8Array): T {\n const type = uint8Array[0];\n const payload = uint8Array.slice(1);\n\n if (type === this.codes.BINARY) {\n return payload as T;\n }\n if (type === this.codes.JSON) {\n return JSON.parse(this.decoder.decode(payload)) as T;\n }\n if (type === this.codes.STRING) {\n return this.decoder.decode(payload) as T;\n }\n\n throw new CacheError(`Unknown serialization type: ${type}`);\n }\n\n /**\n * Compress data with gzip, prepending a COMPRESSED marker byte.\n */\n protected async compress(data: Uint8Array): Promise<Uint8Array> {\n const buf = (data.buffer as ArrayBuffer).slice(\n data.byteOffset,\n data.byteOffset + data.byteLength,\n );\n const compressed = new Uint8Array(\n await new Response(\n new Blob([buf]).stream().pipeThrough(new CompressionStream(\"gzip\")),\n ).arrayBuffer(),\n );\n const result = new Uint8Array(1 + compressed.length);\n result[0] = this.codes.COMPRESSED;\n result.set(compressed, 1);\n return result;\n }\n\n /**\n * Decompress gzipped data.\n */\n protected async decompress(data: Uint8Array): Promise<Uint8Array> {\n const buf = (data.buffer as ArrayBuffer).slice(\n data.byteOffset,\n data.byteOffset + data.byteLength,\n );\n return new Uint8Array(\n await new Response(\n new Blob([buf]).stream().pipeThrough(new DecompressionStream(\"gzip\")),\n ).arrayBuffer(),\n );\n }\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider, type Timeout } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { CacheProvider } from \"./CacheProvider.ts\";\n\ntype CacheName = string;\ntype CacheKey = string;\ntype CacheValue = {\n data?: Uint8Array;\n timeout?: Timeout;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface MemoryCacheCall {\n name: string;\n key: string;\n timestamp: number;\n}\n\nexport interface MemoryCacheSetCall extends MemoryCacheCall {\n value: Uint8Array;\n ttl?: number;\n}\n\nexport interface MemoryCacheDelCall {\n name: string;\n keys: string[];\n timestamp: number;\n}\n\nexport interface MemoryCacheStats {\n hits: number;\n misses: number;\n sets: number;\n deletes: number;\n}\n\nexport interface MemoryCacheProviderOptions {\n /**\n * Error to throw on get operations (for testing error handling)\n */\n getError?: Error | null;\n /**\n * Error to throw on set operations (for testing error handling)\n */\n setError?: Error | null;\n /**\n * Error to throw on del operations (for testing error handling)\n */\n delError?: Error | null;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * In-memory implementation of CacheProvider for testing.\n *\n * This provider stores all cache entries in memory, making it ideal for\n * unit tests that need to verify cache operations without touching Redis or other backends.\n *\n * @example\n * ```typescript\n * // In tests, substitute the real CacheProvider with MemoryCacheProvider\n * const alepha = Alepha.create().with({\n * provide: CacheProvider,\n * use: MemoryCacheProvider,\n * });\n *\n * // Run code that uses caching\n * const service = alepha.inject(MyService);\n * await service.fetchWithCache(\"key\");\n *\n * // Verify cache behavior\n * const cache = alepha.inject(MemoryCacheProvider);\n * expect(cache.stats().misses).toBe(1);\n * await service.fetchWithCache(\"key\");\n * expect(cache.stats().hits).toBe(1);\n * ```\n */\nexport class MemoryCacheProvider extends CacheProvider {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n\n protected store: Record<CacheName, Record<CacheKey, CacheValue>> = {};\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Test tracking\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * All recorded get calls.\n */\n public getCalls: MemoryCacheCall[] = [];\n\n /**\n * All recorded set calls.\n */\n public setCalls: MemoryCacheSetCall[] = [];\n\n /**\n * All recorded del calls.\n */\n public delCalls: MemoryCacheDelCall[] = [];\n\n /**\n * Cache statistics.\n */\n protected _stats: MemoryCacheStats = {\n hits: 0,\n misses: 0,\n sets: 0,\n deletes: 0,\n };\n\n /**\n * Error to throw on get (for testing error handling)\n */\n public getError: Error | null = null;\n\n /**\n * Error to throw on set (for testing error handling)\n */\n public setError: Error | null = null;\n\n /**\n * Error to throw on del (for testing error handling)\n */\n public delError: Error | null = null;\n\n constructor(options: MemoryCacheProviderOptions = {}) {\n super();\n this.getError = options.getError ?? null;\n this.setError = options.setError ?? null;\n this.delError = options.delError ?? null;\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // CacheProvider implementation\n // ─────────────────────────────────────────────────────────────────────────────\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n this.getCalls.push({\n name,\n key,\n timestamp: this.dateTimeProvider.nowMillis(),\n });\n\n if (this.getError) {\n throw this.getError;\n }\n\n const data = this.store[name]?.[key]?.data;\n\n if (data !== undefined) {\n this._stats.hits++;\n } else {\n this._stats.misses++;\n }\n\n return data;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n this.setCalls.push({\n name,\n key,\n value,\n ttl,\n timestamp: this.dateTimeProvider.nowMillis(),\n });\n this._stats.sets++;\n\n if (this.setError) {\n throw this.setError;\n }\n\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n this.store[name][key] ??= {};\n this.store[name][key].data = value;\n\n this.log.debug(`Setting cache for name`, { name, key, ttl });\n\n // clear previous timeout if exists\n if (this.store[name][key].timeout) {\n this.dateTimeProvider.clearTimeout(this.store[name][key].timeout);\n this.store[name][key].timeout = undefined;\n }\n\n if (ttl) {\n this.store[name][key].timeout = this.dateTimeProvider.createTimeout(\n () => this.del(name, key),\n ttl,\n );\n }\n\n return this.store[name][key].data;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n this.delCalls.push({\n name,\n keys,\n timestamp: this.dateTimeProvider.nowMillis(),\n });\n this._stats.deletes++;\n\n if (this.delError) {\n throw this.delError;\n }\n\n // delete all keys in name\n if (keys.length === 0) {\n this.log.debug(`Deleting all cache for name`, { name });\n\n if (this.store[name]) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n delete this.store[name];\n return;\n }\n\n this.log.debug(`Deleting cache for name`, { name, keys });\n\n // delete specific keys in name\n for (const key of keys) {\n if (this.store[name] == null) break;\n\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n\n delete this.store[name][key];\n }\n\n if (Object.keys(this.store[name] ?? {}).length === 0) {\n // if name is empty, delete it\n delete this.store[name];\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.store[name]?.[key]?.data != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const store = this.store[name] ?? {};\n const keys = Object.keys(store);\n if (filter) {\n return keys.filter((key) => key.startsWith(filter));\n }\n return keys;\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n\n // Clear all timeouts before clearing the store\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n const existing = this.store[name][key]?.data;\n let current = 0;\n\n if (existing) {\n try {\n current = this.deserialize<number>(existing);\n } catch {\n // Fallback for raw bytes without type marker\n const str = new TextDecoder().decode(existing);\n current = Number.parseInt(str, 10) || 0;\n }\n }\n\n const newValue = current + amount;\n this.store[name][key] ??= {};\n this.store[name][key].data = this.serialize(newValue);\n\n return newValue;\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Test utilities\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Get cache statistics (hits, misses, sets, deletes).\n *\n * @example\n * ```typescript\n * expect(cache.stats().hits).toBe(1);\n * expect(cache.stats().misses).toBe(0);\n * ```\n */\n public stats(): MemoryCacheStats {\n return { ...this._stats };\n }\n\n /**\n * Check if a key was set during the test.\n *\n * @example\n * ```typescript\n * expect(cache.wasSet(\"my-cache\", \"user:123\")).toBe(true);\n * ```\n */\n public wasSet(name: string, key?: string): boolean {\n if (key === undefined) {\n return this.setCalls.some((call) => call.name === name);\n }\n return this.setCalls.some((call) => call.name === name && call.key === key);\n }\n\n /**\n * Check if a key was retrieved during the test.\n *\n * @example\n * ```typescript\n * expect(cache.wasGet(\"my-cache\", \"user:123\")).toBe(true);\n * ```\n */\n public wasGet(name: string, key?: string): boolean {\n if (key === undefined) {\n return this.getCalls.some((call) => call.name === name);\n }\n return this.getCalls.some((call) => call.name === name && call.key === key);\n }\n\n /**\n * Check if a key was deleted during the test.\n *\n * @example\n * ```typescript\n * expect(cache.wasDeleted(\"my-cache\", \"user:123\")).toBe(true);\n * ```\n */\n public wasDeleted(name: string, key?: string): boolean {\n if (key === undefined) {\n return this.delCalls.some((call) => call.name === name);\n }\n return this.delCalls.some(\n (call) => call.name === name && call.keys.includes(key),\n );\n }\n\n /**\n * Get the number of cached entries for a specific cache name.\n *\n * @example\n * ```typescript\n * expect(cache.size(\"my-cache\")).toBe(5);\n * ```\n */\n public size(name?: string): number {\n if (name === undefined) {\n return Object.values(this.store).reduce(\n (total, entries) => total + Object.keys(entries).length,\n 0,\n );\n }\n return Object.keys(this.store[name] ?? {}).length;\n }\n\n /**\n * Get all cache names.\n *\n * @example\n * ```typescript\n * expect(cache.names()).toContain(\"my-cache\");\n * ```\n */\n public names(): string[] {\n return Object.keys(this.store);\n }\n\n /**\n * Reset all in-memory state (useful between tests).\n *\n * @example\n * ```typescript\n * beforeEach(() => {\n * cache.reset();\n * });\n * ```\n */\n public reset(): void {\n // Clear all timeouts\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n this.getCalls = [];\n this.setCalls = [];\n this.delCalls = [];\n this._stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };\n this.getError = null;\n this.setError = null;\n this.delError = null;\n }\n}\n","import {\n $atom,\n $inject,\n $state,\n AlephaError,\n createPrimitive,\n KIND,\n type MiddlewareMetadata,\n OPTIONS,\n Primitive,\n type Service,\n type Static,\n t,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { CacheProvider } from \"../providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"../providers/MemoryCacheProvider.ts\";\n\n/**\n * Creates a cache primitive for caching with automatic management.\n *\n * **Middleware mode** (no `handler`) — usable in `use` arrays AND as a store:\n * ```ts\n * class UserService {\n * userCache = $cache({ name: \"users\", ttl: [10, \"minutes\"] });\n *\n * fetchUser = $pipeline({\n * use: [this.userCache],\n * handler: async (userId: string) => this.repo.getById(userId),\n * });\n *\n * async invalidateUser(userId: string) {\n * await this.userCache.invalidate(userId);\n * }\n * }\n * ```\n *\n * **Primitive mode** (with `handler`) — standalone callable:\n * ```ts\n * getUserData = $cache({\n * name: \"user-data\",\n * ttl: [10, \"minutes\"],\n * handler: async (userId: string) => {\n * return await database.users.findById(userId);\n * }\n * });\n * ```\n */\nexport function $cache<TReturn = string, TParameter extends any[] = any[]>(\n options: CachePrimitiveOptions<TReturn, TParameter> & {\n handler: (...args: TParameter) => TReturn;\n },\n): CachePrimitiveFn<TReturn, TParameter>;\nexport function $cache<TReturn = any, TParameter extends any[] = any[]>(\n options?: CachePrimitiveOptions<TReturn, TParameter>,\n): CacheMiddlewareFn<TReturn>;\nexport function $cache(options: any = {}): any {\n const instance = createPrimitive(CachePrimitive, options);\n\n if (options.handler) {\n const fn = (...args: any[]): Promise<any> => instance.run(...args);\n return Object.setPrototypeOf(fn, instance);\n }\n\n // Middleware mode: callable as (handler) => wrappedHandler, with store methods\n const mw: any = <T extends (...args: any[]) => any>(handler: T): T => {\n return (async (...args: any[]) => {\n const key = instance.key(...args);\n const read = await instance.read(key);\n\n if (read.value !== undefined) {\n if (read.stale) {\n instance.scheduleRefresh(key, () => handler(...args));\n }\n return read.value;\n }\n\n const result = await instance.runSingleFlight(key, () =>\n handler(...args),\n );\n // Fire-and-forget — cache write failures must not break the handler result\n instance.set(key, result).catch(() => {});\n return result;\n }) as any as T;\n };\n\n mw[OPTIONS] = {\n name: \"$cache\",\n options: options as unknown as Record<string, unknown>,\n } satisfies MiddlewareMetadata;\n\n return Object.setPrototypeOf(mw, instance);\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Options for the in-memory L1 tier.\n */\nexport interface CacheMemoryTierOptions {\n /**\n * TTL for the in-memory tier. Should be ≤ the remote `ttl`.\n * Bounds the cross-isolate staleness window after invalidation.\n *\n * @default min(ttl, 30s)\n */\n ttl?: DurationLike;\n\n /**\n * LRU bound — max entries kept in memory before eviction.\n *\n * @default 500\n */\n max?: number;\n\n /**\n * Also cache provider misses (`undefined`) in memory for this duration.\n * Prevents hammering the remote tier on cold/unknown keys.\n *\n * @default off\n */\n negative?: DurationLike;\n}\n\nexport interface CachePrimitiveOptions<\n TReturn = any,\n TParameter extends any[] = any[],\n> {\n /**\n * The cache name. This is useful for invalidating multiple caches at once.\n *\n * Store key as `cache:$name:$key`.\n *\n * @default Name of the key of the class.\n */\n name?: string;\n\n /**\n * Function which returns cached data.\n */\n handler?: (...args: TParameter) => TReturn;\n\n /**\n * The key generator for the cache.\n * If not provided, the arguments will be json.stringify().\n */\n key?: (...args: TParameter) => string;\n\n /**\n * The store provider for the cache.\n *\n * Accepts:\n * - `\"memory\"` — short-circuits to {@link MemoryCacheProvider} regardless\n * of the default `CacheProvider` binding. Useful for caches that must\n * stay process-local (e.g. ETag, HTTP client).\n * - A {@link CacheProvider} class (concrete OR abstract) — resolved via\n * `alepha.inject(...)` at primitive construction. Use this to opt a\n * specific cache into a non-default backend (e.g.\n * `provider: DatabaseCacheProvider` to keep one cache in SQL while the\n * rest of the app uses Cloudflare KV).\n * - `undefined` — falls back to whatever is bound to `CacheProvider` in\n * the container. On Cloudflare workers this is\n * {@link CloudflareKVProvider} by default; on Node it's\n * {@link MemoryCacheProvider}.\n *\n * Note: passing an *abstract* class works because Alepha's DI resolves\n * through substitutions, e.g. `alepha.with({ provide: CacheProvider, use:\n * MyCacheProvider })`.\n */\n provider?: Service<CacheProvider> | \"memory\";\n\n /**\n * The time-to-live for the cache in seconds.\n * Set 0 to skip expiration.\n *\n * @default 300 (5 minutes).\n */\n ttl?: DurationLike;\n\n /**\n * If the cache is disabled.\n */\n disabled?: boolean;\n\n /**\n * Enable gzip compression for cached values.\n * Reduces storage size by 60-80% for JSON payloads at the cost of CPU.\n */\n compress?: boolean;\n\n /**\n * Add an in-process L1 memory tier in front of `provider`.\n *\n * Reads check memory first, fall back to the provider on miss. Writes go\n * to both tiers (write-through), so own-writes are immediately visible.\n *\n * Caveats:\n * - Per-process only. Each Worker isolate / Node process has its own L1.\n * `invalidate()` clears the local L1 + the remote provider; other\n * processes keep their L1 until its TTL expires.\n * - Use a short L1 TTL to bound the cross-isolate staleness window.\n *\n * @default off\n */\n memory?: true | CacheMemoryTierOptions;\n\n /**\n * Stale-while-revalidate window. After `ttl` expires, the cached value\n * remains servable for `stale` longer; reads in this window return the\n * stale value immediately and trigger ONE background refresh\n * (single-flight per key).\n *\n * Requires a `handler` (primitive mode) OR middleware mode wrapping a\n * handler — the cache needs to know how to recompute.\n *\n * @default off\n */\n stale?: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cache configuration atom.\n */\nexport const cacheOptions = $atom({\n name: \"alepha.cache.options\",\n schema: t.object({\n enabled: t.boolean({\n default: true,\n description: \"Whether caching is enabled.\",\n }),\n defaultTtl: t.number({\n default: 300,\n description: \"Default time to live for cache entries in seconds.\",\n }),\n }),\n default: {\n enabled: true,\n defaultTtl: 300,\n },\n});\n\nexport type CacheAtomOptions = Static<typeof cacheOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [cacheOptions.key]: CacheAtomOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst DEFAULT_MEMORY_MAX = 500;\nconst DEFAULT_MEMORY_TTL_MS = 30_000;\nconst SWR_MARKER = \"__swr\" as const;\n\ntype SwrEnvelope = {\n [SWR_MARKER]: 1;\n v: unknown;\n f: number;\n};\n\ntype L1Entry<T> = {\n value: T | undefined;\n expiresAt: number;\n negative: boolean;\n};\n\ntype ReadResult<T> = {\n value: T | undefined;\n stale: boolean;\n};\n\nexport class CachePrimitive<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {\n protected readonly settings = $state(cacheOptions);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n public readonly provider = this.$provider();\n\n protected readonly memoryStore?: Map<string, L1Entry<TReturn>>;\n protected readonly memoryMax: number = DEFAULT_MEMORY_MAX;\n protected readonly memoryTtlMs: number = 0;\n protected readonly negativeTtlMs: number = 0;\n\n protected readonly inflightRefreshes = new Map<string, Promise<TReturn>>();\n\n constructor(\n args: ConstructorParameters<\n typeof Primitive<CachePrimitiveOptions<TReturn, TParameter>>\n >[0],\n ) {\n super(args);\n const mem = this.options.memory;\n if (mem) {\n this.memoryStore = new Map();\n const memOpts: CacheMemoryTierOptions = mem === true ? {} : mem;\n this.memoryMax = memOpts.max ?? DEFAULT_MEMORY_MAX;\n // Default L1 TTL: min(remote ttl, 30s). If remote ttl is 0/infinite, use 30s.\n if (memOpts.ttl !== undefined) {\n this.memoryTtlMs = this.dateTimeProvider\n .duration(memOpts.ttl)\n .as(\"milliseconds\");\n } else {\n const remoteTtlMs = this.options.ttl\n ? this.dateTimeProvider.duration(this.options.ttl).as(\"milliseconds\")\n : 0;\n this.memoryTtlMs =\n remoteTtlMs > 0\n ? Math.min(remoteTtlMs, DEFAULT_MEMORY_TTL_MS)\n : DEFAULT_MEMORY_TTL_MS;\n }\n if (memOpts.negative !== undefined) {\n this.negativeTtlMs = this.dateTimeProvider\n .duration(memOpts.negative)\n .as(\"milliseconds\");\n }\n }\n }\n\n public get container(): string {\n return (\n this.options.name ??\n `${this.config.service.name}:${this.config.propertyKey}`\n );\n }\n\n public async run(...args: TParameter): Promise<TReturn> {\n const handler = this.options.handler;\n if (!handler) {\n throw new AlephaError(\"Cache handler is not defined.\");\n }\n\n const key = this.key(...args);\n const read = await this.read(key);\n\n if (read.value !== undefined) {\n if (read.stale) {\n this.scheduleRefresh(key, () => handler(...args));\n }\n return read.value;\n }\n\n const result = await this.runSingleFlight(key, () => handler(...args));\n // note: when exception occurs, don't cache the result\n await this.set(key, result);\n return result;\n }\n\n public key(...args: TParameter): string {\n return this.options.key ? this.options.key(...args) : JSON.stringify(args);\n }\n\n public async incr(key: string, amount = 1): Promise<number> {\n const result = await this.provider.incr(this.container, key, amount);\n // L1 is no longer authoritative after atomic incr on remote.\n this.delL1(key);\n return result;\n }\n\n public async invalidate(...keys: string[]): Promise<void> {\n if (this.memoryStore) {\n if (keys.length === 0) {\n this.memoryStore.clear();\n } else {\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const prefix = key.slice(0, -1);\n for (const k of this.memoryStore.keys()) {\n if (k.startsWith(prefix)) this.memoryStore.delete(k);\n }\n } else {\n this.memoryStore.delete(key);\n }\n }\n }\n }\n await this.provider.invalidateKeys(this.container, keys);\n }\n\n public async set(\n key: string,\n value: TReturn,\n ttl?: DurationLike,\n ): Promise<void> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.settings.enabled\n ) {\n return;\n }\n\n const freshTtlMs = this.dateTimeProvider\n .duration(\n ttl ?? this.options.ttl ?? [this.settings.defaultTtl, \"seconds\"],\n )\n .as(\"milliseconds\");\n\n const staleMs = this.options.stale\n ? this.dateTimeProvider.duration(this.options.stale).as(\"milliseconds\")\n : 0;\n\n const providerTtlMs = freshTtlMs > 0 ? freshTtlMs + staleMs : 0;\n const now = this.dateTimeProvider.nowMillis();\n const freshUntil = freshTtlMs > 0 ? now + freshTtlMs : 0;\n\n const payload =\n this.options.stale && freshTtlMs > 0\n ? ({ [SWR_MARKER]: 1, v: value, f: freshUntil } satisfies SwrEnvelope)\n : value;\n\n await this.provider.setTyped(this.container, key, payload, {\n ttl: providerTtlMs > 0 ? providerTtlMs : undefined,\n compress: this.options.compress,\n });\n\n // Write-through to L1 (raw value, not wrapped).\n this.setL1(key, {\n value,\n expiresAt:\n this.memoryTtlMs > 0\n ? now + this.memoryTtlMs\n : Number.POSITIVE_INFINITY,\n negative: false,\n });\n\n // A fresh write supersedes any pending refresh for this key.\n this.inflightRefreshes.delete(key);\n\n await this.alepha.events.emit(\"cache:set\", {\n container: this.container,\n key,\n ttlMs: providerTtlMs > 0 ? providerTtlMs : undefined,\n });\n }\n\n public async get(key: string): Promise<TReturn | undefined> {\n const read = await this.read(key);\n return read.value;\n }\n\n /**\n * Internal read that also reports whether the value is stale (SWR\n * grace window). Middleware and `run()` use this to decide whether to\n * schedule a background refresh.\n */\n public async read(key: string): Promise<ReadResult<TReturn>> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.settings.enabled\n ) {\n return { value: undefined, stale: false };\n }\n\n const now = this.dateTimeProvider.nowMillis();\n\n // L1 check\n if (this.memoryStore) {\n const entry = this.memoryStore.get(key);\n if (entry !== undefined) {\n if (entry.expiresAt > now) {\n // LRU touch\n this.memoryStore.delete(key);\n this.memoryStore.set(key, entry);\n await this.alepha.events.emit(\"cache:hit\", {\n container: this.container,\n key,\n });\n return {\n value: entry.negative ? undefined : entry.value,\n stale: false,\n };\n }\n this.memoryStore.delete(key);\n }\n }\n\n // L2 check\n const raw = await this.provider.getTyped<TReturn | SwrEnvelope>(\n this.container,\n key,\n );\n\n if (raw === undefined) {\n // Negative caching\n if (this.memoryStore && this.negativeTtlMs > 0) {\n this.setL1(key, {\n value: undefined,\n expiresAt: now + this.negativeTtlMs,\n negative: true,\n });\n }\n await this.alepha.events.emit(\"cache:miss\", {\n container: this.container,\n key,\n });\n return { value: undefined, stale: false };\n }\n\n let value: TReturn;\n let stale = false;\n\n if (this.isSwrEnvelope(raw)) {\n value = raw.v as TReturn;\n stale = raw.f > 0 && raw.f <= now;\n } else {\n value = raw as TReturn;\n }\n\n // Populate L1 (write-back from L2 read)\n if (this.memoryStore && this.memoryTtlMs > 0) {\n this.setL1(key, {\n value,\n expiresAt: now + this.memoryTtlMs,\n negative: false,\n });\n }\n\n await this.alepha.events.emit(stale ? \"cache:stale\" : \"cache:hit\", {\n container: this.container,\n key,\n });\n\n return { value, stale };\n }\n\n /**\n * Run a handler under single-flight: concurrent callers for the same\n * key share one in-flight promise.\n */\n public async runSingleFlight(\n key: string,\n handler: () => Promise<TReturn> | TReturn,\n ): Promise<TReturn> {\n const existing = this.inflightRefreshes.get(key);\n if (existing) {\n return existing;\n }\n const promise = (async () => {\n try {\n return await handler();\n } finally {\n this.inflightRefreshes.delete(key);\n }\n })();\n this.inflightRefreshes.set(key, promise);\n return promise;\n }\n\n /**\n * Schedule a background refresh for a stale key. At most one refresh\n * per key is in-flight at any time; failures are swallowed (the stale\n * value keeps being served until expiry).\n */\n public scheduleRefresh(\n key: string,\n handler: () => Promise<TReturn> | TReturn,\n ): void {\n if (this.inflightRefreshes.has(key)) {\n return;\n }\n const promise = (async () => {\n try {\n const result = await handler();\n await this.set(key, result);\n await this.alepha.events.emit(\"cache:revalidate\", {\n container: this.container,\n key,\n });\n return result;\n } finally {\n this.inflightRefreshes.delete(key);\n }\n })();\n promise.catch(() => {\n // swallow: stale value keeps serving until expiry\n });\n this.inflightRefreshes.set(key, promise);\n }\n\n protected $provider(): CacheProvider {\n if (!this.options.provider) {\n return this.alepha.inject(CacheProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryCacheProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n\n protected isSwrEnvelope(value: unknown): value is SwrEnvelope {\n return (\n value !== null &&\n typeof value === \"object\" &&\n (value as Record<string, unknown>)[SWR_MARKER] === 1 &&\n \"f\" in (value as Record<string, unknown>) &&\n \"v\" in (value as Record<string, unknown>)\n );\n }\n\n protected setL1(key: string, entry: L1Entry<TReturn>): void {\n if (!this.memoryStore) return;\n if (this.memoryStore.has(key)) {\n this.memoryStore.delete(key);\n }\n this.memoryStore.set(key, entry);\n while (this.memoryStore.size > this.memoryMax) {\n const first = this.memoryStore.keys().next().value;\n if (first === undefined) break;\n this.memoryStore.delete(first);\n }\n }\n\n protected delL1(key: string): void {\n this.memoryStore?.delete(key);\n }\n}\n\nexport interface CachePrimitiveFn<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends CachePrimitive<TReturn, TParameter> {\n /**\n * Run the cache primitive with the provided arguments.\n */\n (...args: TParameter): Promise<TReturn>;\n}\n\n/**\n * Cache middleware + store. Callable as middleware `(handler) => wrappedHandler`\n * AND exposes store methods (`.get()`, `.set()`, `.invalidate()`, `.incr()`).\n */\nexport interface CacheMiddlewareFn<TReturn = any>\n extends CachePrimitive<TReturn, any[]> {\n <T extends (...args: any[]) => any>(handler: T): T;\n [OPTIONS]?: MiddlewareMetadata;\n}\n\n$cache[KIND] = CachePrimitive;\n","import { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { CachePrimitive } from \"../primitives/$cache.ts\";\nimport { CacheProvider } from \"./CacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * KVNamespace interface matching Cloudflare's KV API.\n */\nexport interface KVNamespace {\n get(key: string, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n get(key: string, type: \"text\"): Promise<string | null>;\n get(key: string, type?: \"text\"): Promise<string | null>;\n put(\n key: string,\n value: string | ArrayBuffer | ReadableStream,\n options?: KVPutOptions,\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KVListOptions): Promise<KVListResult>;\n}\n\nexport interface KVPutOptions {\n expiration?: number;\n expirationTtl?: number;\n metadata?: unknown;\n}\n\nexport interface KVListOptions {\n prefix?: string;\n limit?: number;\n cursor?: string;\n}\n\nexport interface KVListResult {\n keys: KVListKey[];\n list_complete: boolean;\n cursor?: string;\n}\n\nexport interface KVListKey {\n name: string;\n expiration?: number;\n metadata?: unknown;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Default KV binding name used in wrangler configuration.\n */\nexport const KV_DEFAULT_BINDING = \"KV_CACHE\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cloudflare KV cache provider.\n *\n * Uses a KV namespace binding for all cache operations.\n * Keys are stored as: `cache:{name}:{key}`\n *\n * **Required Cloudflare binding:**\n * - `KV_CACHE` - A KV namespace binding in wrangler configuration\n *\n * @example\n * ```toml\n * # wrangler.toml - automatically generated by alepha build\n * [[kv_namespaces]]\n * binding = \"KV_CACHE\"\n * id = \"abc123\"\n * ```\n */\nexport class CloudflareKVProvider extends CacheProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n\n protected kv?: KVNamespace;\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n const caches = this.alepha\n .primitives<CachePrimitive>(\"cache\")\n .filter((it) => it.provider === this);\n\n if (caches.length === 0) {\n this.log.info(\n \"CloudflareKVProvider is registered but no cache primitives are using it. Skipping KV initialization.\",\n );\n return;\n }\n\n const cloudflareEnv = this.alepha.get(\"cloudflare.env\") as\n | Record<string, unknown>\n | undefined;\n if (!cloudflareEnv) {\n throw new AlephaError(\n \"Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.\",\n );\n }\n\n const binding = cloudflareEnv[KV_DEFAULT_BINDING] as\n | KVNamespace\n | undefined;\n if (!binding) {\n throw new AlephaError(\n `KV binding '${KV_DEFAULT_BINDING}' not found in Cloudflare Workers environment.`,\n );\n }\n\n this.kv = binding;\n\n this.log.info(\"Cloudflare KV cache OK\");\n },\n });\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n if (!this.alepha.isStarted()) {\n return;\n }\n\n const kvKey = this.prefix(name, key);\n const buffer = await this.getKV().get(kvKey, \"arrayBuffer\");\n if (!buffer) {\n return;\n }\n\n this.log.debug(\"Cache hit\", {\n size: buffer.byteLength,\n key: kvKey,\n });\n\n return new Uint8Array(buffer);\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (!this.alepha.isReady()) {\n return new Uint8Array(value);\n }\n\n const kvKey = this.prefix(name, key);\n const options: KVPutOptions = {};\n\n if (ttl) {\n // KV expects TTL in seconds, we receive milliseconds\n options.expirationTtl = Math.max(60, Math.ceil(ttl / 1000));\n }\n\n await this.getKV().put(\n kvKey,\n value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength,\n ) as ArrayBuffer,\n options,\n );\n return value;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n const kv = this.getKV();\n\n if (keys.length === 0) {\n // Delete all keys under this cache name\n const prefix = this.prefix(name);\n const allKeys = await this.listAllKeys(`${prefix}:`);\n for (const k of allKeys) {\n await kv.delete(k);\n }\n return;\n }\n\n const nameKey = this.prefix(name);\n for (const key of keys) {\n const fullKey = key.startsWith(nameKey) ? key : this.prefix(name, key);\n await kv.delete(fullKey);\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n const kvKey = this.prefix(name, key);\n const value = await this.getKV().get(kvKey, \"text\");\n return value !== null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const prefix = filter\n ? `${this.prefix(name)}:${filter}`\n : `${this.prefix(name)}:`;\n\n return this.listAllKeys(prefix);\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n const kv = this.getKV();\n const allKeys = await this.listAllKeys(\"cache:\");\n for (const key of allKeys) {\n await kv.delete(key);\n }\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n const kvKey = this.prefix(name, key);\n const kv = this.getKV();\n\n const existing = await kv.get(kvKey, \"text\");\n let current = 0;\n\n if (existing !== null) {\n current = Number.parseInt(existing, 10) || 0;\n }\n\n const newValue = current + amount;\n await kv.put(kvKey, String(newValue));\n return newValue;\n }\n\n /**\n * Build the full KV key: `cache:{name}:{key}`\n */\n protected prefix(...path: string[]): string {\n return [\"cache\", ...path].join(\":\");\n }\n\n protected getKV(): KVNamespace {\n if (!this.kv) {\n throw new AlephaError(\n \"KV namespace not initialized. Call start() first.\",\n );\n }\n return this.kv;\n }\n\n /**\n * List all keys matching a prefix, handling pagination.\n */\n protected async listAllKeys(prefix: string): Promise<string[]> {\n const kv = this.getKV();\n const allKeys: string[] = [];\n let cursor: string | undefined;\n\n do {\n const result = await kv.list({\n prefix,\n cursor,\n });\n\n for (const key of result.keys) {\n allKeys.push(key.name);\n }\n\n cursor = result.list_complete ? undefined : result.cursor;\n } while (cursor);\n\n return allKeys;\n }\n}\n","import { $module } from \"alepha\";\nimport { $cache } from \"./primitives/$cache.ts\";\nimport { CacheProvider } from \"./providers/CacheProvider.ts\";\nimport { CloudflareKVProvider } from \"./providers/CloudflareKVProvider.ts\";\nimport { MemoryCacheProvider } from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cache.ts\";\nexport * from \"./providers/CacheProvider.ts\";\nexport * from \"./providers/CloudflareKVProvider.ts\";\nexport * from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaCache = $module({\n name: \"alepha.cache\",\n primitives: [$cache],\n services: [CacheProvider],\n variants: [MemoryCacheProvider, CloudflareKVProvider],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: CacheProvider,\n use: CloudflareKVProvider,\n });\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.workerd.js","names":[],"sources":["../../../src/cache/core/errors/CacheError.ts","../../../src/cache/core/providers/CacheProvider.ts","../../../src/cache/core/providers/MemoryCacheProvider.ts","../../../src/cache/core/primitives/$cache.ts","../../../src/cache/core/providers/CloudflareKVProvider.ts","../../../src/cache/core/index.workerd.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class CacheError extends AlephaError {}\n","import { CacheError } from \"../errors/CacheError.ts\";\n\n/**\n * Cache provider interface.\n *\n * All methods are asynchronous and return promises.\n * Values are stored as Uint8Array.\n */\nexport abstract class CacheProvider {\n protected encoder: TextEncoder = new TextEncoder();\n protected decoder: TextDecoder = new TextDecoder();\n protected codes = {\n BINARY: 0x01,\n JSON: 0x02,\n STRING: 0x03,\n COMPRESSED: 0x04,\n };\n\n /**\n * Get the value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to get.\n *\n * @return The value of the key, or undefined if the key does not exist.\n */\n public abstract get(\n name: string,\n key: string,\n ): Promise<Uint8Array | undefined>;\n\n /**\n * Set the string value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to set.\n * @param value The value to set.\n * @param ttl The time-to-live of the key, in milliseconds.\n *\n * @return The value of the key.\n */\n public abstract set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array>;\n\n /**\n * Remove the specified keys.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param keys The keys to delete.\n */\n public abstract del(name: string, ...keys: string[]): Promise<void>;\n\n public abstract has(name: string, key: string): Promise<boolean>;\n\n public abstract keys(name: string, filter?: string): Promise<string[]>;\n\n /**\n * Remove all keys from all cache names.\n */\n public abstract clear(): Promise<void>;\n\n /**\n * Increment the integer value of a key by the given amount.\n *\n * If the key does not exist, it is set to 0 before performing the operation.\n * This operation is atomic when using Redis.\n *\n * @param name Cache name, used to group keys.\n * @param key The key to increment.\n * @param amount The amount to increment by.\n * @returns The new value after incrementing.\n */\n public abstract incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number>;\n\n // ---------------------------------------------------------------------------\n // High-level methods — serialize/compress layer used by CachePrimitive\n // ---------------------------------------------------------------------------\n\n /**\n * Set a typed value with automatic serialization and optional compression.\n */\n public async setTyped(\n name: string,\n key: string,\n value: unknown,\n options?: { ttl?: number; compress?: boolean },\n ): Promise<void> {\n let data = this.serialize(value);\n if (options?.compress) {\n data = await this.compress(data);\n }\n await this.set(name, key, data, options?.ttl);\n }\n\n /**\n * Get a typed value with automatic deserialization and optional decompression.\n */\n public async getTyped<T>(name: string, key: string): Promise<T | undefined> {\n const data = await this.get(name, key);\n if (data) {\n if (data[0] === this.codes.COMPRESSED) {\n const decompressed = await this.decompress(data.subarray(1));\n return this.deserialize<T>(decompressed);\n }\n return this.deserialize<T>(data);\n }\n return undefined;\n }\n\n /**\n * Invalidate cache keys with wildcard support.\n *\n * Keys ending in `*` are expanded via `this.keys()`.\n * Called with no keys, delegates to `this.del(name)` which clears the entire container.\n */\n public async invalidateKeys(name: string, keys: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const result = await this.keys(name, key.slice(0, -1));\n keysToDelete.push(...result);\n } else {\n keysToDelete.push(key);\n }\n }\n\n await this.del(name, ...keysToDelete);\n }\n\n /**\n * Serialize a value to a typed Uint8Array with a leading type marker byte.\n */\n protected serialize(value: unknown): Uint8Array {\n if (value instanceof Uint8Array) {\n const result = new Uint8Array(1 + value.length);\n result[0] = this.codes.BINARY;\n result.set(value, 1);\n return result;\n }\n\n const encoded = this.encoder.encode(\n typeof value === \"string\" ? value : JSON.stringify(value),\n );\n const code =\n typeof value === \"string\" ? this.codes.STRING : this.codes.JSON;\n const result = new Uint8Array(1 + encoded.length);\n result[0] = code;\n result.set(encoded, 1);\n return result;\n }\n\n /**\n * Deserialize a typed Uint8Array back to the original value.\n */\n protected deserialize<T>(uint8Array: Uint8Array): T {\n const type = uint8Array[0];\n const payload = uint8Array.slice(1);\n\n if (type === this.codes.BINARY) {\n return payload as T;\n }\n if (type === this.codes.JSON) {\n return JSON.parse(this.decoder.decode(payload)) as T;\n }\n if (type === this.codes.STRING) {\n return this.decoder.decode(payload) as T;\n }\n\n throw new CacheError(`Unknown serialization type: ${type}`);\n }\n\n /**\n * Compress data with gzip, prepending a COMPRESSED marker byte.\n */\n protected async compress(data: Uint8Array): Promise<Uint8Array> {\n const buf = (data.buffer as ArrayBuffer).slice(\n data.byteOffset,\n data.byteOffset + data.byteLength,\n );\n const compressed = new Uint8Array(\n await new Response(\n new Blob([buf]).stream().pipeThrough(new CompressionStream(\"gzip\")),\n ).arrayBuffer(),\n );\n const result = new Uint8Array(1 + compressed.length);\n result[0] = this.codes.COMPRESSED;\n result.set(compressed, 1);\n return result;\n }\n\n /**\n * Decompress gzipped data.\n */\n protected async decompress(data: Uint8Array): Promise<Uint8Array> {\n const buf = (data.buffer as ArrayBuffer).slice(\n data.byteOffset,\n data.byteOffset + data.byteLength,\n );\n return new Uint8Array(\n await new Response(\n new Blob([buf]).stream().pipeThrough(new DecompressionStream(\"gzip\")),\n ).arrayBuffer(),\n );\n }\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider, type Timeout } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { CacheProvider } from \"./CacheProvider.ts\";\n\ntype CacheName = string;\ntype CacheKey = string;\ntype CacheValue = {\n data?: Uint8Array;\n timeout?: Timeout;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface MemoryCacheCall {\n name: string;\n key: string;\n timestamp: number;\n}\n\nexport interface MemoryCacheSetCall extends MemoryCacheCall {\n value: Uint8Array;\n ttl?: number;\n}\n\nexport interface MemoryCacheDelCall {\n name: string;\n keys: string[];\n timestamp: number;\n}\n\nexport interface MemoryCacheStats {\n hits: number;\n misses: number;\n sets: number;\n deletes: number;\n}\n\nexport interface MemoryCacheProviderOptions {\n /**\n * Error to throw on get operations (for testing error handling)\n */\n getError?: Error | null;\n /**\n * Error to throw on set operations (for testing error handling)\n */\n setError?: Error | null;\n /**\n * Error to throw on del operations (for testing error handling)\n */\n delError?: Error | null;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * In-memory implementation of CacheProvider for testing.\n *\n * This provider stores all cache entries in memory, making it ideal for\n * unit tests that need to verify cache operations without touching Redis or other backends.\n *\n * @example\n * ```typescript\n * // In tests, substitute the real CacheProvider with MemoryCacheProvider\n * const alepha = Alepha.create().with({\n * provide: CacheProvider,\n * use: MemoryCacheProvider,\n * });\n *\n * // Run code that uses caching\n * const service = alepha.inject(MyService);\n * await service.fetchWithCache(\"key\");\n *\n * // Verify cache behavior\n * const cache = alepha.inject(MemoryCacheProvider);\n * expect(cache.stats().misses).toBe(1);\n * await service.fetchWithCache(\"key\");\n * expect(cache.stats().hits).toBe(1);\n * ```\n */\nexport class MemoryCacheProvider extends CacheProvider {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n\n protected store: Record<CacheName, Record<CacheKey, CacheValue>> = {};\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Test tracking\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * All recorded get calls.\n */\n public getCalls: MemoryCacheCall[] = [];\n\n /**\n * All recorded set calls.\n */\n public setCalls: MemoryCacheSetCall[] = [];\n\n /**\n * All recorded del calls.\n */\n public delCalls: MemoryCacheDelCall[] = [];\n\n /**\n * Cache statistics.\n */\n protected _stats: MemoryCacheStats = {\n hits: 0,\n misses: 0,\n sets: 0,\n deletes: 0,\n };\n\n /**\n * Error to throw on get (for testing error handling)\n */\n public getError: Error | null = null;\n\n /**\n * Error to throw on set (for testing error handling)\n */\n public setError: Error | null = null;\n\n /**\n * Error to throw on del (for testing error handling)\n */\n public delError: Error | null = null;\n\n constructor(options: MemoryCacheProviderOptions = {}) {\n super();\n this.getError = options.getError ?? null;\n this.setError = options.setError ?? null;\n this.delError = options.delError ?? null;\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // CacheProvider implementation\n // ─────────────────────────────────────────────────────────────────────────────\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n this.getCalls.push({\n name,\n key,\n timestamp: this.dateTimeProvider.nowMillis(),\n });\n\n if (this.getError) {\n throw this.getError;\n }\n\n const data = this.store[name]?.[key]?.data;\n\n if (data !== undefined) {\n this._stats.hits++;\n } else {\n this._stats.misses++;\n }\n\n return data;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n this.setCalls.push({\n name,\n key,\n value,\n ttl,\n timestamp: this.dateTimeProvider.nowMillis(),\n });\n this._stats.sets++;\n\n if (this.setError) {\n throw this.setError;\n }\n\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n this.store[name][key] ??= {};\n this.store[name][key].data = value;\n\n this.log.debug(`Setting cache for name`, { name, key, ttl });\n\n // clear previous timeout if exists\n if (this.store[name][key].timeout) {\n this.dateTimeProvider.clearTimeout(this.store[name][key].timeout);\n this.store[name][key].timeout = undefined;\n }\n\n if (ttl) {\n this.store[name][key].timeout = this.dateTimeProvider.createTimeout(\n () => this.del(name, key),\n ttl,\n );\n }\n\n return this.store[name][key].data;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n this.delCalls.push({\n name,\n keys,\n timestamp: this.dateTimeProvider.nowMillis(),\n });\n this._stats.deletes++;\n\n if (this.delError) {\n throw this.delError;\n }\n\n // delete all keys in name\n if (keys.length === 0) {\n this.log.debug(`Deleting all cache for name`, { name });\n\n if (this.store[name]) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n delete this.store[name];\n return;\n }\n\n this.log.debug(`Deleting cache for name`, { name, keys });\n\n // delete specific keys in name\n for (const key of keys) {\n if (this.store[name] == null) break;\n\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n\n delete this.store[name][key];\n }\n\n if (Object.keys(this.store[name] ?? {}).length === 0) {\n // if name is empty, delete it\n delete this.store[name];\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.store[name]?.[key]?.data != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const store = this.store[name] ?? {};\n const keys = Object.keys(store);\n if (filter) {\n return keys.filter((key) => key.startsWith(filter));\n }\n return keys;\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n\n // Clear all timeouts before clearing the store\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n const existing = this.store[name][key]?.data;\n let current = 0;\n\n if (existing) {\n try {\n current = this.deserialize<number>(existing);\n } catch {\n // Fallback for raw bytes without type marker\n const str = new TextDecoder().decode(existing);\n current = Number.parseInt(str, 10) || 0;\n }\n }\n\n const newValue = current + amount;\n this.store[name][key] ??= {};\n this.store[name][key].data = this.serialize(newValue);\n\n return newValue;\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Test utilities\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Get cache statistics (hits, misses, sets, deletes).\n *\n * @example\n * ```typescript\n * expect(cache.stats().hits).toBe(1);\n * expect(cache.stats().misses).toBe(0);\n * ```\n */\n public stats(): MemoryCacheStats {\n return { ...this._stats };\n }\n\n /**\n * Check if a key was set during the test.\n *\n * @example\n * ```typescript\n * expect(cache.wasSet(\"my-cache\", \"user:123\")).toBe(true);\n * ```\n */\n public wasSet(name: string, key?: string): boolean {\n if (key === undefined) {\n return this.setCalls.some((call) => call.name === name);\n }\n return this.setCalls.some((call) => call.name === name && call.key === key);\n }\n\n /**\n * Check if a key was retrieved during the test.\n *\n * @example\n * ```typescript\n * expect(cache.wasGet(\"my-cache\", \"user:123\")).toBe(true);\n * ```\n */\n public wasGet(name: string, key?: string): boolean {\n if (key === undefined) {\n return this.getCalls.some((call) => call.name === name);\n }\n return this.getCalls.some((call) => call.name === name && call.key === key);\n }\n\n /**\n * Check if a key was deleted during the test.\n *\n * @example\n * ```typescript\n * expect(cache.wasDeleted(\"my-cache\", \"user:123\")).toBe(true);\n * ```\n */\n public wasDeleted(name: string, key?: string): boolean {\n if (key === undefined) {\n return this.delCalls.some((call) => call.name === name);\n }\n return this.delCalls.some(\n (call) => call.name === name && call.keys.includes(key),\n );\n }\n\n /**\n * Get the number of cached entries for a specific cache name.\n *\n * @example\n * ```typescript\n * expect(cache.size(\"my-cache\")).toBe(5);\n * ```\n */\n public size(name?: string): number {\n if (name === undefined) {\n return Object.values(this.store).reduce(\n (total, entries) => total + Object.keys(entries).length,\n 0,\n );\n }\n return Object.keys(this.store[name] ?? {}).length;\n }\n\n /**\n * Get all cache names.\n *\n * @example\n * ```typescript\n * expect(cache.names()).toContain(\"my-cache\");\n * ```\n */\n public names(): string[] {\n return Object.keys(this.store);\n }\n\n /**\n * Reset all in-memory state (useful between tests).\n *\n * @example\n * ```typescript\n * beforeEach(() => {\n * cache.reset();\n * });\n * ```\n */\n public reset(): void {\n // Clear all timeouts\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n this.getCalls = [];\n this.setCalls = [];\n this.delCalls = [];\n this._stats = { hits: 0, misses: 0, sets: 0, deletes: 0 };\n this.getError = null;\n this.setError = null;\n this.delError = null;\n }\n}\n","import {\n $atom,\n $inject,\n $state,\n AlephaError,\n createPrimitive,\n KIND,\n type MiddlewareMetadata,\n OPTIONS,\n Primitive,\n type Service,\n type Static,\n t,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { CacheProvider } from \"../providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"../providers/MemoryCacheProvider.ts\";\n\n/**\n * Creates a cache primitive for caching with automatic management.\n *\n * **Middleware mode** (no `handler`) — usable in `use` arrays AND as a store:\n * ```ts\n * class UserService {\n * userCache = $cache({ name: \"users\", ttl: [10, \"minutes\"] });\n *\n * fetchUser = $pipeline({\n * use: [this.userCache],\n * handler: async (userId: string) => this.repo.getById(userId),\n * });\n *\n * async invalidateUser(userId: string) {\n * await this.userCache.invalidate(userId);\n * }\n * }\n * ```\n *\n * **Primitive mode** (with `handler`) — standalone callable:\n * ```ts\n * getUserData = $cache({\n * name: \"user-data\",\n * ttl: [10, \"minutes\"],\n * handler: async (userId: string) => {\n * return await database.users.findById(userId);\n * }\n * });\n * ```\n */\nexport function $cache<TReturn = string, TParameter extends any[] = any[]>(\n options: CachePrimitiveOptions<TReturn, TParameter> & {\n handler: (...args: TParameter) => TReturn;\n },\n): CachePrimitiveFn<TReturn, TParameter>;\nexport function $cache<TReturn = any, TParameter extends any[] = any[]>(\n options?: CachePrimitiveOptions<TReturn, TParameter>,\n): CacheMiddlewareFn<TReturn>;\nexport function $cache(options: any = {}): any {\n const instance = createPrimitive(CachePrimitive, options);\n\n if (options.handler) {\n const fn = (...args: any[]): Promise<any> => instance.run(...args);\n return Object.setPrototypeOf(fn, instance);\n }\n\n // Middleware mode: callable as (handler) => wrappedHandler, with store methods\n const mw: any = <T extends (...args: any[]) => any>(handler: T): T => {\n return (async (...args: any[]) => {\n const key = instance.key(...args);\n const read = await instance.read(key);\n\n if (read.value !== undefined) {\n if (read.stale) {\n instance.scheduleRefresh(key, () => handler(...args));\n }\n return read.value;\n }\n\n const result = await instance.runSingleFlight(key, () =>\n handler(...args),\n );\n // Fire-and-forget — cache write failures must not break the handler result\n instance.set(key, result).catch(() => {});\n return result;\n }) as any as T;\n };\n\n mw[OPTIONS] = {\n name: \"$cache\",\n options: options as unknown as Record<string, unknown>,\n } satisfies MiddlewareMetadata;\n\n return Object.setPrototypeOf(mw, instance);\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Options for the in-memory L1 tier.\n */\nexport interface CacheMemoryTierOptions {\n /**\n * TTL for the in-memory tier. Should be ≤ the remote `ttl`.\n * Bounds the cross-isolate staleness window after invalidation.\n *\n * @default min(ttl, 30s)\n */\n ttl?: DurationLike;\n\n /**\n * LRU bound — max entries kept in memory before eviction.\n *\n * @default 500\n */\n max?: number;\n\n /**\n * Also cache provider misses (`undefined`) in memory for this duration.\n * Prevents hammering the remote tier on cold/unknown keys.\n *\n * @default off\n */\n negative?: DurationLike;\n}\n\nexport interface CachePrimitiveOptions<\n TReturn = any,\n TParameter extends any[] = any[],\n> {\n /**\n * The cache name. This is useful for invalidating multiple caches at once.\n *\n * Store key as `cache:$name:$key`.\n *\n * @default Name of the key of the class.\n */\n name?: string;\n\n /**\n * Function which returns cached data.\n */\n handler?: (...args: TParameter) => TReturn;\n\n /**\n * The key generator for the cache.\n * If not provided, the arguments will be json.stringify().\n */\n key?: (...args: TParameter) => string;\n\n /**\n * The store provider for the cache.\n *\n * Accepts:\n * - `\"memory\"` — short-circuits to {@link MemoryCacheProvider} regardless\n * of the default `CacheProvider` binding. Useful for caches that must\n * stay process-local (e.g. ETag, HTTP client).\n * - A {@link CacheProvider} class (concrete OR abstract) — resolved via\n * `alepha.inject(...)` at primitive construction. Use this to opt a\n * specific cache into a non-default backend (e.g.\n * `provider: DatabaseCacheProvider` to keep one cache in SQL while the\n * rest of the app uses Cloudflare KV).\n * - `undefined` — falls back to whatever is bound to `CacheProvider` in\n * the container. On Cloudflare workers this is\n * {@link CloudflareKVProvider} by default; on Node it's\n * {@link MemoryCacheProvider}.\n *\n * Note: passing an *abstract* class works because Alepha's DI resolves\n * through substitutions, e.g. `alepha.with({ provide: CacheProvider, use:\n * MyCacheProvider })`.\n */\n provider?: Service<CacheProvider> | \"memory\";\n\n /**\n * The time-to-live for the cache in seconds.\n * Set 0 to skip expiration.\n *\n * @default 300 (5 minutes).\n */\n ttl?: DurationLike;\n\n /**\n * If the cache is disabled.\n */\n disabled?: boolean;\n\n /**\n * Enable gzip compression for cached values.\n * Reduces storage size by 60-80% for JSON payloads at the cost of CPU.\n */\n compress?: boolean;\n\n /**\n * Add an in-process L1 memory tier in front of `provider`.\n *\n * Reads check memory first, fall back to the provider on miss. Writes go\n * to both tiers (write-through), so own-writes are immediately visible.\n *\n * Caveats:\n * - Per-process only. Each Worker isolate / Node process has its own L1.\n * `invalidate()` clears the local L1 + the remote provider; other\n * processes keep their L1 until its TTL expires.\n * - Use a short L1 TTL to bound the cross-isolate staleness window.\n *\n * @default off\n */\n memory?: true | CacheMemoryTierOptions;\n\n /**\n * Stale-while-revalidate window. After `ttl` expires, the cached value\n * remains servable for `stale` longer; reads in this window return the\n * stale value immediately and trigger ONE background refresh\n * (single-flight per key).\n *\n * Requires a `handler` (primitive mode) OR middleware mode wrapping a\n * handler — the cache needs to know how to recompute.\n *\n * @default off\n */\n stale?: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cache configuration atom.\n */\nexport const cacheOptions = $atom({\n name: \"alepha.cache.options\",\n schema: t.object({\n enabled: t.boolean({\n default: true,\n description: \"Whether caching is enabled.\",\n }),\n defaultTtl: t.number({\n default: 300,\n description: \"Default time to live for cache entries in seconds.\",\n }),\n }),\n default: {\n enabled: true,\n defaultTtl: 300,\n },\n});\n\nexport type CacheAtomOptions = Static<typeof cacheOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [cacheOptions.key]: CacheAtomOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst DEFAULT_MEMORY_MAX = 500;\nconst DEFAULT_MEMORY_TTL_MS = 30_000;\nconst SWR_MARKER = \"__swr\" as const;\n\ntype SwrEnvelope = {\n [SWR_MARKER]: 1;\n v: unknown;\n f: number;\n};\n\ntype L1Entry<T> = {\n value: T | undefined;\n expiresAt: number;\n negative: boolean;\n};\n\ntype ReadResult<T> = {\n value: T | undefined;\n stale: boolean;\n};\n\nexport class CachePrimitive<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {\n protected readonly settings = $state(cacheOptions);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n public readonly provider = this.$provider();\n\n protected readonly memoryStore?: Map<string, L1Entry<TReturn>>;\n protected readonly memoryMax: number = DEFAULT_MEMORY_MAX;\n protected readonly memoryTtlMs: number = 0;\n protected readonly negativeTtlMs: number = 0;\n\n protected readonly inflightRefreshes = new Map<string, Promise<TReturn>>();\n\n constructor(\n args: ConstructorParameters<\n typeof Primitive<CachePrimitiveOptions<TReturn, TParameter>>\n >[0],\n ) {\n super(args);\n const mem = this.options.memory;\n if (mem) {\n this.memoryStore = new Map();\n const memOpts: CacheMemoryTierOptions = mem === true ? {} : mem;\n this.memoryMax = memOpts.max ?? DEFAULT_MEMORY_MAX;\n // Default L1 TTL: min(remote ttl, 30s). If remote ttl is 0/infinite, use 30s.\n if (memOpts.ttl !== undefined) {\n this.memoryTtlMs = this.dateTimeProvider\n .duration(memOpts.ttl)\n .as(\"milliseconds\");\n } else {\n const remoteTtlMs = this.options.ttl\n ? this.dateTimeProvider.duration(this.options.ttl).as(\"milliseconds\")\n : 0;\n this.memoryTtlMs =\n remoteTtlMs > 0\n ? Math.min(remoteTtlMs, DEFAULT_MEMORY_TTL_MS)\n : DEFAULT_MEMORY_TTL_MS;\n }\n if (memOpts.negative !== undefined) {\n this.negativeTtlMs = this.dateTimeProvider\n .duration(memOpts.negative)\n .as(\"milliseconds\");\n }\n }\n }\n\n public get container(): string {\n return (\n this.options.name ??\n `${this.config.service.name}:${this.config.propertyKey}`\n );\n }\n\n public async run(...args: TParameter): Promise<TReturn> {\n const handler = this.options.handler;\n if (!handler) {\n throw new AlephaError(\"Cache handler is not defined.\");\n }\n\n const key = this.key(...args);\n const read = await this.read(key);\n\n if (read.value !== undefined) {\n if (read.stale) {\n this.scheduleRefresh(key, () => handler(...args));\n }\n return read.value;\n }\n\n const result = await this.runSingleFlight(key, () => handler(...args));\n // note: when exception occurs, don't cache the result\n await this.set(key, result);\n return result;\n }\n\n public key(...args: TParameter): string {\n return this.options.key ? this.options.key(...args) : JSON.stringify(args);\n }\n\n public async incr(key: string, amount = 1): Promise<number> {\n const result = await this.provider.incr(this.container, key, amount);\n // L1 is no longer authoritative after atomic incr on remote.\n this.delL1(key);\n return result;\n }\n\n public async invalidate(...keys: string[]): Promise<void> {\n if (this.memoryStore) {\n if (keys.length === 0) {\n this.memoryStore.clear();\n } else {\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const prefix = key.slice(0, -1);\n for (const k of this.memoryStore.keys()) {\n if (k.startsWith(prefix)) this.memoryStore.delete(k);\n }\n } else {\n this.memoryStore.delete(key);\n }\n }\n }\n }\n await this.provider.invalidateKeys(this.container, keys);\n }\n\n public async set(\n key: string,\n value: TReturn,\n ttl?: DurationLike,\n ): Promise<void> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.settings.enabled\n ) {\n return;\n }\n\n const freshTtlMs = this.dateTimeProvider\n .duration(\n ttl ?? this.options.ttl ?? [this.settings.defaultTtl, \"seconds\"],\n )\n .as(\"milliseconds\");\n\n const staleMs = this.options.stale\n ? this.dateTimeProvider.duration(this.options.stale).as(\"milliseconds\")\n : 0;\n\n const providerTtlMs = freshTtlMs > 0 ? freshTtlMs + staleMs : 0;\n const now = this.dateTimeProvider.nowMillis();\n const freshUntil = freshTtlMs > 0 ? now + freshTtlMs : 0;\n\n const payload =\n this.options.stale && freshTtlMs > 0\n ? ({ [SWR_MARKER]: 1, v: value, f: freshUntil } satisfies SwrEnvelope)\n : value;\n\n await this.provider.setTyped(this.container, key, payload, {\n ttl: providerTtlMs > 0 ? providerTtlMs : undefined,\n compress: this.options.compress,\n });\n\n // Write-through to L1 (raw value, not wrapped).\n this.setL1(key, {\n value,\n expiresAt:\n this.memoryTtlMs > 0\n ? now + this.memoryTtlMs\n : Number.POSITIVE_INFINITY,\n negative: false,\n });\n\n // A fresh write supersedes any pending refresh for this key.\n this.inflightRefreshes.delete(key);\n\n await this.alepha.events.emit(\"cache:set\", {\n container: this.container,\n key,\n ttlMs: providerTtlMs > 0 ? providerTtlMs : undefined,\n });\n }\n\n public async get(key: string): Promise<TReturn | undefined> {\n const read = await this.read(key);\n return read.value;\n }\n\n /**\n * Internal read that also reports whether the value is stale (SWR\n * grace window). Middleware and `run()` use this to decide whether to\n * schedule a background refresh.\n */\n public async read(key: string): Promise<ReadResult<TReturn>> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.settings.enabled\n ) {\n return { value: undefined, stale: false };\n }\n\n const now = this.dateTimeProvider.nowMillis();\n\n // L1 check\n if (this.memoryStore) {\n const entry = this.memoryStore.get(key);\n if (entry !== undefined) {\n if (entry.expiresAt > now) {\n // LRU touch\n this.memoryStore.delete(key);\n this.memoryStore.set(key, entry);\n await this.alepha.events.emit(\"cache:hit\", {\n container: this.container,\n key,\n });\n return {\n value: entry.negative ? undefined : entry.value,\n stale: false,\n };\n }\n this.memoryStore.delete(key);\n }\n }\n\n // L2 check\n const raw = await this.provider.getTyped<TReturn | SwrEnvelope>(\n this.container,\n key,\n );\n\n if (raw === undefined) {\n // Negative caching\n if (this.memoryStore && this.negativeTtlMs > 0) {\n this.setL1(key, {\n value: undefined,\n expiresAt: now + this.negativeTtlMs,\n negative: true,\n });\n }\n await this.alepha.events.emit(\"cache:miss\", {\n container: this.container,\n key,\n });\n return { value: undefined, stale: false };\n }\n\n let value: TReturn;\n let stale = false;\n\n if (this.isSwrEnvelope(raw)) {\n value = raw.v as TReturn;\n stale = raw.f > 0 && raw.f <= now;\n } else {\n value = raw as TReturn;\n }\n\n // Populate L1 (write-back from L2 read)\n if (this.memoryStore && this.memoryTtlMs > 0) {\n this.setL1(key, {\n value,\n expiresAt: now + this.memoryTtlMs,\n negative: false,\n });\n }\n\n await this.alepha.events.emit(stale ? \"cache:stale\" : \"cache:hit\", {\n container: this.container,\n key,\n });\n\n return { value, stale };\n }\n\n /**\n * Run a handler under single-flight: concurrent callers for the same\n * key share one in-flight promise.\n */\n public async runSingleFlight(\n key: string,\n handler: () => Promise<TReturn> | TReturn,\n ): Promise<TReturn> {\n const existing = this.inflightRefreshes.get(key);\n if (existing) {\n return existing;\n }\n const promise = (async () => {\n try {\n return await handler();\n } finally {\n this.inflightRefreshes.delete(key);\n }\n })();\n this.inflightRefreshes.set(key, promise);\n return promise;\n }\n\n /**\n * Schedule a background refresh for a stale key. At most one refresh\n * per key is in-flight at any time; failures are swallowed (the stale\n * value keeps being served until expiry).\n */\n public scheduleRefresh(\n key: string,\n handler: () => Promise<TReturn> | TReturn,\n ): void {\n if (this.inflightRefreshes.has(key)) {\n return;\n }\n const promise = (async () => {\n try {\n const result = await handler();\n await this.set(key, result);\n await this.alepha.events.emit(\"cache:revalidate\", {\n container: this.container,\n key,\n });\n return result;\n } finally {\n this.inflightRefreshes.delete(key);\n }\n })();\n promise.catch(() => {\n // swallow: stale value keeps serving until expiry\n });\n this.inflightRefreshes.set(key, promise);\n }\n\n protected $provider(): CacheProvider {\n if (!this.options.provider) {\n return this.alepha.inject(CacheProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryCacheProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n\n protected isSwrEnvelope(value: unknown): value is SwrEnvelope {\n return (\n value !== null &&\n typeof value === \"object\" &&\n (value as Record<string, unknown>)[SWR_MARKER] === 1 &&\n \"f\" in (value as Record<string, unknown>) &&\n \"v\" in (value as Record<string, unknown>)\n );\n }\n\n protected setL1(key: string, entry: L1Entry<TReturn>): void {\n if (!this.memoryStore) return;\n if (this.memoryStore.has(key)) {\n this.memoryStore.delete(key);\n }\n this.memoryStore.set(key, entry);\n while (this.memoryStore.size > this.memoryMax) {\n const first = this.memoryStore.keys().next().value;\n if (first === undefined) break;\n this.memoryStore.delete(first);\n }\n }\n\n protected delL1(key: string): void {\n this.memoryStore?.delete(key);\n }\n}\n\nexport interface CachePrimitiveFn<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends CachePrimitive<TReturn, TParameter> {\n /**\n * Run the cache primitive with the provided arguments.\n */\n (...args: TParameter): Promise<TReturn>;\n}\n\n/**\n * Cache middleware + store. Callable as middleware `(handler) => wrappedHandler`\n * AND exposes store methods (`.get()`, `.set()`, `.invalidate()`, `.incr()`).\n */\nexport interface CacheMiddlewareFn<TReturn = any>\n extends CachePrimitive<TReturn, any[]> {\n <T extends (...args: any[]) => any>(handler: T): T;\n [OPTIONS]?: MiddlewareMetadata;\n}\n\n$cache[KIND] = CachePrimitive;\n","import { $hook, $inject, Alepha, AlephaError } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { CachePrimitive } from \"../primitives/$cache.ts\";\nimport { CacheProvider } from \"./CacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * KVNamespace interface matching Cloudflare's KV API.\n */\nexport interface KVNamespace {\n get(key: string, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n get(key: string, type: \"text\"): Promise<string | null>;\n get(key: string, type?: \"text\"): Promise<string | null>;\n put(\n key: string,\n value: string | ArrayBuffer | ReadableStream,\n options?: KVPutOptions,\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KVListOptions): Promise<KVListResult>;\n}\n\nexport interface KVPutOptions {\n expiration?: number;\n expirationTtl?: number;\n metadata?: unknown;\n}\n\nexport interface KVListOptions {\n prefix?: string;\n limit?: number;\n cursor?: string;\n}\n\nexport interface KVListResult {\n keys: KVListKey[];\n list_complete: boolean;\n cursor?: string;\n}\n\nexport interface KVListKey {\n name: string;\n expiration?: number;\n metadata?: unknown;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Default KV binding name used in wrangler configuration.\n */\nexport const KV_DEFAULT_BINDING = \"KV_CACHE\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cloudflare KV cache provider.\n *\n * Uses a KV namespace binding for all cache operations.\n * Keys are stored as: `cache:{name}:{key}`\n *\n * **Required Cloudflare binding:**\n * - `KV_CACHE` - A KV namespace binding in wrangler configuration\n *\n * @example\n * ```toml\n * # wrangler.toml - automatically generated by alepha build\n * [[kv_namespaces]]\n * binding = \"KV_CACHE\"\n * id = \"abc123\"\n * ```\n */\nexport class CloudflareKVProvider extends CacheProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n\n protected kv?: KVNamespace;\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n const caches = this.alepha\n .primitives<CachePrimitive>(\"cache\")\n .filter((it) => it.provider === this);\n\n if (caches.length === 0) {\n this.log.info(\n \"CloudflareKVProvider is registered but no cache primitives are using it. Skipping KV initialization.\",\n );\n return;\n }\n\n const cloudflareEnv = this.alepha.get(\"cloudflare.env\") as\n | Record<string, unknown>\n | undefined;\n if (!cloudflareEnv) {\n throw new AlephaError(\n \"Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.\",\n );\n }\n\n const binding = cloudflareEnv[KV_DEFAULT_BINDING] as\n | KVNamespace\n | undefined;\n if (!binding) {\n throw new AlephaError(\n `KV binding '${KV_DEFAULT_BINDING}' not found in Cloudflare Workers environment.`,\n );\n }\n\n this.kv = binding;\n\n this.log.info(\"Cloudflare KV cache OK\");\n },\n });\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n if (!this.alepha.isStarted()) {\n return;\n }\n\n const kvKey = this.prefix(name, key);\n const buffer = await this.getKV().get(kvKey, \"arrayBuffer\");\n if (!buffer) {\n return;\n }\n\n this.log.debug(\"Cache hit\", {\n size: buffer.byteLength,\n key: kvKey,\n });\n\n return new Uint8Array(buffer);\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (!this.alepha.isReady()) {\n return new Uint8Array(value);\n }\n\n const kvKey = this.prefix(name, key);\n const options: KVPutOptions = {};\n\n if (ttl) {\n // KV expects TTL in seconds, we receive milliseconds\n options.expirationTtl = Math.max(60, Math.ceil(ttl / 1000));\n }\n\n await this.getKV().put(\n kvKey,\n value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength,\n ) as ArrayBuffer,\n options,\n );\n return value;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n const kv = this.getKV();\n\n if (keys.length === 0) {\n // Delete all keys under this cache name\n const prefix = this.prefix(name);\n const allKeys = await this.listAllKeys(`${prefix}:`);\n for (const k of allKeys) {\n await kv.delete(k);\n }\n return;\n }\n\n const nameKey = this.prefix(name);\n for (const key of keys) {\n const fullKey = key.startsWith(nameKey) ? key : this.prefix(name, key);\n await kv.delete(fullKey);\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n const kvKey = this.prefix(name, key);\n const value = await this.getKV().get(kvKey, \"text\");\n return value !== null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const prefix = filter\n ? `${this.prefix(name)}:${filter}`\n : `${this.prefix(name)}:`;\n\n return this.listAllKeys(prefix);\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n const kv = this.getKV();\n const allKeys = await this.listAllKeys(\"cache:\");\n for (const key of allKeys) {\n await kv.delete(key);\n }\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n const kvKey = this.prefix(name, key);\n const kv = this.getKV();\n\n const existing = await kv.get(kvKey, \"text\");\n let current = 0;\n\n if (existing !== null) {\n current = Number.parseInt(existing, 10) || 0;\n }\n\n const newValue = current + amount;\n await kv.put(kvKey, String(newValue));\n return newValue;\n }\n\n /**\n * Build the full KV key: `cache:{name}:{key}`\n */\n protected prefix(...path: string[]): string {\n return [\"cache\", ...path].join(\":\");\n }\n\n protected getKV(): KVNamespace {\n if (!this.kv) {\n throw new AlephaError(\n \"KV namespace not initialized. Call start() first.\",\n );\n }\n return this.kv;\n }\n\n /**\n * List all keys matching a prefix, handling pagination.\n */\n protected async listAllKeys(prefix: string): Promise<string[]> {\n const kv = this.getKV();\n const allKeys: string[] = [];\n let cursor: string | undefined;\n\n do {\n const result = await kv.list({\n prefix,\n cursor,\n });\n\n for (const key of result.keys) {\n allKeys.push(key.name);\n }\n\n cursor = result.list_complete ? undefined : result.cursor;\n } while (cursor);\n\n return allKeys;\n }\n}\n","import { $module } from \"alepha\";\nimport { $cache } from \"./primitives/$cache.ts\";\nimport { CacheProvider } from \"./providers/CacheProvider.ts\";\nimport { CloudflareKVProvider } from \"./providers/CloudflareKVProvider.ts\";\nimport { MemoryCacheProvider } from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cache.ts\";\nexport * from \"./providers/CacheProvider.ts\";\nexport * from \"./providers/CloudflareKVProvider.ts\";\nexport * from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaCache = $module({\n name: \"alepha.cache\",\n primitives: [$cache],\n services: [CacheProvider],\n variants: [MemoryCacheProvider, CloudflareKVProvider],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: CacheProvider,\n use: CloudflareKVProvider,\n });\n },\n});\n"],"mappings":"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"}
@@ -1,10 +1,6 @@
1
- import * as _$alepha from "alepha";
2
1
  import { Alepha, Static } from "alepha";
3
2
  import { CacheProvider } from "alepha/cache";
4
3
  import { DateTimeProvider } from "alepha/datetime";
5
- import * as _$alepha_logger0 from "alepha/logger";
6
- import * as _$alepha_orm0 from "alepha/orm";
7
- import * as _$typebox from "typebox";
8
4
 
9
5
  //#region ../../src/cache/database/entities/cacheEntries.d.ts
10
6
  /**
@@ -17,14 +13,14 @@ import * as _$typebox from "typebox";
17
13
  * - `expiresAt` is null for entries that never expire, or a timestamp after
18
14
  * which the entry is considered gone (filtered out at read time).
19
15
  */
20
- declare const cacheEntries: _$alepha_orm0.EntityPrimitive<_$typebox.TObject<{
21
- id: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_PRIMARY_KEY>, typeof _$alepha_orm0.PG_DEFAULT>;
22
- createdAt: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_CREATED_AT>, typeof _$alepha_orm0.PG_DEFAULT>;
23
- container: _$typebox.TString;
24
- cacheKey: _$typebox.TString;
25
- value: _$typebox.TOptional<_$typebox.TString>;
26
- count: _$typebox.TOptional<_$typebox.TInteger>;
27
- expiresAt: _$typebox.TOptional<_$typebox.TString>;
16
+ declare const cacheEntries: import("alepha/orm").EntityPrimitive<import("typebox").TObject<{
17
+ id: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_PRIMARY_KEY>, typeof import("alepha/orm").PG_DEFAULT>;
18
+ createdAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_CREATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
19
+ container: import("typebox").TString;
20
+ cacheKey: import("typebox").TString;
21
+ value: import("typebox").TOptional<import("typebox").TString>;
22
+ count: import("typebox").TOptional<import("typebox").TInteger>;
23
+ expiresAt: import("typebox").TOptional<import("typebox").TString>;
28
24
  }>>;
29
25
  type CacheEntry = Static<typeof cacheEntries.schema>;
30
26
  //#endregion
@@ -32,9 +28,9 @@ type CacheEntry = Static<typeof cacheEntries.schema>;
32
28
  /**
33
29
  * Configuration atom for {@link DatabaseCacheProvider}.
34
30
  */
35
- declare const databaseCacheOptions: _$alepha.Atom<_$typebox.TObject<{
36
- sweepProbability: _$typebox.TNumber;
37
- sweepBatchSize: _$typebox.TInteger;
31
+ declare const databaseCacheOptions: import("alepha").Atom<import("typebox").TObject<{
32
+ sweepProbability: import("typebox").TNumber;
33
+ sweepBatchSize: import("typebox").TInteger;
38
34
  }>, "alepha.cache.database.options">;
39
35
  type DatabaseCacheOptions = Static<typeof databaseCacheOptions.schema>;
40
36
  declare module "alepha" {
@@ -72,17 +68,17 @@ declare module "alepha" {
72
68
  * @see {@link CacheProvider}
73
69
  */
74
70
  declare class DatabaseCacheProvider extends CacheProvider {
75
- protected readonly log: _$alepha_logger0.Logger;
71
+ protected readonly log: import("alepha/logger").Logger;
76
72
  protected readonly alepha: Alepha;
77
73
  protected readonly dateTimeProvider: DateTimeProvider;
78
- protected readonly repository: _$alepha_orm0.Repository<_$typebox.TObject<{
79
- id: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_PRIMARY_KEY>, typeof _$alepha_orm0.PG_DEFAULT>;
80
- createdAt: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_CREATED_AT>, typeof _$alepha_orm0.PG_DEFAULT>;
81
- container: _$typebox.TString;
82
- cacheKey: _$typebox.TString;
83
- value: _$typebox.TOptional<_$typebox.TString>;
84
- count: _$typebox.TOptional<_$typebox.TInteger>;
85
- expiresAt: _$typebox.TOptional<_$typebox.TString>;
74
+ protected readonly repository: import("alepha/orm").Repository<import("typebox").TObject<{
75
+ id: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_PRIMARY_KEY>, typeof import("alepha/orm").PG_DEFAULT>;
76
+ createdAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_CREATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
77
+ container: import("typebox").TString;
78
+ cacheKey: import("typebox").TString;
79
+ value: import("typebox").TOptional<import("typebox").TString>;
80
+ count: import("typebox").TOptional<import("typebox").TInteger>;
81
+ expiresAt: import("typebox").TOptional<import("typebox").TString>;
86
82
  }>>;
87
83
  protected readonly options: Readonly<{
88
84
  sweepProbability: number;
@@ -100,7 +96,7 @@ declare class DatabaseCacheProvider extends CacheProvider {
100
96
  * Last error caught while sweeping (sweeps must never throw).
101
97
  */
102
98
  lastSweepError?: unknown;
103
- protected readonly onStart: _$alepha.HookPrimitive<"start">;
99
+ protected readonly onStart: import("alepha").HookPrimitive<"start">;
104
100
  get(name: string, key: string): Promise<Uint8Array | undefined>;
105
101
  set(name: string, key: string, value: Uint8Array, ttl?: number): Promise<Uint8Array>;
106
102
  del(name: string, ...keys: string[]): Promise<void>;
@@ -150,7 +146,7 @@ declare class DatabaseCacheProvider extends CacheProvider {
150
146
  * @see {@link DatabaseCacheProvider}
151
147
  * @module alepha.cache.database
152
148
  */
153
- declare const AlephaCacheDatabase: _$alepha.Service<_$alepha.Module>;
149
+ declare const AlephaCacheDatabase: import("alepha").Service<import("alepha").Module>;
154
150
  //#endregion
155
151
  export { AlephaCacheDatabase, CacheEntry, DatabaseCacheOptions, DatabaseCacheProvider, cacheEntries, databaseCacheOptions };
156
152
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/cache/database/entities/cacheEntries.ts","../../../src/cache/database/providers/DatabaseCacheProvider.ts","../../../src/cache/database/index.ts"],"mappings":";;;;;;;;;;;;;;;;;AAaA;;cAAa,YAAA,EAAY,aAAA,CAAA,eAAA,WAAA,OAAA;gDAuCvB,SAAA,CAAA,OAAA;;;;;;;;KAEU,UAAA,GAAa,MAAA,QAAc,YAAA,CAAa,MAAA;;;;;;cC1CvC,oBAAA,EAAoB,QAAA,CAAA,IAAA,WAAA,OAAA;oBAqB/B,SAAA,CAAA,OAAA;;;KAEU,oBAAA,GAAuB,MAAA,QAAc,oBAAA,CAAqB,MAAA;AAAA;EAAA,UAG1D,KAAA;IAAA,CACP,oBAAA,CAAqB,GAAA,GAAM,oBAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmCnB,qBAAA,SAA8B,aAAA;EAAA,mBACtB,GAAA,EADc,gBAAA,CACX,MAAA;EAAA,mBACH,MAAA,EAAM,MAAA;EAAA,mBACN,gBAAA,EAAgB,gBAAA;EAAA,mBAChB,UAAA,EAAU,aAAA,CAAA,UAAA,WAAA,OAAA;kDADM,SAAA,CAAA,OAAA;;;;;;;;qBAEhB,OAAA,EAAO,QAAA;;;;;ADzB5B;;EC8BS,MAAA;ED9BsB;;;ECmCtB,MAAA;EDnCiD;;;ECwCjD,cAAA;EAAA,mBAIY,OAAA,EAnBO,QAAA,CAmBA,aAAA;EASb,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA,CAAQ,UAAA;EA2BxC,GAAA,CACX,IAAA,UACA,GAAA,UACA,KAAA,EAAO,UAAA,EACP,GAAA,YACC,OAAA,CAAQ,UAAA;EA+BE,GAAA,CAAI,IAAA,aAAiB,IAAA,aAAiB,OAAA;EActC,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA;EAOhC,IAAA,CAAK,IAAA,UAAc,MAAA,YAAkB,OAAA;EAgBrC,KAAA,CAAA,GAAS,OAAA;EAIT,IAAA,CACX,IAAA,UACA,GAAA,UACA,MAAA,WACC,OAAA;EA3M4B;;;;EAoPlB,YAAA,CAAA,GAAgB,OAAA;EAAA,UAUnB,iBAAA,CAAA,GAAqB,MAAA;EAAA,UAOrB,cAAA,CAAe,IAAA,UAAc,GAAA,WAAc,MAAA;EAAA,UAU3C,gBAAA,CAAiB,GAAA;EAAA,UASjB,QAAA,CAAS,KAAA,EAAO,UAAA;EAAA,UAQhB,UAAA,CAAW,KAAA,WAAgB,UAAA;EAzQ3B;;;;;;;EAAA,UAoRA,UAAA,CAAA;EAAA,UAgBM,QAAA,CAAA,GAAY,OAAA;AAAA;;;;;;;;;;AD1T9B;;;;;;;;;;;;;cEkBa,mBAAA,EAAmB,QAAA,CAAA,OAAA,CAI9B,QAAA,CAJ8B,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/cache/database/entities/cacheEntries.ts","../../../src/cache/database/providers/DatabaseCacheProvider.ts","../../../src/cache/database/index.ts"],"mappings":";;;;;;;;;AAaA;;;;;;cAAa,YAAA,uBAAY,eAAA,mBAAA,OAAA;;;;;;;;;KAyCb,UAAA,GAAa,MAAM,QAAQ,YAAA,CAAa,MAAA;;;;;AAzCpD;cCDa,oBAAA,mBAAoB,IAAA,mBAAA,OAAA;;;;KAuBrB,oBAAA,GAAuB,MAAM,QAAQ,oBAAA,CAAqB,MAAA;AAAA;EAAA,UAG1D,KAAA;IAAA,CACP,oBAAA,CAAqB,GAAG,GAAG,oBAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;ADehC;;;;;;;cCoBa,qBAAA,SAA8B,aAAA;EAAA,mBACtB,GAAA,0BAAG,MAAA;EAAA,mBACH,MAAA,EAAM,MAAA;EAAA,mBACN,gBAAA,EAAgB,gBAAA;EAAA,mBAChB,UAAA,uBAAU,UAAA,mBAAA,OAAA;;;;;;;;;qBACV,OAAA,EAAO,QAAA;;;;EA5ChB;;;EAiDH,MAAA;EAjD0B;;;EAsD1B,MAAA;EAtDmE;AAAE;;EA2DrE,cAAA;EAAA,mBAIY,OAAA,mBAAO,aAAA;EASb,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA,CAAQ,UAAA;EA2BxC,GAAA,CACX,IAAA,UACA,GAAA,UACA,KAAA,EAAO,UAAA,EACP,GAAA,YACC,OAAA,CAAQ,UAAA;EA+BE,GAAA,CAAI,IAAA,aAAiB,IAAA,aAAiB,OAAA;EActC,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA;EAOhC,IAAA,CAAK,IAAA,UAAc,MAAA,YAAkB,OAAA;EAgBrC,KAAA,IAAS,OAAA;EAIT,IAAA,CACX,IAAA,UACA,GAAA,UACA,MAAA,WACC,OAAA;EAhL+C;AAmCpD;;;EAsLe,YAAA,IAAgB,OAAA;EAAA,UAUnB,iBAAA,IAAqB,MAAA;EAAA,UAOrB,cAAA,CAAe,IAAA,UAAc,GAAA,WAAc,MAAA;EAAA,UAU3C,gBAAA,CAAiB,GAAA;EAAA,UASjB,QAAA,CAAS,KAAA,EAAO,UAAA;EAAA,UAQhB,UAAA,CAAW,KAAA,WAAgB,UAAA;EAjK1B;;;;;;;EAAA,UA4KD,UAAA;EAAA,UAgBM,QAAA,IAAY,OAAA;AAAA;;;;;;AD1T9B;;;;;;;;;;;;;;;;;cEkBa,mBAAA,mBAAmB,OAAA,kBAAA,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/cache/database/entities/cacheEntries.ts","../../../src/cache/database/providers/DatabaseCacheProvider.ts","../../../src/cache/database/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * Storage table for the {@link DatabaseCacheProvider}.\n *\n * Each row represents one cache entry:\n * - `(container, cacheKey)` is the logical key (uniqueness enforced by index).\n * - `value` holds base64-encoded bytes for `set/get/setTyped/getTyped`.\n * - `count` holds an integer counter for atomic `incr` operations.\n * - `expiresAt` is null for entries that never expire, or a timestamp after\n * which the entry is considered gone (filtered out at read time).\n */\nexport const cacheEntries = $entity({\n name: \"cache_entries\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n\n createdAt: db.createdAt(),\n\n container: t.text({\n description: \"Cache container name, set by the $cache primitive.\",\n }),\n\n cacheKey: t.text({\n description: \"Per-container key chosen by the caller.\",\n }),\n\n value: t.optional(\n // No maxLength: cache values are arbitrary-sized (especially when\n // `compress: true` is enabled on the $cache primitive, which can\n // produce blobs well above the default 255-char `t.text()` cap). This\n // resolves to TEXT in both Postgres and SQLite, which have no\n // practical length limit either.\n t.string({\n description: \"Base64-encoded bytes. Used by set/get.\",\n }),\n ),\n\n count: t.optional(\n t.integer({\n description: \"Counter value. Used by atomic incr().\",\n }),\n ),\n\n expiresAt: t.optional(\n t.datetime({\n description: \"Null means no expiration.\",\n }),\n ),\n }),\n indexes: [{ columns: [\"container\", \"cacheKey\"], unique: true }, \"expiresAt\"],\n});\n\nexport type CacheEntry = Static<typeof cacheEntries.schema>;\n","import { $atom, $hook, $inject, $state, Alepha, type Static, t } from \"alepha\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport { cacheEntries } from \"../entities/cacheEntries.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Configuration atom for {@link DatabaseCacheProvider}.\n */\nexport const databaseCacheOptions = $atom({\n name: \"alepha.cache.database.options\",\n schema: t.object({\n sweepProbability: t.number({\n description:\n \"Probability (0..1) that a write operation triggers a sweep of expired rows. Set to 0 to disable opportunistic sweeping.\",\n default: 0.01,\n minimum: 0,\n maximum: 1,\n }),\n sweepBatchSize: t.integer({\n description:\n \"Maximum number of expired rows deleted per opportunistic sweep.\",\n default: 100,\n minimum: 1,\n }),\n }),\n default: {\n sweepProbability: 0.01,\n sweepBatchSize: 100,\n },\n});\n\nexport type DatabaseCacheOptions = Static<typeof databaseCacheOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [databaseCacheOptions.key]: DatabaseCacheOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cache provider backed by the application's SQL database.\n *\n * Uses the `cache_entries` table as a generic key/value store with optional\n * TTL. Works on every database supported by Alepha's ORM (Postgres, SQLite,\n * Cloudflare D1, Bun SQLite).\n *\n * **Why use this over Cloudflare KV / Redis ?**\n *\n * - You already have a SQL database, no extra resource to provision/secure.\n * - `incr()` is **atomic** through `INSERT ... ON CONFLICT DO UPDATE`.\n * - Reads/writes are **strongly consistent** (KV is eventually consistent).\n * - Phase-2 flows (registration, password reset) become transactional with\n * the user-creation INSERT.\n *\n * **When to prefer Cloudflare KV / Redis instead ?**\n *\n * - Hot read paths where DB latency matters.\n * - Very high write rates where DB pressure becomes a concern.\n *\n * **Storage layout**\n *\n * - `value` column: base64-encoded bytes for `set/get/setTyped/getTyped`.\n * - `count` column: integer counter for atomic `incr()`.\n * - `expiresAt` column: nullable timestamp; expired rows are filtered at read\n * time and reaped opportunistically on writes.\n *\n * @see {@link CacheProvider}\n */\nexport class DatabaseCacheProvider extends CacheProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly repository = $repository(cacheEntries);\n protected readonly options = $state(databaseCacheOptions);\n\n /**\n * Total writes performed since startup. Useful for tests and metrics.\n */\n public writes = 0;\n\n /**\n * Total opportunistic sweep cycles executed. Useful for tests.\n */\n public sweeps = 0;\n\n /**\n * Last error caught while sweeping (sweeps must never throw).\n */\n public lastSweepError?: unknown;\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: () => {\n this.log.debug(\"DatabaseCacheProvider ready\");\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n if (!this.alepha.isStarted()) {\n return undefined;\n }\n\n const row = await this.repository.findOne({\n where: this.unexpiredWhere(name, key) as any,\n });\n\n if (!row) {\n return undefined;\n }\n\n if (row.value != null) {\n return this.fromBase64(row.value);\n }\n\n if (row.count != null) {\n // The caller wrote the entry through `incr()`. Surface the count via\n // the same byte format that MemoryCacheProvider uses, so `getTyped`\n // returns the number transparently.\n return this.serialize(row.count);\n }\n\n return undefined;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (!this.alepha.isStarted()) {\n return value;\n }\n\n const expiresAt = this.computeExpiresAt(ttl);\n\n await this.repository.upsert(\n {\n container: name,\n cacheKey: key,\n value: this.toBase64(value),\n count: null,\n expiresAt,\n } as any,\n {\n target: [\"container\", \"cacheKey\"],\n set: {\n value: this.toBase64(value),\n count: null,\n expiresAt,\n },\n },\n );\n\n this.writes++;\n this.maybeSweep();\n\n return value;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n if (keys.length === 0) {\n await this.repository.deleteMany({\n container: { eq: name },\n });\n return;\n }\n\n await this.repository.deleteMany({\n container: { eq: name },\n cacheKey: { inArray: keys },\n });\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n const row = await this.repository.findOne({\n where: this.unexpiredWhere(name, key) as any,\n });\n return row != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const baseAnd: any[] = [\n { container: { eq: name } },\n this.unexpiredOrClause(),\n ];\n if (filter) {\n baseAnd.push({ cacheKey: { startsWith: filter } });\n }\n\n const rows = await this.repository.findMany({\n where: { and: baseAnd } as any,\n });\n\n return rows.map((row) => row.cacheKey);\n }\n\n public async clear(): Promise<void> {\n await this.repository.deleteMany({});\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n if (!this.alepha.isStarted()) {\n return amount;\n }\n\n // Atomic upsert via `INSERT ... ON CONFLICT (container, cacheKey) DO UPDATE\n // SET count = COALESCE(cache_entries.count, 0) + excluded.count`.\n // Both Postgres and SQLite (incl. D1) support this in a single statement,\n // so concurrent callers can never observe an interleaved read/write.\n const table = this.repository.table;\n\n const updated = await this.repository.upsert(\n {\n container: name,\n cacheKey: key,\n count: amount,\n value: null,\n expiresAt: null,\n } as any,\n {\n target: [\"container\", \"cacheKey\"],\n set: {\n // `excluded.count` references the value being inserted on conflict.\n count: sql`coalesce(${table.count}, 0) + ${amount}`,\n value: null,\n },\n },\n );\n\n this.writes++;\n this.maybeSweep();\n\n return Number(updated.count ?? amount);\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n /**\n * Sweep all expired rows in one shot. Useful for tests and for users who\n * want to schedule their own cleanup job.\n */\n public async sweepExpired(): Promise<number> {\n const nowIso = this.dateTimeProvider.nowISOString();\n const ids = await this.repository.deleteMany({\n expiresAt: { lt: nowIso },\n });\n return ids.length;\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected unexpiredOrClause(): Record<string, any> {\n const nowIso = this.dateTimeProvider.nowISOString();\n return {\n or: [{ expiresAt: { isNull: true } }, { expiresAt: { gt: nowIso } }],\n };\n }\n\n protected unexpiredWhere(name: string, key: string): Record<string, any> {\n return {\n and: [\n { container: { eq: name } },\n { cacheKey: { eq: key } },\n this.unexpiredOrClause(),\n ],\n };\n }\n\n protected computeExpiresAt(ttl?: number): string | null {\n if (!ttl || ttl <= 0) {\n return null;\n }\n return this.dateTimeProvider\n .of(this.dateTimeProvider.nowMillis() + ttl)\n .toISOString();\n }\n\n protected toBase64(value: Uint8Array): string {\n return Buffer.from(\n value.buffer as ArrayBuffer,\n value.byteOffset,\n value.byteLength,\n ).toString(\"base64\");\n }\n\n protected fromBase64(value: string): Uint8Array {\n return new Uint8Array(Buffer.from(value, \"base64\"));\n }\n\n /**\n * Run an opportunistic sweep with `sweepProbability` chance per write.\n *\n * Sweep failures are swallowed: cleanup is best-effort, lazy expiration on\n * read keeps correctness regardless. Errors are logged once on `lastSweepError`\n * so tests can assert on them.\n */\n protected maybeSweep(): void {\n const probability = this.options.sweepProbability;\n if (probability <= 0) {\n return;\n }\n\n if (Math.random() >= probability) {\n return;\n }\n\n this.runSweep().catch((err) => {\n this.lastSweepError = err;\n this.log.warn(\"DatabaseCacheProvider sweep failed\", err);\n });\n }\n\n protected async runSweep(): Promise<void> {\n this.sweeps++;\n const nowIso = this.dateTimeProvider.nowISOString();\n\n // Drizzle does not expose a portable LIMIT on DELETE, so we select first\n // and then delete by ID. Two roundtrips, but only on the ~1% sweep path.\n const expired = await this.repository.findMany({\n where: { expiresAt: { lt: nowIso } },\n columns: [\"id\"],\n limit: this.options.sweepBatchSize,\n });\n\n if (expired.length === 0) {\n return;\n }\n\n await this.repository.deleteMany({\n id: { inArray: expired.map((it) => it.id) },\n });\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache } from \"alepha/cache\";\nimport { DatabaseCacheProvider } from \"./providers/DatabaseCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./entities/cacheEntries.ts\";\nexport * from \"./providers/DatabaseCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Cache that stores entries in the application's SQL database.\n *\n * Adds a `cache_entries` table and a {@link DatabaseCacheProvider}\n * implementation of {@link CacheProvider} that:\n *\n * - reads/writes through the framework's ORM (Postgres, SQLite, D1, Bun);\n * - exposes an **atomic** `incr()` via `INSERT ... ON CONFLICT DO UPDATE`;\n * - filters expired rows on every read (lazy expiration);\n * - opportunistically sweeps a small batch of expired rows on writes\n * (configurable via {@link databaseCacheOptions}).\n *\n * **Module is opt-in.** Importing this module does not change the default\n * `CacheProvider` binding — pass `provider: DatabaseCacheProvider` explicitly\n * to the relevant `$cache(...)` calls, or rebind globally via\n * `alepha.with({ provide: CacheProvider, use: DatabaseCacheProvider })`.\n *\n * @see {@link DatabaseCacheProvider}\n * @module alepha.cache.database\n */\nexport const AlephaCacheDatabase = $module({\n name: \"alepha.cache.database\",\n services: [DatabaseCacheProvider],\n register: (alepha) => alepha.with(AlephaCache),\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/cache/database/entities/cacheEntries.ts","../../../src/cache/database/providers/DatabaseCacheProvider.ts","../../../src/cache/database/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * Storage table for the {@link DatabaseCacheProvider}.\n *\n * Each row represents one cache entry:\n * - `(container, cacheKey)` is the logical key (uniqueness enforced by index).\n * - `value` holds base64-encoded bytes for `set/get/setTyped/getTyped`.\n * - `count` holds an integer counter for atomic `incr` operations.\n * - `expiresAt` is null for entries that never expire, or a timestamp after\n * which the entry is considered gone (filtered out at read time).\n */\nexport const cacheEntries = $entity({\n name: \"cache_entries\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n\n createdAt: db.createdAt(),\n\n container: t.text({\n description: \"Cache container name, set by the $cache primitive.\",\n }),\n\n cacheKey: t.text({\n description: \"Per-container key chosen by the caller.\",\n }),\n\n value: t.optional(\n // No maxLength: cache values are arbitrary-sized (especially when\n // `compress: true` is enabled on the $cache primitive, which can\n // produce blobs well above the default 255-char `t.text()` cap). This\n // resolves to TEXT in both Postgres and SQLite, which have no\n // practical length limit either.\n t.string({\n description: \"Base64-encoded bytes. Used by set/get.\",\n }),\n ),\n\n count: t.optional(\n t.integer({\n description: \"Counter value. Used by atomic incr().\",\n }),\n ),\n\n expiresAt: t.optional(\n t.datetime({\n description: \"Null means no expiration.\",\n }),\n ),\n }),\n indexes: [{ columns: [\"container\", \"cacheKey\"], unique: true }, \"expiresAt\"],\n});\n\nexport type CacheEntry = Static<typeof cacheEntries.schema>;\n","import { $atom, $hook, $inject, $state, Alepha, type Static, t } from \"alepha\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, sql } from \"alepha/orm\";\nimport { cacheEntries } from \"../entities/cacheEntries.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Configuration atom for {@link DatabaseCacheProvider}.\n */\nexport const databaseCacheOptions = $atom({\n name: \"alepha.cache.database.options\",\n schema: t.object({\n sweepProbability: t.number({\n description:\n \"Probability (0..1) that a write operation triggers a sweep of expired rows. Set to 0 to disable opportunistic sweeping.\",\n default: 0.01,\n minimum: 0,\n maximum: 1,\n }),\n sweepBatchSize: t.integer({\n description:\n \"Maximum number of expired rows deleted per opportunistic sweep.\",\n default: 100,\n minimum: 1,\n }),\n }),\n default: {\n sweepProbability: 0.01,\n sweepBatchSize: 100,\n },\n});\n\nexport type DatabaseCacheOptions = Static<typeof databaseCacheOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [databaseCacheOptions.key]: DatabaseCacheOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cache provider backed by the application's SQL database.\n *\n * Uses the `cache_entries` table as a generic key/value store with optional\n * TTL. Works on every database supported by Alepha's ORM (Postgres, SQLite,\n * Cloudflare D1, Bun SQLite).\n *\n * **Why use this over Cloudflare KV / Redis ?**\n *\n * - You already have a SQL database, no extra resource to provision/secure.\n * - `incr()` is **atomic** through `INSERT ... ON CONFLICT DO UPDATE`.\n * - Reads/writes are **strongly consistent** (KV is eventually consistent).\n * - Phase-2 flows (registration, password reset) become transactional with\n * the user-creation INSERT.\n *\n * **When to prefer Cloudflare KV / Redis instead ?**\n *\n * - Hot read paths where DB latency matters.\n * - Very high write rates where DB pressure becomes a concern.\n *\n * **Storage layout**\n *\n * - `value` column: base64-encoded bytes for `set/get/setTyped/getTyped`.\n * - `count` column: integer counter for atomic `incr()`.\n * - `expiresAt` column: nullable timestamp; expired rows are filtered at read\n * time and reaped opportunistically on writes.\n *\n * @see {@link CacheProvider}\n */\nexport class DatabaseCacheProvider extends CacheProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly repository = $repository(cacheEntries);\n protected readonly options = $state(databaseCacheOptions);\n\n /**\n * Total writes performed since startup. Useful for tests and metrics.\n */\n public writes = 0;\n\n /**\n * Total opportunistic sweep cycles executed. Useful for tests.\n */\n public sweeps = 0;\n\n /**\n * Last error caught while sweeping (sweeps must never throw).\n */\n public lastSweepError?: unknown;\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: () => {\n this.log.debug(\"DatabaseCacheProvider ready\");\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n if (!this.alepha.isStarted()) {\n return undefined;\n }\n\n const row = await this.repository.findOne({\n where: this.unexpiredWhere(name, key) as any,\n });\n\n if (!row) {\n return undefined;\n }\n\n if (row.value != null) {\n return this.fromBase64(row.value);\n }\n\n if (row.count != null) {\n // The caller wrote the entry through `incr()`. Surface the count via\n // the same byte format that MemoryCacheProvider uses, so `getTyped`\n // returns the number transparently.\n return this.serialize(row.count);\n }\n\n return undefined;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (!this.alepha.isStarted()) {\n return value;\n }\n\n const expiresAt = this.computeExpiresAt(ttl);\n\n await this.repository.upsert(\n {\n container: name,\n cacheKey: key,\n value: this.toBase64(value),\n count: null,\n expiresAt,\n } as any,\n {\n target: [\"container\", \"cacheKey\"],\n set: {\n value: this.toBase64(value),\n count: null,\n expiresAt,\n },\n },\n );\n\n this.writes++;\n this.maybeSweep();\n\n return value;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n if (keys.length === 0) {\n await this.repository.deleteMany({\n container: { eq: name },\n });\n return;\n }\n\n await this.repository.deleteMany({\n container: { eq: name },\n cacheKey: { inArray: keys },\n });\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n const row = await this.repository.findOne({\n where: this.unexpiredWhere(name, key) as any,\n });\n return row != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const baseAnd: any[] = [\n { container: { eq: name } },\n this.unexpiredOrClause(),\n ];\n if (filter) {\n baseAnd.push({ cacheKey: { startsWith: filter } });\n }\n\n const rows = await this.repository.findMany({\n where: { and: baseAnd } as any,\n });\n\n return rows.map((row) => row.cacheKey);\n }\n\n public async clear(): Promise<void> {\n await this.repository.deleteMany({});\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n if (!this.alepha.isStarted()) {\n return amount;\n }\n\n // Atomic upsert via `INSERT ... ON CONFLICT (container, cacheKey) DO UPDATE\n // SET count = COALESCE(cache_entries.count, 0) + excluded.count`.\n // Both Postgres and SQLite (incl. D1) support this in a single statement,\n // so concurrent callers can never observe an interleaved read/write.\n const table = this.repository.table;\n\n const updated = await this.repository.upsert(\n {\n container: name,\n cacheKey: key,\n count: amount,\n value: null,\n expiresAt: null,\n } as any,\n {\n target: [\"container\", \"cacheKey\"],\n set: {\n // `excluded.count` references the value being inserted on conflict.\n count: sql`coalesce(${table.count}, 0) + ${amount}`,\n value: null,\n },\n },\n );\n\n this.writes++;\n this.maybeSweep();\n\n return Number(updated.count ?? amount);\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n /**\n * Sweep all expired rows in one shot. Useful for tests and for users who\n * want to schedule their own cleanup job.\n */\n public async sweepExpired(): Promise<number> {\n const nowIso = this.dateTimeProvider.nowISOString();\n const ids = await this.repository.deleteMany({\n expiresAt: { lt: nowIso },\n });\n return ids.length;\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected unexpiredOrClause(): Record<string, any> {\n const nowIso = this.dateTimeProvider.nowISOString();\n return {\n or: [{ expiresAt: { isNull: true } }, { expiresAt: { gt: nowIso } }],\n };\n }\n\n protected unexpiredWhere(name: string, key: string): Record<string, any> {\n return {\n and: [\n { container: { eq: name } },\n { cacheKey: { eq: key } },\n this.unexpiredOrClause(),\n ],\n };\n }\n\n protected computeExpiresAt(ttl?: number): string | null {\n if (!ttl || ttl <= 0) {\n return null;\n }\n return this.dateTimeProvider\n .of(this.dateTimeProvider.nowMillis() + ttl)\n .toISOString();\n }\n\n protected toBase64(value: Uint8Array): string {\n return Buffer.from(\n value.buffer as ArrayBuffer,\n value.byteOffset,\n value.byteLength,\n ).toString(\"base64\");\n }\n\n protected fromBase64(value: string): Uint8Array {\n return new Uint8Array(Buffer.from(value, \"base64\"));\n }\n\n /**\n * Run an opportunistic sweep with `sweepProbability` chance per write.\n *\n * Sweep failures are swallowed: cleanup is best-effort, lazy expiration on\n * read keeps correctness regardless. Errors are logged once on `lastSweepError`\n * so tests can assert on them.\n */\n protected maybeSweep(): void {\n const probability = this.options.sweepProbability;\n if (probability <= 0) {\n return;\n }\n\n if (Math.random() >= probability) {\n return;\n }\n\n this.runSweep().catch((err) => {\n this.lastSweepError = err;\n this.log.warn(\"DatabaseCacheProvider sweep failed\", err);\n });\n }\n\n protected async runSweep(): Promise<void> {\n this.sweeps++;\n const nowIso = this.dateTimeProvider.nowISOString();\n\n // Drizzle does not expose a portable LIMIT on DELETE, so we select first\n // and then delete by ID. Two roundtrips, but only on the ~1% sweep path.\n const expired = await this.repository.findMany({\n where: { expiresAt: { lt: nowIso } },\n columns: [\"id\"],\n limit: this.options.sweepBatchSize,\n });\n\n if (expired.length === 0) {\n return;\n }\n\n await this.repository.deleteMany({\n id: { inArray: expired.map((it) => it.id) },\n });\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache } from \"alepha/cache\";\nimport { DatabaseCacheProvider } from \"./providers/DatabaseCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./entities/cacheEntries.ts\";\nexport * from \"./providers/DatabaseCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Cache that stores entries in the application's SQL database.\n *\n * Adds a `cache_entries` table and a {@link DatabaseCacheProvider}\n * implementation of {@link CacheProvider} that:\n *\n * - reads/writes through the framework's ORM (Postgres, SQLite, D1, Bun);\n * - exposes an **atomic** `incr()` via `INSERT ... ON CONFLICT DO UPDATE`;\n * - filters expired rows on every read (lazy expiration);\n * - opportunistically sweeps a small batch of expired rows on writes\n * (configurable via {@link databaseCacheOptions}).\n *\n * **Module is opt-in.** Importing this module does not change the default\n * `CacheProvider` binding — pass `provider: DatabaseCacheProvider` explicitly\n * to the relevant `$cache(...)` calls, or rebind globally via\n * `alepha.with({ provide: CacheProvider, use: DatabaseCacheProvider })`.\n *\n * @see {@link DatabaseCacheProvider}\n * @module alepha.cache.database\n */\nexport const AlephaCacheDatabase = $module({\n name: \"alepha.cache.database\",\n services: [DatabaseCacheProvider],\n register: (alepha) => alepha.with(AlephaCache),\n});\n"],"mappings":"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"}
@@ -1,16 +1,13 @@
1
- import * as _$alepha from "alepha";
2
1
  import { Alepha, Static } from "alepha";
3
2
  import { CacheProvider } from "alepha/cache";
4
- import * as _$alepha_logger0 from "alepha/logger";
5
3
  import { RedisProvider } from "alepha/redis";
6
- import * as _$typebox from "typebox";
7
4
 
8
5
  //#region ../../src/cache/redis/providers/RedisCacheProvider.d.ts
9
6
  /**
10
7
  * Redis cache configuration atom.
11
8
  */
12
- declare const redisCacheOptions: _$alepha.Atom<_$typebox.TObject<{
13
- prefix: _$typebox.TOptional<_$typebox.TString>;
9
+ declare const redisCacheOptions: import("alepha").Atom<import("typebox").TObject<{
10
+ prefix: import("typebox").TOptional<import("typebox").TString>;
14
11
  }>, "alepha.cache.redis.options">;
15
12
  type RedisCacheOptions = Static<typeof redisCacheOptions.schema>;
16
13
  declare module "alepha" {
@@ -19,7 +16,7 @@ declare module "alepha" {
19
16
  }
20
17
  }
21
18
  declare class RedisCacheProvider extends CacheProvider {
22
- protected readonly log: _$alepha_logger0.Logger;
19
+ protected readonly log: import("alepha/logger").Logger;
23
20
  protected readonly redisProvider: RedisProvider;
24
21
  protected readonly options: Readonly<{
25
22
  prefix?: string | undefined;
@@ -42,7 +39,7 @@ declare class RedisCacheProvider extends CacheProvider {
42
39
  * @see {@link RedisCacheProvider}
43
40
  * @module alepha.cache.redis
44
41
  */
45
- declare const AlephaCacheRedis: _$alepha.Service<_$alepha.Module>;
42
+ declare const AlephaCacheRedis: import("alepha").Service<import("alepha").Module>;
46
43
  //#endregion
47
44
  export { AlephaCacheRedis, RedisCacheOptions, RedisCacheProvider, redisCacheOptions };
48
45
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/cache/redis/providers/RedisCacheProvider.ts","../../../src/cache/redis/index.ts"],"mappings":";;;;;;;;;;;cAUa,iBAAA,EAAiB,QAAA,CAAA,IAAA,WAAA,OAAA;8BAW5B,SAAA,CAAA,OAAA;AAAA;AAAA,KAEU,iBAAA,GAAoB,MAAA,QAAc,iBAAA,CAAkB,MAAA;AAAA;EAAA,UAGpD,KAAA;IAAA,CACP,iBAAA,CAAkB,GAAA,GAAM,iBAAA;EAAA;AAAA;AAAA,cAMhB,kBAAA,SAA2B,aAAA;EAAA,mBACnB,GAAA,EADW,gBAAA,CACR,MAAA;EAAA,mBACH,aAAA,EAAa,aAAA;EAAA,mBACb,OAAA,EAAO,QAAA;;;qBACP,MAAA,EAAM,MAAA;EAEZ,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA,CAAQ,UAAA;EAmBxC,GAAA,CACX,IAAA,UACA,GAAA,UACA,KAAA,EAAO,UAAA,WACP,GAAA,YACC,OAAA,CAAQ,UAAA;EAmBE,GAAA,CAAI,IAAA,aAAiB,IAAA,aAAiB,OAAA;EAmBtC,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA;EAIhC,IAAA,CAAK,IAAA,UAAc,MAAA,YAAkB,OAAA;EAOrC,KAAA,CAAA,GAAS,OAAA;EAST,IAAA,CACX,IAAA,UACA,GAAA,UACA,MAAA,WACC,OAAA;EAAA,UAKO,MAAA,CAAA,GAAU,IAAA;AAAA;;;;;;;;;cClHT,gBAAA,EAAgB,QAAA,CAAA,OAAA,CAW3B,QAAA,CAX2B,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/cache/redis/providers/RedisCacheProvider.ts","../../../src/cache/redis/index.ts"],"mappings":";;;;;;;AAUA;cAAa,iBAAA,mBAAiB,IAAA,mBAAA,OAAA;;;KAalB,iBAAA,GAAoB,MAAM,QAAQ,iBAAA,CAAkB,MAAA;AAAA;EAAA,UAGpD,KAAA;IAAA,CACP,iBAAA,CAAkB,GAAG,GAAG,iBAAA;EAAA;AAAA;AAAA,cAMhB,kBAAA,SAA2B,aAAA;EAAA,mBACnB,GAAA,0BAAG,MAAA;EAAA,mBACH,aAAA,EAAa,aAAA;EAAA,mBACb,OAAA,EAAO,QAAA;;;qBACP,MAAA,EAAM,MAAA;EAEZ,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA,CAAQ,UAAA;EAmBxC,GAAA,CACX,IAAA,UACA,GAAA,UACA,KAAA,EAAO,UAAA,WACP,GAAA,YACC,OAAA,CAAQ,UAAA;EAmBE,GAAA,CAAI,IAAA,aAAiB,IAAA,aAAiB,OAAA;EAmBtC,GAAA,CAAI,IAAA,UAAc,GAAA,WAAc,OAAA;EAIhC,IAAA,CAAK,IAAA,UAAc,MAAA,YAAkB,OAAA;EAOrC,KAAA,IAAS,OAAA;EAST,IAAA,CACX,IAAA,UACA,GAAA,UACA,MAAA,WACC,OAAA;EAAA,UAKO,MAAA,IAAU,IAAA;AAAA;;;;;;;AAxHtB;;cCMa,gBAAA,mBAAgB,OAAA,kBAAA,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/cache/redis/providers/RedisCacheProvider.ts","../../../src/cache/redis/index.ts"],"sourcesContent":["import { $atom, $inject, $state, Alepha, type Static, t } from \"alepha\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider } from \"alepha/redis\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Redis cache configuration atom.\n */\nexport const redisCacheOptions = $atom({\n name: \"alepha.cache.redis.options\",\n schema: t.object({\n prefix: t.optional(\n t.text({\n description:\n \"Prefix for all cache keys in Redis. Useful for testing or multi-tenant applications.\",\n }),\n ),\n }),\n default: {},\n});\n\nexport type RedisCacheOptions = Static<typeof redisCacheOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [redisCacheOptions.key]: RedisCacheOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class RedisCacheProvider extends CacheProvider {\n protected readonly log = $logger();\n protected readonly redisProvider = $inject(RedisProvider);\n protected readonly options = $state(redisCacheOptions);\n protected readonly alepha = $inject(Alepha);\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n if (!this.alepha.isStarted()) {\n return;\n }\n\n const keyWithPrefix = this.prefix(name, key);\n const buffer = await this.redisProvider.get(keyWithPrefix);\n if (!buffer) {\n return;\n }\n\n this.log.debug(`Cache hit`, {\n size: buffer.byteLength,\n key: keyWithPrefix,\n });\n\n return new Uint8Array(buffer);\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array | string,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (!this.alepha.isReady()) {\n return new Uint8Array(Buffer.from(value));\n }\n\n const buffer = Buffer.from(value);\n const prefix = this.prefix(name, key);\n\n if (ttl) {\n return new Uint8Array(\n await this.redisProvider.set(prefix, buffer, {\n expiration: { type: \"PX\", value: ttl },\n }),\n );\n }\n\n return new Uint8Array(await this.redisProvider.set(prefix, buffer));\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n const nameKey = this.prefix(name);\n\n if (keys.length === 0) {\n const matched = await this.redisProvider.keys(`${nameKey}:*`);\n if (matched.length > 0) {\n await this.redisProvider.del(matched);\n }\n return;\n }\n\n const prefixed = keys.map((key) =>\n key.startsWith(nameKey) ? key : this.prefix(name, key),\n );\n if (prefixed.length > 0) {\n await this.redisProvider.del(prefixed);\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.redisProvider.has(this.prefix(name, key));\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n if (filter) {\n return await this.redisProvider.keys(`${this.prefix(name)}:${filter}*`);\n }\n return this.redisProvider.keys(`${this.prefix(name)}:*`);\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n const pattern = `${this.prefix()}:*`;\n const keys = await this.redisProvider.keys(pattern);\n if (keys.length > 0) {\n await this.redisProvider.del(keys);\n }\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n const keyWithPrefix = this.prefix(name, key);\n return this.redisProvider.incr(keyWithPrefix, amount);\n }\n\n protected prefix(...path: string[]): string {\n const parts = [\"cache\", ...path];\n\n if (this.options.prefix) {\n parts.unshift(this.options.prefix);\n }\n\n return parts.join(\":\");\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache, CacheProvider } from \"alepha/cache\";\nimport { RedisCacheProvider } from \"./providers/RedisCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/RedisCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Cache that provides Redis caching capabilities.\n *\n * @see {@link RedisCacheProvider}\n * @module alepha.cache.redis\n */\nexport const AlephaCacheRedis = $module({\n name: \"alepha.cache.redis\",\n services: [RedisCacheProvider],\n register: (alepha) =>\n alepha\n .with({\n provide: CacheProvider,\n use: RedisCacheProvider,\n optional: true,\n })\n .with(AlephaCache),\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/cache/redis/providers/RedisCacheProvider.ts","../../../src/cache/redis/index.ts"],"sourcesContent":["import { $atom, $inject, $state, Alepha, type Static, t } from \"alepha\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { $logger } from \"alepha/logger\";\nimport { RedisProvider } from \"alepha/redis\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Redis cache configuration atom.\n */\nexport const redisCacheOptions = $atom({\n name: \"alepha.cache.redis.options\",\n schema: t.object({\n prefix: t.optional(\n t.text({\n description:\n \"Prefix for all cache keys in Redis. Useful for testing or multi-tenant applications.\",\n }),\n ),\n }),\n default: {},\n});\n\nexport type RedisCacheOptions = Static<typeof redisCacheOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [redisCacheOptions.key]: RedisCacheOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class RedisCacheProvider extends CacheProvider {\n protected readonly log = $logger();\n protected readonly redisProvider = $inject(RedisProvider);\n protected readonly options = $state(redisCacheOptions);\n protected readonly alepha = $inject(Alepha);\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n if (!this.alepha.isStarted()) {\n return;\n }\n\n const keyWithPrefix = this.prefix(name, key);\n const buffer = await this.redisProvider.get(keyWithPrefix);\n if (!buffer) {\n return;\n }\n\n this.log.debug(`Cache hit`, {\n size: buffer.byteLength,\n key: keyWithPrefix,\n });\n\n return new Uint8Array(buffer);\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array | string,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (!this.alepha.isReady()) {\n return new Uint8Array(Buffer.from(value));\n }\n\n const buffer = Buffer.from(value);\n const prefix = this.prefix(name, key);\n\n if (ttl) {\n return new Uint8Array(\n await this.redisProvider.set(prefix, buffer, {\n expiration: { type: \"PX\", value: ttl },\n }),\n );\n }\n\n return new Uint8Array(await this.redisProvider.set(prefix, buffer));\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n const nameKey = this.prefix(name);\n\n if (keys.length === 0) {\n const matched = await this.redisProvider.keys(`${nameKey}:*`);\n if (matched.length > 0) {\n await this.redisProvider.del(matched);\n }\n return;\n }\n\n const prefixed = keys.map((key) =>\n key.startsWith(nameKey) ? key : this.prefix(name, key),\n );\n if (prefixed.length > 0) {\n await this.redisProvider.del(prefixed);\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.redisProvider.has(this.prefix(name, key));\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n if (filter) {\n return await this.redisProvider.keys(`${this.prefix(name)}:${filter}*`);\n }\n return this.redisProvider.keys(`${this.prefix(name)}:*`);\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n const pattern = `${this.prefix()}:*`;\n const keys = await this.redisProvider.keys(pattern);\n if (keys.length > 0) {\n await this.redisProvider.del(keys);\n }\n }\n\n public async incr(\n name: string,\n key: string,\n amount: number,\n ): Promise<number> {\n const keyWithPrefix = this.prefix(name, key);\n return this.redisProvider.incr(keyWithPrefix, amount);\n }\n\n protected prefix(...path: string[]): string {\n const parts = [\"cache\", ...path];\n\n if (this.options.prefix) {\n parts.unshift(this.options.prefix);\n }\n\n return parts.join(\":\");\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache, CacheProvider } from \"alepha/cache\";\nimport { RedisCacheProvider } from \"./providers/RedisCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/RedisCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Plugin for Alepha Cache that provides Redis caching capabilities.\n *\n * @see {@link RedisCacheProvider}\n * @module alepha.cache.redis\n */\nexport const AlephaCacheRedis = $module({\n name: \"alepha.cache.redis\",\n services: [RedisCacheProvider],\n register: (alepha) =>\n alepha\n .with({\n provide: CacheProvider,\n use: RedisCacheProvider,\n optional: true,\n })\n .with(AlephaCache),\n});\n"],"mappings":"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"}