gemi 0.115.0 → 0.117.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 (348) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/ai/index.js.map +1 -1
  3. package/dist/app/index.js +2 -2
  4. package/dist/app/index.js.map +1 -1
  5. package/dist/auth/AuthServiceProvider.d.ts.map +1 -1
  6. package/dist/auth/config.d.ts +27 -0
  7. package/dist/auth/config.d.ts.map +1 -1
  8. package/dist/auth/connections/ConnectionManager.d.ts +133 -0
  9. package/dist/auth/connections/ConnectionManager.d.ts.map +1 -0
  10. package/dist/auth/connections/ConnectionStore.d.ts +146 -0
  11. package/dist/auth/connections/ConnectionStore.d.ts.map +1 -0
  12. package/dist/auth/connections/ConnectionsController.d.ts +23 -0
  13. package/dist/auth/connections/ConnectionsController.d.ts.map +1 -0
  14. package/dist/auth/connections/OAuthConnectionProvider.d.ts +105 -0
  15. package/dist/auth/connections/OAuthConnectionProvider.d.ts.map +1 -0
  16. package/dist/auth/connections/errors.d.ts +32 -0
  17. package/dist/auth/connections/errors.d.ts.map +1 -0
  18. package/dist/auth/oauth/oauthState.d.ts +15 -2
  19. package/dist/auth/oauth/oauthState.d.ts.map +1 -1
  20. package/dist/auth/routes.d.ts +13 -0
  21. package/dist/auth/routes.d.ts.map +1 -1
  22. package/dist/bin/orm/emit.d.ts.map +1 -1
  23. package/dist/bin/orm-generator.js +51 -4
  24. package/dist/bin/orm-generator.js.map +4 -4
  25. package/dist/bun/plugin.js +2 -2
  26. package/dist/bun/plugin.js.map +1 -1
  27. package/dist/bun/preload.js +2 -2
  28. package/dist/bun/preload.js.map +1 -1
  29. package/dist/chunk-00n3gwr8.js +71 -0
  30. package/dist/chunk-00n3gwr8.js.map +13 -0
  31. package/dist/{chunk-t4yh4nf9.js → chunk-076jezzg.js} +3 -3
  32. package/dist/{chunk-t4yh4nf9.js.map → chunk-076jezzg.js.map} +1 -1
  33. package/dist/{chunk-qjhw8nje.js → chunk-077rea02.js} +1 -1
  34. package/dist/{chunk-0av6xvm6.js → chunk-1ckha5dt.js} +3 -3
  35. package/dist/{chunk-0av6xvm6.js.map → chunk-1ckha5dt.js.map} +1 -1
  36. package/dist/{chunk-mschc94q.js → chunk-1zzd39qg.js} +2 -2
  37. package/dist/{chunk-mschc94q.js.map → chunk-1zzd39qg.js.map} +1 -1
  38. package/dist/{chunk-5stgtwqw.js → chunk-2bja99cq.js} +2 -2
  39. package/dist/{chunk-5stgtwqw.js.map → chunk-2bja99cq.js.map} +1 -1
  40. package/dist/chunk-2czfefn2.js +10 -0
  41. package/dist/{chunk-t4t3hqe0.js.map → chunk-2czfefn2.js.map} +5 -4
  42. package/dist/{chunk-aazmrxj9.js → chunk-37pk2cjg.js} +2 -2
  43. package/dist/{chunk-aazmrxj9.js.map → chunk-37pk2cjg.js.map} +1 -1
  44. package/dist/{chunk-qsrcdzan.js → chunk-3w4hk6q2.js} +3 -3
  45. package/dist/{chunk-qsrcdzan.js.map → chunk-3w4hk6q2.js.map} +1 -1
  46. package/dist/chunk-51w202vr.js +5 -0
  47. package/dist/{chunk-whtzggn6.js.map → chunk-51w202vr.js.map} +3 -4
  48. package/dist/{chunk-0z8ejxre.js → chunk-5m8vvtvn.js} +3 -3
  49. package/dist/{chunk-0z8ejxre.js.map → chunk-5m8vvtvn.js.map} +1 -1
  50. package/dist/{chunk-jafm3613.js → chunk-5p4z1fgc.js} +2 -2
  51. package/dist/{chunk-jafm3613.js.map → chunk-5p4z1fgc.js.map} +1 -1
  52. package/dist/{chunk-kejwjerj.js → chunk-5w6kyxsj.js} +2 -2
  53. package/dist/{chunk-kejwjerj.js.map → chunk-5w6kyxsj.js.map} +1 -1
  54. package/dist/{chunk-pyk3gzvc.js → chunk-62jw0vsf.js} +2 -2
  55. package/dist/{chunk-pyk3gzvc.js.map → chunk-62jw0vsf.js.map} +1 -1
  56. package/dist/chunk-63ewmmhc.js +5 -0
  57. package/dist/{chunk-t24p7gc6.js.map → chunk-63ewmmhc.js.map} +1 -1
  58. package/dist/{chunk-p1aqq0ga.js → chunk-6ggmk2eg.js} +3 -3
  59. package/dist/{chunk-p1aqq0ga.js.map → chunk-6ggmk2eg.js.map} +1 -1
  60. package/dist/{chunk-2tzm58j3.js → chunk-6y4nk9f1.js} +2 -2
  61. package/dist/{chunk-2tzm58j3.js.map → chunk-6y4nk9f1.js.map} +1 -1
  62. package/dist/{chunk-1hr4xht1.js → chunk-8gtc3s1y.js} +2 -2
  63. package/dist/{chunk-1hr4xht1.js.map → chunk-8gtc3s1y.js.map} +1 -1
  64. package/dist/{chunk-frerzsxq.js → chunk-a22h5cby.js} +2 -2
  65. package/dist/{chunk-frerzsxq.js.map → chunk-a22h5cby.js.map} +1 -1
  66. package/dist/{chunk-zyt2w03d.js → chunk-a32wfzdq.js} +2 -2
  67. package/dist/{chunk-zyt2w03d.js.map → chunk-a32wfzdq.js.map} +1 -1
  68. package/dist/{chunk-t04f3akb.js → chunk-a77586gv.js} +2 -2
  69. package/dist/{chunk-t04f3akb.js.map → chunk-a77586gv.js.map} +1 -1
  70. package/dist/chunk-a7r39713.js +5 -0
  71. package/dist/chunk-a7r39713.js.map +10 -0
  72. package/dist/{chunk-vxm6yevm.js → chunk-arwmvgrz.js} +2 -2
  73. package/dist/{chunk-vxm6yevm.js.map → chunk-arwmvgrz.js.map} +1 -1
  74. package/dist/{chunk-41da9628.js → chunk-bvqae0px.js} +1 -1
  75. package/dist/{chunk-d9mar37g.js → chunk-c2n6zdyd.js} +2 -2
  76. package/dist/{chunk-d9mar37g.js.map → chunk-c2n6zdyd.js.map} +1 -1
  77. package/dist/{chunk-56jky6vf.js → chunk-cytss9mb.js} +2 -2
  78. package/dist/{chunk-56jky6vf.js.map → chunk-cytss9mb.js.map} +1 -1
  79. package/dist/{chunk-vw8849ys.js → chunk-dcgvq79a.js} +2 -2
  80. package/dist/{chunk-m54s6jpr.js.map → chunk-dcgvq79a.js.map} +1 -1
  81. package/dist/{chunk-6chz1ktz.js → chunk-de4t48j9.js} +3 -3
  82. package/dist/{chunk-6chz1ktz.js.map → chunk-de4t48j9.js.map} +1 -1
  83. package/dist/{chunk-cbqazg9k.js → chunk-dz99g58c.js} +2 -2
  84. package/dist/{chunk-cbqazg9k.js.map → chunk-dz99g58c.js.map} +1 -1
  85. package/dist/{chunk-e573yxpq.js → chunk-e7ezmag9.js} +3 -3
  86. package/dist/{chunk-e573yxpq.js.map → chunk-e7ezmag9.js.map} +1 -1
  87. package/dist/{chunk-yer1r3t3.js → chunk-epvpmakp.js} +2 -2
  88. package/dist/{chunk-yer1r3t3.js.map → chunk-epvpmakp.js.map} +1 -1
  89. package/dist/{chunk-yhrye9cm.js → chunk-f8vcwzgd.js} +2 -2
  90. package/dist/{chunk-yhrye9cm.js.map → chunk-f8vcwzgd.js.map} +1 -1
  91. package/dist/chunk-fa37qzm3.js +5 -0
  92. package/dist/chunk-fa37qzm3.js.map +12 -0
  93. package/dist/{chunk-fxgxf16k.js → chunk-ffgmqwzy.js} +2 -2
  94. package/dist/{chunk-fxgxf16k.js.map → chunk-ffgmqwzy.js.map} +1 -1
  95. package/dist/{chunk-er3wf6ng.js → chunk-fk6nxhxn.js} +2 -2
  96. package/dist/{chunk-er3wf6ng.js.map → chunk-fk6nxhxn.js.map} +1 -1
  97. package/dist/{chunk-3hrj5rwm.js → chunk-fnwbbmnk.js} +2 -2
  98. package/dist/{chunk-3hrj5rwm.js.map → chunk-fnwbbmnk.js.map} +1 -1
  99. package/dist/chunk-g2wncay4.js +9 -0
  100. package/dist/{chunk-hytt9qyj.js.map → chunk-g2wncay4.js.map} +3 -4
  101. package/dist/{chunk-gts5p6mw.js → chunk-ge9xjwgq.js} +4 -4
  102. package/dist/{chunk-gts5p6mw.js.map → chunk-ge9xjwgq.js.map} +1 -1
  103. package/dist/{chunk-tw5kwbx4.js → chunk-gjy2qjcr.js} +2 -2
  104. package/dist/{chunk-tw5kwbx4.js.map → chunk-gjy2qjcr.js.map} +1 -1
  105. package/dist/{chunk-15c2vgaf.js → chunk-gn7kne7r.js} +2 -2
  106. package/dist/{chunk-15c2vgaf.js.map → chunk-gn7kne7r.js.map} +1 -1
  107. package/dist/{chunk-9n1hx3mq.js → chunk-hr61ekcr.js} +3 -3
  108. package/dist/{chunk-9n1hx3mq.js.map → chunk-hr61ekcr.js.map} +1 -1
  109. package/dist/{chunk-m54s6jpr.js → chunk-hvgmqt6z.js} +2 -2
  110. package/dist/{chunk-vw8849ys.js.map → chunk-hvgmqt6z.js.map} +1 -1
  111. package/dist/{chunk-aeh67ynz.js → chunk-hvpn18bb.js} +3 -3
  112. package/dist/{chunk-aeh67ynz.js.map → chunk-hvpn18bb.js.map} +1 -1
  113. package/dist/{chunk-jdhdrr2k.js → chunk-j1453bn1.js} +2 -2
  114. package/dist/{chunk-jdhdrr2k.js.map → chunk-j1453bn1.js.map} +1 -1
  115. package/dist/{chunk-tn2bryy2.js → chunk-jpetnpzn.js} +2 -2
  116. package/dist/{chunk-tn2bryy2.js.map → chunk-jpetnpzn.js.map} +1 -1
  117. package/dist/{chunk-mtbygktb.js → chunk-jqrs16g2.js} +3 -3
  118. package/dist/{chunk-mtbygktb.js.map → chunk-jqrs16g2.js.map} +1 -1
  119. package/dist/{chunk-4mfrgd5m.js → chunk-kk77x2v4.js} +2 -2
  120. package/dist/{chunk-4mfrgd5m.js.map → chunk-kk77x2v4.js.map} +1 -1
  121. package/dist/chunk-kpn7f58a.js +14 -0
  122. package/dist/{chunk-dexdkh3z.js.map → chunk-kpn7f58a.js.map} +11 -6
  123. package/dist/chunk-mhteg4yz.js +57 -0
  124. package/dist/{chunk-d6591xck.js.map → chunk-mhteg4yz.js.map} +16 -12
  125. package/dist/{chunk-n672d4g8.js → chunk-n9jyjp4a.js} +2 -2
  126. package/dist/{chunk-n672d4g8.js.map → chunk-n9jyjp4a.js.map} +1 -1
  127. package/dist/{chunk-vbdty91k.js → chunk-nb2gggba.js} +3 -3
  128. package/dist/{chunk-vbdty91k.js.map → chunk-nb2gggba.js.map} +1 -1
  129. package/dist/chunk-ntxsdchf.js +50 -0
  130. package/dist/chunk-ntxsdchf.js.map +10 -0
  131. package/dist/{chunk-2z1981d0.js → chunk-pepzecrm.js} +2 -2
  132. package/dist/{chunk-2z1981d0.js.map → chunk-pepzecrm.js.map} +1 -1
  133. package/dist/{chunk-785b41h1.js → chunk-pgy3mfxw.js} +3 -3
  134. package/dist/{chunk-785b41h1.js.map → chunk-pgy3mfxw.js.map} +1 -1
  135. package/dist/{chunk-98m15b9x.js → chunk-qhw0hej0.js} +2 -2
  136. package/dist/{chunk-98m15b9x.js.map → chunk-qhw0hej0.js.map} +1 -1
  137. package/dist/{chunk-303mprnv.js → chunk-r3v2cnrj.js} +2 -2
  138. package/dist/{chunk-303mprnv.js.map → chunk-r3v2cnrj.js.map} +1 -1
  139. package/dist/{chunk-ktr3k768.js → chunk-rhmpphhc.js} +3 -3
  140. package/dist/{chunk-ktr3k768.js.map → chunk-rhmpphhc.js.map} +1 -1
  141. package/dist/{chunk-fre6f4nk.js → chunk-s4f56035.js} +2 -2
  142. package/dist/{chunk-fre6f4nk.js.map → chunk-s4f56035.js.map} +1 -1
  143. package/dist/{chunk-sr942vb1.js → chunk-sj4f7x4p.js} +2 -2
  144. package/dist/{chunk-sr942vb1.js.map → chunk-sj4f7x4p.js.map} +1 -1
  145. package/dist/{chunk-wyw6p3fg.js → chunk-sksmnjkn.js} +3 -3
  146. package/dist/{chunk-wyw6p3fg.js.map → chunk-sksmnjkn.js.map} +1 -1
  147. package/dist/{chunk-kqxdsv22.js → chunk-stnd9qht.js} +2 -2
  148. package/dist/{chunk-kqxdsv22.js.map → chunk-stnd9qht.js.map} +1 -1
  149. package/dist/{chunk-1myhm0g3.js → chunk-t65074mw.js} +3 -3
  150. package/dist/{chunk-1myhm0g3.js.map → chunk-t65074mw.js.map} +1 -1
  151. package/dist/chunk-t87w4h84.js +24 -0
  152. package/dist/{chunk-wzrt5mac.js.map → chunk-t87w4h84.js.map} +2 -2
  153. package/dist/{chunk-z10cas5r.js → chunk-thz75w6e.js} +2 -2
  154. package/dist/{chunk-z10cas5r.js.map → chunk-thz75w6e.js.map} +1 -1
  155. package/dist/{chunk-xt9tv99x.js → chunk-v8vnxkvp.js} +2 -2
  156. package/dist/{chunk-xt9tv99x.js.map → chunk-v8vnxkvp.js.map} +1 -1
  157. package/dist/{chunk-x7cpth2f.js → chunk-veq07rp8.js} +1 -1
  158. package/dist/{chunk-agyfv3f8.js → chunk-vj5necm0.js} +1 -1
  159. package/dist/{chunk-mps6ynbh.js → chunk-vtc23xpk.js} +2 -2
  160. package/dist/{chunk-mps6ynbh.js.map → chunk-vtc23xpk.js.map} +1 -1
  161. package/dist/{chunk-jmpw6bqz.js → chunk-w4e480he.js} +2 -2
  162. package/dist/{chunk-jmpw6bqz.js.map → chunk-w4e480he.js.map} +1 -1
  163. package/dist/{chunk-8rv62v14.js → chunk-wgb79ysr.js} +2 -2
  164. package/dist/{chunk-8rv62v14.js.map → chunk-wgb79ysr.js.map} +1 -1
  165. package/dist/{chunk-1vzks22y.js → chunk-wk1tp1f1.js} +2 -2
  166. package/dist/{chunk-1vzks22y.js.map → chunk-wk1tp1f1.js.map} +1 -1
  167. package/dist/{chunk-bra1a1fq.js → chunk-wwdbgshx.js} +2 -2
  168. package/dist/{chunk-bra1a1fq.js.map → chunk-wwdbgshx.js.map} +1 -1
  169. package/dist/{chunk-eqse2m6n.js → chunk-wzzrz612.js} +2 -2
  170. package/dist/{chunk-eqse2m6n.js.map → chunk-wzzrz612.js.map} +1 -1
  171. package/dist/{chunk-hdps9n52.js → chunk-x7qwx07b.js} +3 -3
  172. package/dist/{chunk-hdps9n52.js.map → chunk-x7qwx07b.js.map} +1 -1
  173. package/dist/chunk-x84as3vy.js +5 -0
  174. package/dist/chunk-x84as3vy.js.map +12 -0
  175. package/dist/chunk-y0en07zh.js +302 -0
  176. package/dist/chunk-y0en07zh.js.map +17 -0
  177. package/dist/{chunk-d2gfcf85.js → chunk-yfwrbfnw.js} +2 -2
  178. package/dist/{chunk-d2gfcf85.js.map → chunk-yfwrbfnw.js.map} +1 -1
  179. package/dist/{chunk-mazznq15.js → chunk-yhp6bvhj.js} +3 -3
  180. package/dist/{chunk-mazznq15.js.map → chunk-yhp6bvhj.js.map} +1 -1
  181. package/dist/{chunk-tzm3c7n6.js → chunk-yvtmygrk.js} +4 -4
  182. package/dist/{chunk-tzm3c7n6.js.map → chunk-yvtmygrk.js.map} +1 -1
  183. package/dist/{chunk-mqfzdfnz.js → chunk-zkjs3m5m.js} +2 -2
  184. package/dist/{chunk-mqfzdfnz.js.map → chunk-zkjs3m5m.js.map} +1 -1
  185. package/dist/chunk-zw6843j3.js +5 -0
  186. package/dist/chunk-zw6843j3.js.map +12 -0
  187. package/dist/chunks/{ThemeProvider-BgLyzzOL.js → ThemeProvider-BLI4zpLu.js} +8 -3
  188. package/dist/chunks/ThemeProvider-BLI4zpLu.js.map +1 -0
  189. package/dist/client/ClientRouter.d.ts +3 -5
  190. package/dist/client/ClientRouter.d.ts.map +1 -1
  191. package/dist/client/ClientRouterContext.d.ts.map +1 -1
  192. package/dist/client/Head.d.ts +1 -1
  193. package/dist/client/Head.d.ts.map +1 -1
  194. package/dist/client/ServerDataProvider.d.ts +2 -0
  195. package/dist/client/ServerDataProvider.d.ts.map +1 -1
  196. package/dist/client/createRoot.d.ts +2 -4
  197. package/dist/client/createRoot.d.ts.map +1 -1
  198. package/dist/client/fonts.d.ts +69 -0
  199. package/dist/client/fonts.d.ts.map +1 -0
  200. package/dist/client/htmlAttributes.d.ts +18 -0
  201. package/dist/client/htmlAttributes.d.ts.map +1 -0
  202. package/dist/client/index.d.ts +2 -0
  203. package/dist/client/index.d.ts.map +1 -1
  204. package/dist/client/index.js +153 -6
  205. package/dist/client/index.js.map +1 -1
  206. package/dist/config/index.js +1 -1
  207. package/dist/console/builtins.d.ts +13 -0
  208. package/dist/console/builtins.d.ts.map +1 -0
  209. package/dist/console/run.js +13 -11
  210. package/dist/console/run.js.map +6 -5
  211. package/dist/console/runner.d.ts.map +1 -1
  212. package/dist/console/usage.d.ts +2 -0
  213. package/dist/console/usage.d.ts.map +1 -1
  214. package/dist/container/index.js +2 -2
  215. package/dist/container/index.js.map +1 -1
  216. package/dist/database/index.js +2 -2
  217. package/dist/database/index.js.map +1 -1
  218. package/dist/email/index.js +2 -2
  219. package/dist/email/index.js.map +1 -1
  220. package/dist/facades/Connections.d.ts +36 -0
  221. package/dist/facades/Connections.d.ts.map +1 -0
  222. package/dist/facades/Crypt.d.ts +26 -0
  223. package/dist/facades/Crypt.d.ts.map +1 -0
  224. package/dist/facades/Meta.d.ts +17 -1
  225. package/dist/facades/Meta.d.ts.map +1 -1
  226. package/dist/facades/Storage.d.ts +29 -0
  227. package/dist/facades/Storage.d.ts.map +1 -1
  228. package/dist/facades/index.d.ts +2 -0
  229. package/dist/facades/index.d.ts.map +1 -1
  230. package/dist/facades/index.js +2 -2
  231. package/dist/facades/index.js.map +1 -1
  232. package/dist/foundation/index.js +2 -2
  233. package/dist/foundation/index.js.map +1 -1
  234. package/dist/http/HttpRequest.d.ts +4 -0
  235. package/dist/http/HttpRequest.d.ts.map +1 -1
  236. package/dist/http/Metadata.d.ts +28 -0
  237. package/dist/http/Metadata.d.ts.map +1 -1
  238. package/dist/http/index.d.ts +3 -1
  239. package/dist/http/index.d.ts.map +1 -1
  240. package/dist/http/index.js +2 -2
  241. package/dist/http/index.js.map +1 -1
  242. package/dist/http/requestContext.d.ts +4 -0
  243. package/dist/http/requestContext.d.ts.map +1 -1
  244. package/dist/http/safeFetch/errors.d.ts +10 -0
  245. package/dist/http/safeFetch/errors.d.ts.map +1 -1
  246. package/dist/http/safeFetch/index.d.ts +1 -1
  247. package/dist/http/safeFetch/index.d.ts.map +1 -1
  248. package/dist/http/sitemap/Robots.d.ts +95 -0
  249. package/dist/http/sitemap/Robots.d.ts.map +1 -0
  250. package/dist/http/sitemap/Sitemap.d.ts +145 -0
  251. package/dist/http/sitemap/Sitemap.d.ts.map +1 -0
  252. package/dist/http/sitemap/fetchText.d.ts +16 -0
  253. package/dist/http/sitemap/fetchText.d.ts.map +1 -0
  254. package/dist/http/sitemap/index.d.ts +3 -0
  255. package/dist/http/sitemap/index.d.ts.map +1 -0
  256. package/dist/http/sitemap/xml.d.ts +21 -0
  257. package/dist/http/sitemap/xml.d.ts.map +1 -0
  258. package/dist/i18n/Translator.d.ts.map +1 -1
  259. package/dist/i18n/dictionaryRuntime.js +2 -2
  260. package/dist/i18n/dictionaryRuntime.js.map +1 -1
  261. package/dist/i18n/index.d.ts +1 -0
  262. package/dist/i18n/index.d.ts.map +1 -1
  263. package/dist/i18n/index.js +2 -2
  264. package/dist/i18n/index.js.map +2 -2
  265. package/dist/i18n/resolveLocale.d.ts +18 -0
  266. package/dist/i18n/resolveLocale.d.ts.map +1 -1
  267. package/dist/kernel/index.js +3 -3
  268. package/dist/kernel/index.js.map +3 -3
  269. package/dist/kernel/providers.d.ts +2 -2
  270. package/dist/kernel/providers.d.ts.map +1 -1
  271. package/dist/orm/Model.d.ts.map +1 -1
  272. package/dist/orm/encryption-rotate.d.ts +44 -0
  273. package/dist/orm/encryption-rotate.d.ts.map +1 -0
  274. package/dist/orm/encryption.d.ts +54 -0
  275. package/dist/orm/encryption.d.ts.map +1 -0
  276. package/dist/orm/index.d.ts +2 -0
  277. package/dist/orm/index.d.ts.map +1 -1
  278. package/dist/orm/index.js +3 -3
  279. package/dist/orm/index.js.map +4 -4
  280. package/dist/orm/schema.d.ts +25 -1
  281. package/dist/orm/schema.d.ts.map +1 -1
  282. package/dist/server/index.js +2 -2
  283. package/dist/server/index.js.map +1 -1
  284. package/dist/services/encryption/Encrypter.d.ts +86 -0
  285. package/dist/services/encryption/Encrypter.d.ts.map +1 -0
  286. package/dist/services/encryption/EncryptionServiceProvider.d.ts +13 -0
  287. package/dist/services/encryption/EncryptionServiceProvider.d.ts.map +1 -0
  288. package/dist/services/encryption/config.d.ts +36 -0
  289. package/dist/services/encryption/config.d.ts.map +1 -0
  290. package/dist/services/file-storage/drivers/FileStorageDriver.d.ts +12 -1
  291. package/dist/services/file-storage/drivers/FileStorageDriver.d.ts.map +1 -1
  292. package/dist/services/file-storage/drivers/FileSystemDriver.d.ts +8 -1
  293. package/dist/services/file-storage/drivers/FileSystemDriver.d.ts.map +1 -1
  294. package/dist/services/file-storage/drivers/types.d.ts +17 -0
  295. package/dist/services/file-storage/drivers/types.d.ts.map +1 -1
  296. package/dist/services/file-storage/putFromUrl.d.ts +56 -0
  297. package/dist/services/file-storage/putFromUrl.d.ts.map +1 -0
  298. package/dist/services/file-storage/sniffContentType.d.ts +33 -0
  299. package/dist/services/file-storage/sniffContentType.d.ts.map +1 -0
  300. package/dist/services/index.d.ts +19 -5
  301. package/dist/services/index.d.ts.map +1 -1
  302. package/dist/services/index.js +10 -10
  303. package/dist/services/index.js.map +4 -3
  304. package/dist/services/queue/DatabaseQueueDriver.d.ts +96 -7
  305. package/dist/services/queue/DatabaseQueueDriver.d.ts.map +1 -1
  306. package/dist/services/queue/Job.d.ts +72 -0
  307. package/dist/services/queue/Job.d.ts.map +1 -1
  308. package/dist/services/queue/MemoryQueueDriver.d.ts +22 -2
  309. package/dist/services/queue/MemoryQueueDriver.d.ts.map +1 -1
  310. package/dist/services/queue/QueueDriver.d.ts +74 -1
  311. package/dist/services/queue/QueueDriver.d.ts.map +1 -1
  312. package/dist/services/queue/QueueManager.d.ts +47 -0
  313. package/dist/services/queue/QueueManager.d.ts.map +1 -1
  314. package/dist/services/queue/batch.d.ts +112 -0
  315. package/dist/services/queue/batch.d.ts.map +1 -0
  316. package/dist/services/queue/queueDriverContract.d.ts +5 -0
  317. package/dist/services/queue/queueDriverContract.d.ts.map +1 -1
  318. package/dist/services/queue/work.js +2 -2
  319. package/dist/services/queue/work.js.map +1 -1
  320. package/dist/services/router/ViewRouteDispatcher.d.ts +3 -1
  321. package/dist/services/router/ViewRouteDispatcher.d.ts.map +1 -1
  322. package/dist/services/router/config.d.ts +19 -0
  323. package/dist/services/router/config.d.ts.map +1 -1
  324. package/dist/stats/route-table.js +1 -1
  325. package/dist/support/index.js +2 -2
  326. package/dist/support/index.js.map +1 -1
  327. package/dist/testing/index.js +1 -1
  328. package/dist/vitest/index.js +2 -2
  329. package/dist/vitest/index.js.map +1 -1
  330. package/package.json +1 -1
  331. package/dist/chunk-7g9yh5ae.js +0 -5
  332. package/dist/chunk-7g9yh5ae.js.map +0 -12
  333. package/dist/chunk-7vb4eey2.js +0 -217
  334. package/dist/chunk-7vb4eey2.js.map +0 -16
  335. package/dist/chunk-9b4erdy2.js +0 -5
  336. package/dist/chunk-9b4erdy2.js.map +0 -11
  337. package/dist/chunk-d6591xck.js +0 -46
  338. package/dist/chunk-dexdkh3z.js +0 -14
  339. package/dist/chunk-hytt9qyj.js +0 -54
  340. package/dist/chunk-t24p7gc6.js +0 -5
  341. package/dist/chunk-t4t3hqe0.js +0 -10
  342. package/dist/chunk-whtzggn6.js +0 -5
  343. package/dist/chunk-wzrt5mac.js +0 -24
  344. package/dist/chunks/ThemeProvider-BgLyzzOL.js.map +0 -1
  345. /package/dist/{chunk-qjhw8nje.js.map → chunk-077rea02.js.map} +0 -0
  346. /package/dist/{chunk-41da9628.js.map → chunk-bvqae0px.js.map} +0 -0
  347. /package/dist/{chunk-x7cpth2f.js.map → chunk-veq07rp8.js.map} +0 -0
  348. /package/dist/{chunk-agyfv3f8.js.map → chunk-vj5necm0.js.map} +0 -0
@@ -1,16 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../services/lock/MemoryLockStore.ts", "../services/lock/DatabaseLockStore.ts", "../services/queue/DatabaseQueueDriver.ts", "../services/queue/QueueManager.ts", "../services/queue/config.ts", "../services/queue/MemoryQueueDriver.ts", "../services/queue/Job.ts"],
4
- "sourcesContent": [
5
- "import { LockLostError, type LockStore } from \"./LockStore\";\n\ntype Entry = {\n owner: string;\n token: number;\n expiresAt: number;\n /** Running `fence` calls. A fenced lock cannot change hands, lease or not. */\n fences: number;\n};\n\n/**\n * Locks in this process's memory. Correct for one process and useless across\n * several: two servers each have their own map, so each takes every lock. It\n * is what the queue uses with the memory driver, and in tests.\n */\nexport class MemoryLockStore implements LockStore {\n private readonly entries = new Map<string, Entry>();\n private readonly counters = new Map<string, number>();\n\n async acquire(name: string, owner: string, ttlMs: number): Promise<number | null> {\n const now = Date.now();\n const entry = this.entries.get(name);\n if (entry && (entry.expiresAt > now || entry.fences > 0)) return null;\n const token = (entry?.token ?? 0) + 1;\n this.entries.set(name, { owner, token, expiresAt: now + Math.max(0, ttlMs), fences: 0 });\n return token;\n }\n\n async extend(name: string, owner: string, token: number, ttlMs: number): Promise<boolean> {\n const entry = this.current(name, owner, token);\n if (!entry) return false;\n entry.expiresAt = Date.now() + Math.max(0, ttlMs);\n return true;\n }\n\n async release(name: string, owner: string, token?: number): Promise<boolean> {\n const entry = this.current(name, owner, token);\n if (!entry) return false;\n entry.owner = \"\";\n entry.expiresAt = 0;\n return true;\n }\n\n async holder(name: string) {\n const entry = this.entries.get(name);\n if (!entry || entry.expiresAt <= Date.now()) return null;\n return { owner: entry.owner, token: entry.token };\n }\n\n async held(name: string, owner: string, token: number): Promise<boolean> {\n return this.current(name, owner, token) !== undefined;\n }\n\n async advance(name: string, value: number): Promise<boolean> {\n if ((this.counters.get(name) ?? -Infinity) >= value) return false;\n this.counters.set(name, value);\n return true;\n }\n\n async fence<T>(name: string, owner: string, token: number, fn: () => Promise<T>): Promise<T> {\n const entry = this.current(name, owner, token);\n if (!entry) throw new LockLostError(name, token);\n entry.fences++;\n try {\n return await fn();\n } finally {\n entry.fences--;\n }\n }\n\n private readonly windows = new Map<string, { count: number; endsAt: number }>();\n\n async hit(name: string, limit: number, windowMs: number) {\n const now = Date.now();\n let window = this.windows.get(name);\n if (!window || window.endsAt <= now) {\n window = { count: 0, endsAt: now + Math.max(1, windowMs) };\n this.windows.set(name, window);\n }\n if (window.count >= limit) return { allowed: false, resetInMs: window.endsAt - now };\n window.count++;\n return { allowed: true, resetInMs: window.endsAt - now };\n }\n\n async refund(name: string) {\n const window = this.windows.get(name);\n if (window && window.endsAt > Date.now() && window.count > 0) window.count--;\n }\n\n private current(name: string, owner: string, token?: number) {\n const entry = this.entries.get(name);\n if (!entry || entry.owner !== owner || entry.expiresAt <= Date.now()) return undefined;\n if (token !== undefined && entry.token !== token) return undefined;\n return entry;\n }\n}\n",
6
- "import type { SQL } from \"bun\";\n\nimport type { DatabaseConnection } from \"../../database/Connection\";\nimport type { Dialect } from \"../../database/dialect\";\nimport { runOnConnection, withTransaction } from \"../../orm/context\";\nimport { LockLostError, type LockStore } from \"./LockStore\";\n\nexport type DatabaseLockStoreOptions = {\n /**\n * The locks table. Default `gemi_locks`. A plain identifier, because it is\n * spliced into every statement.\n */\n table?: string;\n /**\n * SQLite only: the busy timeout given to a connection still at SQLite's `0`,\n * as `DatabaseQueueDriver` does. Default `1000`; `0` leaves the connection\n * alone.\n */\n busyTimeout?: number;\n};\n\n/**\n * Locks in a table of the application's database, so every process sharing\n * the database sees the same holder. One row per lock name, kept after\n * release so the name's fencing token keeps growing.\n *\n * Every statement is a single atomic row update, guarded by the holder's\n * owner, token and lease: `acquire` is \"take the row if its lease ran out\",\n * `extend` and `release` are \"change the row if it is still mine\". Leases are\n * measured on the database's clock, as the queue's are, so skewed replicas\n * agree on when one ran out. No Redis is involved.\n *\n * `fence` opens a transaction that locks the row first (`SELECT … FOR UPDATE`\n * on Postgres and MySQL, a write on SQLite, which takes the file's write\n * lock), so a new holder's `acquire` waits until the fenced work commits.\n *\n * Written for SQLite, Postgres and MySQL 8 / MariaDB 10.6+, like the queue's\n * driver.\n */\nexport class DatabaseLockStore implements LockStore {\n readonly table: string;\n private readonly sql: SQL;\n private readonly dialect: Dialect;\n private readonly connection: string | undefined;\n private readonly busyTimeout: number;\n private configured: Promise<void> | undefined;\n\n constructor(\n connection: Pick<DatabaseConnection, \"sql\" | \"dialect\"> &\n Partial<Pick<DatabaseConnection, \"name\">>,\n options: DatabaseLockStoreOptions = {},\n ) {\n const table = options.table ?? \"gemi_locks\";\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {\n throw new Error(\n `The lock table name \"${table}\" is not a plain identifier. Use ` +\n `letters, digits and underscores.`,\n );\n }\n this.table = table;\n this.sql = connection.sql;\n this.dialect = connection.dialect;\n this.connection = connection.name;\n this.busyTimeout = options.busyTimeout ?? 1000;\n }\n\n /** Creates the table if it does not exist; the DDL Prisma makes for the model in the docs. */\n async createTable(): Promise<void> {\n await this.configure();\n for (const statement of createLockTableStatements(this.dialect, this.table)) {\n await this.sql.unsafe(statement);\n }\n }\n\n async acquire(name: string, owner: string, ttlMs: number): Promise<number | null> {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n await this.seed(q, name);\n\n if (this.mysql) {\n const result = await q`\n UPDATE ${this.name(q)}\n SET owner = ${owner}, token = token + 1, expires_at = ${now} + ${this.ms(q, ttlMs)},\n updated_at = ${now}\n WHERE name = ${name} AND expires_at <= ${now}\n `;\n if (affected(result) === 0) return null;\n // The owner is unique to this acquisition, so this reads our own row\n // unless the lease already ran out and was taken again.\n const [row] = (await q`\n SELECT token FROM ${this.name(q)} WHERE name = ${name} AND owner = ${owner}\n `) as Array<{ token: unknown }>;\n return row ? Number(row.token) : null;\n }\n\n const rows = (await q`\n UPDATE ${this.name(q)}\n SET owner = ${owner}, token = token + 1, expires_at = ${now} + ${this.ms(q, ttlMs)},\n updated_at = ${now}\n WHERE name = ${name} AND expires_at <= ${now}\n RETURNING token\n `) as Array<{ token: unknown }>;\n return rows[0] ? Number(rows[0].token) : null;\n }\n\n async extend(name: string, owner: string, token: number, ttlMs: number): Promise<boolean> {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n const result = await q`\n UPDATE ${this.name(q)}\n SET expires_at = ${now} + ${this.ms(q, ttlMs)}, updated_at = ${now}\n WHERE name = ${name} AND owner = ${owner} AND token = ${token} AND expires_at > ${now}\n `;\n if (affected(result) > 0) return true;\n // MySQL counts changed rows, not matched ones, so a renewal that lands in\n // the same millisecond as the last one reports nothing changed.\n return this.mysql ? this.held(name, owner, token) : false;\n }\n\n async release(name: string, owner: string, token?: number): Promise<boolean> {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n const byToken = token === undefined ? q`1 = 1` : q`token = ${token}`;\n const result = await q`\n UPDATE ${this.name(q)}\n SET owner = '', expires_at = 0, updated_at = ${now}\n WHERE name = ${name} AND owner = ${owner} AND ${byToken} AND expires_at > ${now}\n `;\n return affected(result) > 0;\n }\n\n async holder(name: string): Promise<{ owner: string; token: number } | null> {\n await this.configure();\n const q = this.sql;\n const [row] = (await q`\n SELECT owner, token FROM ${this.name(q)}\n WHERE name = ${name} AND expires_at > ${this.now(q)}\n `) as Array<{ owner: unknown; token: unknown }>;\n return row ? { owner: String(row.owner), token: Number(row.token) } : null;\n }\n\n async held(name: string, owner: string, token: number): Promise<boolean> {\n await this.configure();\n const q = this.sql;\n const rows = (await q`\n SELECT 1 AS held FROM ${this.name(q)}\n WHERE name = ${name} AND owner = ${owner} AND token = ${token} AND expires_at > ${this.now(q)}\n `) as unknown[];\n return rows.length > 0;\n }\n\n async advance(name: string, value: number): Promise<boolean> {\n await this.configure();\n const q = this.sql;\n await this.seed(q, name);\n const result = await q`\n UPDATE ${this.name(q)}\n SET token = ${this.int(q, value)}, updated_at = ${this.now(q)}\n WHERE name = ${name} AND token < ${this.int(q, value)}\n `;\n return affected(result) > 0;\n }\n\n async fence<T>(name: string, owner: string, token: number, fn: () => Promise<T>): Promise<T> {\n await this.configure();\n const body = async (tx: SQL) => {\n const now = this.now(tx);\n let ok: boolean;\n if (this.dialect === \"sqlite\") {\n // SQLite has no row locks; a write takes the file's write lock until\n // the commit, which is what keeps another holder out.\n const result = await tx`\n UPDATE ${this.name(tx)} SET updated_at = ${now}\n WHERE name = ${name} AND owner = ${owner} AND token = ${token} AND expires_at > ${now}\n `;\n ok = affected(result) > 0;\n } else {\n const rows = (await tx`\n SELECT 1 AS held FROM ${this.name(tx)}\n WHERE name = ${name} AND owner = ${owner} AND token = ${token} AND expires_at > ${now}\n FOR UPDATE\n `) as unknown[];\n ok = rows.length > 0;\n }\n if (!ok) throw new LockLostError(name, token);\n return fn();\n };\n\n const connection = this.connection;\n if (connection === undefined) {\n return (await this.sql.begin((tx) => body(tx))) as T;\n }\n // An ORM transaction on the store's own connection, so model writes in\n // `fn` join it and commit only while the hold is current.\n return withTransaction(this.sql, (tx) => runOnConnection(connection, () => body(tx)), {\n connection,\n });\n }\n\n /**\n * A fixed window in the lock's row: `token` is the count and `expires_at`\n * the window's end. One guarded `UPDATE` either starts a new window, counts\n * a hit in the current one, or matches nothing because it is full.\n */\n async hit(name: string, limit: number, windowMs: number) {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n await this.seed(q, name);\n const window = this.ms(q, Math.max(1, windowMs));\n const result = await q`\n UPDATE ${this.name(q)}\n SET token = CASE WHEN expires_at <= ${now} THEN 1 ELSE token + 1 END,\n expires_at = CASE WHEN expires_at <= ${now} THEN ${now} + ${window} ELSE expires_at END,\n updated_at = ${now}\n WHERE name = ${name} AND (expires_at <= ${now} OR token < ${this.int(q, limit)})\n `;\n const allowed = affected(result) > 0;\n const [row] = (await q`\n SELECT expires_at - ${now} AS reset FROM ${this.name(q)} WHERE name = ${name}\n `) as Array<{ reset: unknown }>;\n return { allowed, resetInMs: Math.max(0, Number(row?.reset ?? 0)) };\n }\n\n async refund(name: string) {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n await q`\n UPDATE ${this.name(q)} SET token = token - 1, updated_at = ${now}\n WHERE name = ${name} AND token > 0 AND expires_at > ${now}\n `;\n }\n\n /**\n * Deletes locks that are free and were last touched more than `olderThanMs`\n * ago, and resolves to how many. Tokens start again from 1 for a pruned\n * name, so keep `olderThanMs` far above any lease.\n */\n async prune(olderThanMs: number): Promise<number> {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n const result = await q`\n DELETE FROM ${this.name(q)}\n WHERE expires_at <= ${now} AND updated_at <= ${now} - ${this.ms(q, olderThanMs)}\n `;\n return affected(result);\n }\n\n /** Makes sure the name has a row, free, so the guarded `UPDATE`s have something to match. */\n private async seed(q: SQL, name: string) {\n const now = this.now(q);\n if (this.mysql) {\n await q`\n INSERT IGNORE INTO ${this.name(q)} (name, owner, token, expires_at, updated_at)\n VALUES (${name}, '', 0, 0, ${now})\n `;\n return;\n }\n await q`\n INSERT INTO ${this.name(q)} (name, owner, token, expires_at, updated_at)\n VALUES (${name}, '', 0, 0, ${now})\n ON CONFLICT (name) DO NOTHING\n `;\n }\n\n private get mysql() {\n return this.dialect === \"mysql\" || this.dialect === \"mariadb\";\n }\n\n /** See `DatabaseQueueDriver.configure`. */\n private configure(): Promise<void> {\n if (this.dialect !== \"sqlite\" || this.busyTimeout === 0) return Promise.resolve();\n this.configured ??= (async () => {\n const [current] = (await this.sql.unsafe(\"PRAGMA busy_timeout\")) as Array<{\n timeout: number | string;\n }>;\n if (Number(current?.timeout ?? 0) !== 0) return;\n await this.sql.unsafe(`PRAGMA busy_timeout = ${this.busyTimeout}`);\n })().catch((error) => {\n this.configured = undefined;\n throw error;\n });\n return this.configured;\n }\n\n private name(q: SQL) {\n return q.unsafe(this.mysql ? `\\`${this.table}\\`` : `\"${this.table}\"`);\n }\n\n /** The database's clock in epoch milliseconds, as the queue driver reads it. */\n private now(q: SQL) {\n switch (this.dialect) {\n case \"postgres\":\n return q`floor(extract(epoch from statement_timestamp()) * 1000)::bigint`;\n case \"mysql\":\n case \"mariadb\":\n return q`(TIMESTAMPDIFF(MICROSECOND, '1970-01-01 00:00:00', UTC_TIMESTAMP(6)) DIV 1000)`;\n case \"sqlite\":\n return q`CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)`;\n }\n }\n\n private ms(q: SQL, value: number) {\n return this.int(q, Math.max(0, Math.round(value)));\n }\n\n private int(q: SQL, value: number) {\n const n = Math.round(value);\n return this.mysql ? q`CAST(${n} AS SIGNED)` : q`CAST(${n} AS BIGINT)`;\n }\n}\n\nfunction affected(result: unknown): number {\n const counts = result as { count?: number | null; affectedRows?: number | null };\n return Math.max(Number(counts.count ?? 0), Number(counts.affectedRows ?? 0));\n}\n\n/** The locks table, as Prisma generates it for the model in the docs, with `IF NOT EXISTS`. */\nexport function createLockTableStatements(dialect: Dialect, table: string): string[] {\n if (dialect === \"mysql\" || dialect === \"mariadb\") {\n return [\n `CREATE TABLE IF NOT EXISTS \\`${table}\\` (\n \\`name\\` VARCHAR(191) NOT NULL,\n \\`owner\\` VARCHAR(191) NOT NULL,\n \\`token\\` BIGINT NOT NULL DEFAULT 0,\n \\`expires_at\\` BIGINT NOT NULL,\n \\`updated_at\\` BIGINT NOT NULL,\n\n PRIMARY KEY (\\`name\\`)\n) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`,\n ];\n }\n const key = dialect === \"sqlite\" ? `\"name\" TEXT NOT NULL PRIMARY KEY,` : `\"name\" TEXT NOT NULL,`;\n const constraint =\n dialect === \"sqlite\" ? \"\" : `,\\n\\n CONSTRAINT \"${table}_pkey\" PRIMARY KEY (\"name\")`;\n return [\n `CREATE TABLE IF NOT EXISTS \"${table}\" (\n ${key}\n \"owner\" TEXT NOT NULL,\n \"token\" BIGINT NOT NULL DEFAULT 0,\n \"expires_at\" BIGINT NOT NULL,\n \"updated_at\" BIGINT NOT NULL${constraint}\n)`,\n ];\n}\n",
7
- "import type { SQL, TransactionSQL } from \"bun\";\n\nimport type { DatabaseConnection } from \"../../database/Connection\";\nimport type { Dialect } from \"../../database/dialect\";\nimport { DatabaseLockStore } from \"../lock/DatabaseLockStore\";\nimport { commitDependsOn, currentConnectionName, currentTransaction } from \"../../orm/context\";\nimport type {\n ClaimOptions,\n ClaimedJob,\n EnqueueJob,\n JobFailure,\n JobRelease,\n QueueDriver,\n} from \"./QueueDriver\";\n\nexport type DatabaseQueueDriverOptions = {\n /**\n * The jobs table. Default `gemi_jobs`. A plain identifier — letters, digits\n * and underscores — because it is spliced into every statement.\n */\n table?: string;\n /**\n * SQLite only: how long, in milliseconds, a statement waits for another\n * process's write lock before failing with `SQLITE_BUSY`. Default `1000`.\n * Applied only to a connection whose busy timeout is still SQLite's `0`, so\n * one the application configured itself keeps its own; `0` here leaves the\n * connection alone. See `configure` for what the wait costs.\n */\n busyTimeout?: number;\n};\n\n/**\n * What a row's `status` can be. `pending` rows are claimable once\n * `available_at` has passed; `claimed` ones once `lease_expires_at` has. `dead`\n * is terminal and kept, with `last_error`, until `prune` removes it. A\n * completed job is deleted, so there is no status for it.\n */\nexport type DatabaseJobStatus = \"pending\" | \"claimed\" | \"dead\";\n\ntype Row = {\n id: string;\n name: string;\n payload: string;\n attempts: number | string;\n available_at: number | string | bigint;\n created_at: number | string | bigint;\n};\n\n/**\n * A driver that keeps jobs in a table of the application's own database, so\n * they outlive the process: a job dispatched before a deploy, a scale-in or a\n * crash is still there afterwards, and any process sharing the database claims\n * it — the one waiting at once, the one that was running once its lease runs\n * out. Nothing replays anything at boot; claiming is the recovery.\n *\n * **At least once, not exactly once.** A job whose lease lapses while it is\n * still running — its process frozen, or unable to reach the database to\n * heartbeat, for longer than the visibility timeout — can be claimed and run\n * by another process too, and a job whose process died after its work but\n * before `complete` reached the database runs again. Jobs must be idempotent.\n *\n * ### Claiming\n *\n * Postgres and MySQL lock the rows they hand out with `FOR UPDATE SKIP\n * LOCKED`, so concurrent claimers — in this process or any other — each take\n * different rows and none waits on another. Postgres does it in one statement\n * (a locking CTE feeding an `UPDATE … RETURNING`); MySQL has no `RETURNING`\n * and refuses a `LIMIT` inside `IN (…)`, so there it is a `SELECT … FOR\n * UPDATE SKIP LOCKED` and one `UPDATE` per row in a transaction, at READ\n * COMMITTED and in two passes — see `claimLocking`, where both are load\n * bearing rather than tidying. SQLite has no row locks and needs none: it\n * runs one write at a time, so a single `UPDATE … RETURNING` is atomic.\n * MariaDB needs 10.6 for `SKIP LOCKED`.\n *\n * It honours `registered`: a claim is `WHERE name IN (…)` the claimer's\n * registry, so a replica never takes a job it has no class for until that job\n * has been claimable for the grace window. Every replica claims from this one\n * table, and during a blue/green ramp half of them are running the other\n * release.\n *\n * ### Time\n *\n * Every time in the table is epoch milliseconds, read from the database's\n * clock rather than this process's, so replicas with skewed clocks still agree\n * on when a lease ran out. Integers rather than timestamp columns so the same\n * arithmetic works in all three dialects.\n *\n * ### Transactions\n *\n * On Postgres and MySQL, a dispatch inside an ORM transaction on this\n * driver's own connection is written on that transaction: the row commits\n * with the data it describes or not at all, and no claimer — each reads\n * through a pooled connection of its own — sees it before the commit. See\n * `transaction` below for why SQLite is left out, and why a driver built from\n * a bare client never joins.\n *\n * ### What it does not do\n *\n * It has no `subscribe`: another process's dispatch cannot wake this one, so\n * the queue polls every `pollInterval` (and wakes itself for its own\n * dispatches).\n */\nexport class DatabaseQueueDriver implements QueueDriver {\n readonly table: string;\n private readonly sql: SQL;\n private readonly dialect: Dialect;\n /** The connection's name, when it came with one; see `transaction`. */\n private readonly connection: string | undefined;\n private readonly busyTimeout: number;\n private configured: Promise<void> | undefined;\n\n constructor(\n connection: Pick<DatabaseConnection, \"sql\" | \"dialect\"> &\n Partial<Pick<DatabaseConnection, \"name\">>,\n options: DatabaseQueueDriverOptions = {},\n ) {\n const table = options.table ?? \"gemi_jobs\";\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {\n throw new Error(\n `The queue table name \"${table}\" is not a plain identifier. Use ` +\n `letters, digits and underscores.`,\n );\n }\n const busyTimeout = options.busyTimeout ?? 1000;\n if (!Number.isInteger(busyTimeout) || busyTimeout < 0) {\n throw new Error(\n `The queue's busyTimeout must be a whole number of milliseconds, 0 or more; got ${busyTimeout}.`,\n );\n }\n this.table = table;\n this.sql = connection.sql;\n this.dialect = connection.dialect;\n this.connection = connection.name;\n this.busyTimeout = busyTimeout;\n }\n\n /**\n * Gives a SQLite connection a busy timeout, once, before the driver's first\n * statement on it.\n *\n * Bun opens SQLite with `busy_timeout` 0, so a statement that finds another\n * process holding the file's write lock fails at once with `SQLITE_BUSY`.\n * Two processes on one file — `gemi dev` and a script that dispatches, or\n * the dev server and a seed — then lose writes at random: a dispatch whose\n * INSERT is refused is a job that never runs, and a `complete` that is\n * refused leaves the row claimed until its lease lapses and runs it again.\n * With a timeout SQLite retries the lock for that long instead.\n *\n * The wait is not free. Bun runs SQLite on the JavaScript thread, and the\n * busy handler sleeps there: while one statement waits out another\n * process's transaction, this process serves nothing else — measured, a\n * timer due every 10ms did not fire once across a 400ms wait. Hence a\n * second rather than the several a dedicated worker would choose; a lock\n * held longer than that still fails as it did before.\n *\n * On the connection, not on the driver's statements, because SQLite has no\n * other place for it — so it is the application's default connection's\n * setting too whenever the queue shares that connection, as `\"database\"`\n * does. That is why a connection already set to anything but 0 is left as\n * it is. Lazily rather than from the constructor, because a driver built\n * over a connection that is closed unused would otherwise leave a pragma\n * rejecting against it with nobody holding the promise. Retried after a\n * failure rather than remembered, so one lost statement does not fail every\n * later one.\n */\n private configure(): Promise<void> {\n if (this.dialect !== \"sqlite\" || this.busyTimeout === 0) return Promise.resolve();\n this.configured ??= (async () => {\n const [current] = (await this.sql.unsafe(\"PRAGMA busy_timeout\")) as Array<{\n timeout: number | string;\n }>;\n if (Number(current?.timeout ?? 0) !== 0) return;\n await this.sql.unsafe(`PRAGMA busy_timeout = ${this.busyTimeout}`);\n })().catch((error) => {\n this.configured = undefined;\n throw error;\n });\n return this.configured;\n }\n\n /**\n * Creates the table and its indexes if they do not exist — the same DDL\n * Prisma generates for the model in the docs, so a table made either way is\n * the table the other expects. For tests and for an app that does not\n * manage its schema with Prisma; one that does should add the model\n * instead, or `prisma migrate` will see a table it does not know and drop\n * it.\n */\n async createTable(): Promise<void> {\n await this.configure();\n for (const statement of createTableStatements(this.dialect, this.table)) {\n await this.sql.unsafe(statement);\n }\n }\n\n enqueue(job: EnqueueJob): Promise<string> {\n // Read here, at the call, and never kept: Bun's handle stays callable\n // after its transaction ends and then runs on the pool, so a stored one\n // would write outside any transaction and succeed.\n const tx = this.transaction();\n const written = this.insert(tx ?? this.sql, job);\n // A row written on the transaction is part of it, so the commit waits for\n // it and does not happen if it failed. Otherwise a dispatch nobody awaits\n // — every queued listener's — that fails on Postgres aborts the\n // transaction, and the `COMMIT` that follows is quietly a rollback that\n // `begin` reports as success: the caller's rows are gone with no error.\n // Registered synchronously, while the caller's scope is current.\n if (tx) commitDependsOn(written);\n return written;\n }\n\n private async insert(\n q: SQL,\n { name, args, delayMs = 0, id = Bun.randomUUIDv7() }: EnqueueJob,\n ): Promise<string> {\n await this.configure();\n const now = this.now(q);\n await q`\n INSERT INTO ${this.name(q)}\n (id, name, payload, status, attempts, available_at, created_at, updated_at)\n VALUES\n (${id}, ${name}, ${args}, 'pending', 0, ${now} + ${this.ms(q, delayMs)}, ${now}, ${now})\n `;\n return id;\n }\n\n /**\n * Locks in the `gemi_locks` table of the same connection, so unique jobs,\n * `withoutOverlapping` and `onOneServer` hold across every process sharing\n * this database. The table is only touched when one of those is used.\n */\n lockStore(): DatabaseLockStore {\n return new DatabaseLockStore(\n { sql: this.sql, dialect: this.dialect, name: this.connection },\n { busyTimeout: this.busyTimeout },\n );\n }\n\n joinsTransaction(): boolean {\n return this.transaction() !== undefined;\n }\n\n /**\n * The open ORM transaction, when an `enqueue` should be written on it.\n *\n * Only one on this driver's own connection, matched by name as the ORM and\n * the `DB` facade match it: the transaction's handle does not say which\n * pool it came from. A driver built from a bare `{ sql, dialect }` has no\n * name to match, so it never joins, and the manager holds its dispatches\n * until the commit instead. Guessing \"default\" there would write a job\n * into whichever database the default connection is, which need not be the\n * one this driver's table is in.\n *\n * Never on SQLite. Bun gives a SQLite client one connection, and a\n * statement on the pool while a transaction is open runs *inside* that\n * transaction — measured on Bun 1.3.14: it sees the transaction's\n * uncommitted rows. The queue claims through the pool, so a job row\n * written on the transaction would be visible to this process's own claim\n * before the commit, and run before the data it describes exists — or\n * whether or not it ever does. Held until the commit, it cannot.\n */\n private transaction(): TransactionSQL | undefined {\n if (this.dialect === \"sqlite\" || this.connection === undefined) return undefined;\n const tx = currentTransaction();\n if (tx === undefined || currentConnectionName() !== this.connection) return undefined;\n return tx;\n }\n\n async claim(limit: number, options: ClaimOptions): Promise<ClaimedJob[]> {\n const count = Math.max(0, Math.floor(limit));\n if (count === 0) return [];\n await this.configure();\n\n const rows =\n this.dialect === \"mysql\" || this.dialect === \"mariadb\"\n ? await this.claimLocking(count, options)\n : await this.claimUpdating(count, options);\n\n // `RETURNING` promises no order, and the manager expects oldest first.\n return rows\n .map((row) => ({\n order: Number(row.available_at),\n job: {\n id: String(row.id),\n name: String(row.name),\n args: String(row.payload),\n attempt: Number(row.attempts),\n createdAt: Number(row.created_at),\n },\n }))\n .sort((a, b) => a.order - b.order || (a.job.id < b.job.id ? -1 : 1))\n .map(({ job }) => job);\n }\n\n async complete(job: ClaimedJob): Promise<void> {\n await this.configure();\n const q = this.sql;\n await q`\n DELETE FROM ${this.name(q)}\n WHERE id = ${job.id} AND status = 'claimed' AND attempts = ${job.attempt}\n `;\n }\n\n async fail(job: ClaimedJob, failure: JobFailure): Promise<void> {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n if (failure.retryInMs === null) {\n await q`\n UPDATE ${this.name(q)}\n SET status = 'dead', lease_expires_at = NULL, last_error = ${failure.error},\n updated_at = ${now}\n WHERE id = ${job.id} AND status = 'claimed' AND attempts = ${job.attempt}\n `;\n return;\n }\n await q`\n UPDATE ${this.name(q)}\n SET status = 'pending', available_at = ${now} + ${this.ms(q, failure.retryInMs)},\n lease_expires_at = NULL, last_error = ${failure.error}, updated_at = ${now}\n WHERE id = ${job.id} AND status = 'claimed' AND attempts = ${job.attempt}\n `;\n }\n\n /**\n * `attempts - 1`, so the attempt this claim added is taken back. Guarded by\n * the claim's attempt like every other report, so it can only undo the\n * increment it is reporting on.\n */\n async release(job: ClaimedJob, release: JobRelease): Promise<void> {\n const q = this.sql;\n const now = this.now(q);\n await q`\n UPDATE ${this.name(q)}\n SET status = 'pending', attempts = attempts - 1,\n available_at = ${now} + ${this.ms(q, release.retryInMs)},\n lease_expires_at = NULL, updated_at = ${now}\n WHERE id = ${job.id} AND status = 'claimed' AND attempts = ${job.attempt}\n `;\n }\n\n /**\n * Extends a lease that has lapsed but not been re-issued, too, as the memory\n * driver does: the job is still running here and nobody else has it.\n */\n async heartbeat(jobs: ClaimedJob[], options: ClaimOptions): Promise<void> {\n await this.configure();\n const q = this.sql;\n await Promise.all(\n jobs.map((job) => {\n const now = this.now(q);\n return q`\n UPDATE ${this.name(q)}\n SET lease_expires_at = ${now} + ${this.ms(q, options.visibilityTimeoutMs)},\n updated_at = ${now}\n WHERE id = ${job.id} AND status = 'claimed' AND attempts = ${job.attempt}\n `;\n }),\n );\n }\n\n /**\n * Puts a dead row back to waiting with its attempts reset, so the job gets\n * its whole `maxAttempts` again. `last_error` is kept until the next failure\n * replaces it, so the reason it died is still there to read while it runs.\n */\n async retryDead(id: string): Promise<boolean> {\n await this.configure();\n const q = this.sql;\n const now = this.now(q);\n const result = await q`\n UPDATE ${this.name(q)}\n SET status = 'pending', attempts = 0, available_at = ${now}, lease_expires_at = NULL,\n updated_at = ${now}\n WHERE id = ${id} AND status = 'dead'\n `;\n return affected(result) > 0;\n }\n\n /**\n * Deletes dead-lettered jobs last touched more than `olderThanMs` ago, and\n * resolves to how many. Dead rows are kept so a failure can be read and the\n * job brought back with `retryDead`; nothing removes them unless this is called — from\n * a scheduled job, say.\n */\n async prune(olderThanMs: number): Promise<number> {\n await this.configure();\n const q = this.sql;\n const result = await q`\n DELETE FROM ${this.name(q)}\n WHERE status = 'dead' AND updated_at <= ${this.now(q)} - ${this.ms(q, olderThanMs)}\n `;\n return affected(result);\n }\n\n /**\n * Postgres and SQLite: one statement. Postgres locks the rows it picks with\n * `SKIP LOCKED` in a CTE, which it evaluates once, so a concurrent claimer\n * skips them rather than waiting and then taking them too. SQLite serialises\n * writes, so its statement needs no lock clause to be atomic.\n */\n private async claimUpdating(limit: number, options: ClaimOptions): Promise<Row[]> {\n const q = this.sql;\n const now = this.now(q);\n const lease = this.ms(q, options.visibilityTimeoutMs);\n const table = this.name(q);\n\n if (this.dialect === \"postgres\") {\n return await q`\n WITH next AS (\n SELECT id FROM ${table}\n WHERE ${this.claimable(q, options)}\n ORDER BY available_at, id\n LIMIT ${limit}\n FOR UPDATE SKIP LOCKED\n )\n UPDATE ${table} AS job\n SET status = 'claimed', attempts = job.attempts + 1, claimed_at = ${now},\n lease_expires_at = ${now} + ${lease}, updated_at = ${now}\n FROM next\n WHERE job.id = next.id\n RETURNING job.id, job.name, job.payload, job.attempts, job.available_at, job.created_at\n `;\n }\n\n return await q`\n UPDATE ${table}\n SET status = 'claimed', attempts = attempts + 1, claimed_at = ${now},\n lease_expires_at = ${now} + ${lease}, updated_at = ${now}\n WHERE id IN (\n SELECT id FROM ${table}\n WHERE ${this.claimable(q, options)}\n ORDER BY available_at, id\n LIMIT ${limit}\n )\n RETURNING id, name, payload, attempts, available_at, created_at\n `;\n }\n\n /**\n * MySQL and MariaDB: lock, then update. The rows stay locked until the\n * transaction commits, so the attempt counted here is the one written.\n *\n * Two selects rather than one, because the `OR` in `claimable` leaves MySQL\n * no index to walk: it scans the whole table and sorts it (`type: ALL`,\n * `Using filesort`), and `LIMIT` is applied only after the sort, so InnoDB\n * locks every candidate row rather than the handful being claimed. A second\n * claimer's `SKIP LOCKED` then skips the entire table and claims nothing —\n * the opposite of what `SKIP LOCKED` is here for, and enough to serialise\n * every replica onto one claimer. Fixing `status` to a single value in each\n * pass lets its index supply both the filter and the order, so only the rows\n * actually handed out are locked. The two passes cannot overlap, because a\n * row's `status` is in exactly one of them.\n */\n private async claimLocking(limit: number, options: ClaimOptions): Promise<Row[]> {\n // A connection of our own, so the isolation level below is this claim's\n // and not the pool's. MySQL will not change it inside an open\n // transaction, and `begin` opens one at once, so it is set just before.\n const connection = await this.sql.reserve();\n const [isolation] = (await connection`\n SELECT @@transaction_isolation AS level\n `) as Array<{ level: string }>;\n // REPEATABLE READ — MySQL's default — locks the gaps a range scan passes\n // over, not just the rows it returns. The two passes below read different\n // indexes, so two claimers take those gap locks in opposite orders and\n // deadlock, even when the second pass matches no row at all. READ\n // COMMITTED takes no gap locks, which is what `SKIP LOCKED` wants: each\n // claimer locks the rows it is taking and nothing else.\n await connection`SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED`;\n try {\n return await this.claimLockingOn(connection, limit, options);\n } finally {\n // The connection goes back to the pool the application shares, so it\n // goes back as it came. `@@transaction_isolation` reads back with a\n // hyphen and `SET` wants a space.\n const previous = isolation?.level?.replaceAll(\"-\", \" \");\n if (previous && /^[A-Z ]+$/.test(previous)) {\n await connection.unsafe(`SET SESSION TRANSACTION ISOLATION LEVEL ${previous}`);\n }\n connection.release();\n }\n }\n\n private async claimLockingOn(\n connection: SQL,\n limit: number,\n options: ClaimOptions,\n ): Promise<Row[]> {\n return await connection.begin(async (tx) => {\n const table = this.name(tx);\n const columns = tx.unsafe(\"id, name, payload, attempts, available_at, created_at\");\n\n // Waiting and due, oldest first, along the (status, available_at) index.\n const rows: Row[] = await tx`\n SELECT ${columns} FROM ${table}\n WHERE status = 'pending' AND available_at <= ${this.now(tx)}\n AND ${this.runnable(tx, options, \"available_at\")}\n ORDER BY available_at, id\n LIMIT ${limit}\n FOR UPDATE SKIP LOCKED\n `;\n\n // Only then the leases that ran out, longest-expired first, along the\n // (status, lease_expires_at) index. `claim` sorts what both passes\n // return by `available_at` before handing it to the manager.\n if (rows.length < limit) {\n const expired: Row[] = await tx`\n SELECT ${columns} FROM ${table}\n WHERE status = 'claimed' AND lease_expires_at <= ${this.now(tx)}\n AND ${this.runnable(tx, options, \"lease_expires_at\")}\n ORDER BY lease_expires_at, id\n LIMIT ${limit - rows.length}\n FOR UPDATE SKIP LOCKED\n `;\n rows.push(...expired);\n }\n\n for (const row of rows) {\n const now = this.now(tx);\n await tx`\n UPDATE ${table}\n SET status = 'claimed', attempts = attempts + 1, claimed_at = ${now},\n lease_expires_at = ${now} + ${this.ms(tx, options.visibilityTimeoutMs)},\n updated_at = ${now}\n WHERE id = ${row.id}\n `;\n }\n return rows.map((row) => ({ ...row, attempts: Number(row.attempts) + 1 }));\n });\n }\n\n /**\n * Waiting and due, or leased and the lease has run out — and, when the\n * claimer said which names it can run, under one of them or claimable for\n * longer than the grace window. See `ClaimOptions.registered`.\n */\n private claimable(q: SQL, options: ClaimOptions) {\n const now = this.now(q);\n return q`(\n (status = 'pending' AND available_at <= ${now}\n AND ${this.runnable(q, options, \"available_at\")})\n OR (status = 'claimed' AND lease_expires_at <= ${now}\n AND ${this.runnable(q, options, \"lease_expires_at\")})\n )`;\n }\n\n /**\n * Whether a row is one the claimer may take by its name. `since` is the\n * column holding when the row became claimable, which is what the grace\n * window is measured from: a job delayed for a day and due a second ago has\n * been waiting for a second, not a day, and a replica that knows its name\n * may be about to take it.\n *\n * The list is spliced in as one parameter per name, because `name IN ()` is\n * a syntax error in all three dialects; an empty registry matches no name\n * and leaves only the grace window.\n *\n * No index covers `name`, on purpose. On MySQL 8.4 the claim keeps walking\n * `(status, available_at)` and filters names as it goes, and a\n * `(status, name, available_at)` index is not chosen even when it exists:\n * it cannot hand back rows in `available_at` order across several names. A\n * head of rows with names this replica does not know costs only the scan,\n * not locks, because `claimLocking` claims at READ COMMITTED. Measured with\n * 2,000 unknown rows ahead of the rest: 10 record locks held for a claim of\n * 5, and a second replica that knows those names took them meanwhile. At\n * REPEATABLE READ the same claim held 4,014, and the second replica skipped\n * every one of them.\n */\n private runnable(q: SQL, options: ClaimOptions, since: \"available_at\" | \"lease_expires_at\") {\n const registered = options.registered;\n if (!registered) return q`1 = 1`;\n const names = registered.names.length\n ? q`name IN (${registered.names\n .map((name) => q`${name}`)\n .reduce((list, name) => q`${list}, ${name}`)})`\n : q`1 = 0`;\n if (!Number.isFinite(registered.graceMs)) return names;\n const column = q.unsafe(since);\n return q`(${names} OR ${column} <= ${this.now(q)} - ${this.ms(q, registered.graceMs)})`;\n }\n\n /**\n * The table name, already checked to be a plain identifier, and quoted the\n * way `createTable` quotes it. Unquoted, Postgres folds `GemiJobs` to\n * `gemijobs` and misses the table `createTable` (or a Prisma model without\n * `@@map`) made, and a reserved word such as `order` is a syntax error.\n */\n private name(q: SQL) {\n const mysql = this.dialect === \"mysql\" || this.dialect === \"mariadb\";\n return q.unsafe(mysql ? `\\`${this.table}\\`` : `\"${this.table}\"`);\n }\n\n /** The database's clock, in epoch milliseconds. */\n private now(q: SQL) {\n switch (this.dialect) {\n case \"postgres\":\n return q`floor(extract(epoch from statement_timestamp()) * 1000)::bigint`;\n case \"mysql\":\n case \"mariadb\":\n // UTC against a naive epoch literal, so the session's time zone — and a\n // daylight-saving hour that occurs twice in it — never enters in.\n return q`(TIMESTAMPDIFF(MICROSECOND, '1970-01-01 00:00:00', UTC_TIMESTAMP(6)) DIV 1000)`;\n case \"sqlite\":\n return q`CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)`;\n }\n }\n\n /**\n * A millisecond count as an integer parameter. Cast, so Postgres does not\n * have to infer the type of a bare parameter added to a `bigint`.\n */\n private ms(q: SQL, value: number) {\n const ms = Math.max(0, Math.round(value));\n return this.dialect === \"mysql\" || this.dialect === \"mariadb\"\n ? q`CAST(${ms} AS SIGNED)`\n : q`CAST(${ms} AS BIGINT)`;\n }\n}\n\n/**\n * How many rows a `DELETE` or `UPDATE` touched. SQLite and Postgres report\n * them in `count`; MySQL may report them in `affectedRows` and zero rows\n * returned in `count`.\n */\nfunction affected(result: unknown): number {\n const counts = result as { count?: number | null; affectedRows?: number | null };\n return Math.max(Number(counts.count ?? 0), Number(counts.affectedRows ?? 0));\n}\n\n/**\n * The jobs table, as Prisma generates it for the model in the docs — checked\n * against `prisma migrate diff` for all three providers — with `IF NOT EXISTS`\n * added. MySQL cannot say that of an index, so its indexes are declared inside\n * the table, which is also where Prisma puts them.\n */\nexport function createTableStatements(dialect: Dialect, table: string): string[] {\n if (dialect === \"mysql\" || dialect === \"mariadb\") {\n return [\n `CREATE TABLE IF NOT EXISTS \\`${table}\\` (\n \\`id\\` VARCHAR(191) NOT NULL,\n \\`name\\` VARCHAR(191) NOT NULL,\n \\`payload\\` LONGTEXT NOT NULL,\n \\`status\\` VARCHAR(191) NOT NULL,\n \\`attempts\\` INTEGER NOT NULL DEFAULT 0,\n \\`available_at\\` BIGINT NOT NULL,\n \\`claimed_at\\` BIGINT NULL,\n \\`lease_expires_at\\` BIGINT NULL,\n \\`last_error\\` LONGTEXT NULL,\n \\`created_at\\` BIGINT NOT NULL,\n \\`updated_at\\` BIGINT NOT NULL,\n\n INDEX \\`${table}_status_available_at_idx\\`(\\`status\\`, \\`available_at\\`),\n INDEX \\`${table}_status_lease_expires_at_idx\\`(\\`status\\`, \\`lease_expires_at\\`),\n PRIMARY KEY (\\`id\\`)\n) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`,\n ];\n }\n\n const key = dialect === \"sqlite\" ? `\"id\" TEXT NOT NULL PRIMARY KEY,` : `\"id\" TEXT NOT NULL,`;\n const constraint =\n dialect === \"sqlite\" ? \"\" : `,\\n\\n CONSTRAINT \"${table}_pkey\" PRIMARY KEY (\"id\")`;\n return [\n `CREATE TABLE IF NOT EXISTS \"${table}\" (\n ${key}\n \"name\" TEXT NOT NULL,\n \"payload\" TEXT NOT NULL,\n \"status\" TEXT NOT NULL,\n \"attempts\" INTEGER NOT NULL DEFAULT 0,\n \"available_at\" BIGINT NOT NULL,\n \"claimed_at\" BIGINT,\n \"lease_expires_at\" BIGINT,\n \"last_error\" TEXT,\n \"created_at\" BIGINT NOT NULL,\n \"updated_at\" BIGINT NOT NULL${constraint}\n)`,\n `CREATE INDEX IF NOT EXISTS \"${table}_status_available_at_idx\" ON \"${table}\"(\"status\", \"available_at\")`,\n `CREATE INDEX IF NOT EXISTS \"${table}_status_lease_expires_at_idx\" ON \"${table}\"(\"status\", \"lease_expires_at\")`,\n ];\n}\n",
8
- "import { AsyncResource } from \"node:async_hooks\";\nimport { isMainThread } from \"node:worker_threads\";\n\nimport type { Application } from \"../../foundation/Application\";\nimport { DatabaseManager } from \"../../database/DatabaseManager\";\nimport { kernelContext } from \"../../kernel/context\";\nimport { deferUntilCommit } from \"../../orm/context\";\nimport { isShuttingDown } from \"../../server/shutdown\";\nimport { DatabaseLockStore } from \"../lock/DatabaseLockStore\";\nimport { type HeldLock, LockManager } from \"../lock/LockManager\";\nimport type { LockStore } from \"../lock/LockStore\";\nimport { MemoryLockStore } from \"../lock/MemoryLockStore\";\nimport { DatabaseQueueDriver } from \"./DatabaseQueueDriver\";\nimport { Job } from \"./Job\";\nimport { queueConfigDefaults, type QueueConfig } from \"./config\";\nimport { MemoryQueueDriver } from \"./MemoryQueueDriver\";\nimport type { ClaimOptions, ClaimedJob, QueueDriver } from \"./QueueDriver\";\nimport { withDefaults } from \"../../support/withDefaults\";\n\n/**\n * The thread a `worker = true` job runs in.\n *\n * The `await` on `dispatchJob` is load-bearing and its absence was a hang.\n * `postMessage` structured-clones its argument and a Promise is not cloneable,\n * so an un-awaited `dispatchJob` posted the pending promise, threw\n * `DataCloneError` inside this handler, and posted nothing at all. `runInWorker`\n * then never settled: `run` below never returned, its concurrency slot was\n * never decremented, and neither the retry path nor `onDeadletter` ever fired —\n * after `concurrency` such jobs the queue sits at its limit forever, with no\n * error anywhere. Any job whose `run` is `async` hit it, which is every job\n * that touches IO; a queued listener hits it unconditionally, because the\n * synthetic job's `run` awaits `handle`.\n *\n * The await earns two more things. A rejecting job now reaches the `catch` and\n * comes back as `{error}`, where the queue's ordinary retry-then-dead-letter\n * path can have it, instead of being lost with the promise. And `destroy()` now\n * runs after the job rather than under it, so the cloned kernel — its database\n * connections included — outlives the work it was cloned for.\n */\nfunction createWorker() {\n const APP_DIR = process.env.APP_DIR;\n const ROOT_DIR = process.env.ROOT_DIR;\n\n const appPath =\n process.env.NODE_ENV === \"production\"\n ? `${ROOT_DIR}/dist/server/bootstrap.mjs`\n : `${APP_DIR}/bootstrap.ts`;\n\n const file = new File(\n [\n `\n import { app } from \"${appPath}\"\n self.onmessage = async (event) => {\n const clone = app.clone()\n let result = null;\n let error = null;\n try {\n result = await clone.dispatchJob(event.data.jobName, event.data.args)\n } catch (err) {\n error = err\n }\n clone.destroy()\n if(error) {\n self.postMessage({error});\n } else {\n self.postMessage({result});\n }\n };\n `,\n ],\n \"worker.ts\",\n );\n const url = URL.createObjectURL(file);\n return new Worker(url);\n}\n\n// TODO: terminate worker after the job is done\nasync function runInWorker(jobName: string, args: string) {\n const worker = createWorker();\n worker.postMessage({ jobName, args });\n return await new Promise((resolve, reject) => {\n worker.onmessage = (e) => {\n const data = e.data;\n if (\"error\" in data) {\n reject(data.error);\n } else {\n resolve(data.result);\n }\n };\n });\n}\n\n/**\n * The async context every job runs in: the one this module was evaluated in,\n * which is boot, outside any request.\n *\n * The worker loop is long-lived and started lazily, by the first dispatch, and\n * an async function keeps the context it was started in across every `await`.\n * Started from a request, it would run every later job — dispatched by any\n * request at all — inside the first one's `RequestContext`, and with its ORM\n * transaction handle if it had one open: one user's identity leaking into\n * another's work. Captured here, where no request exists, and entered by `start()`, a job\n * sees no request at all; the Application is entered separately, per job.\n */\nconst bootScope = new AsyncResource(\"gemi.queue\");\n\n/**\n * The longest the loop waits between retries while `claim` keeps rejecting.\n *\n * Long enough that a database which is properly down is not asked once a\n * second by every worker process for as long as it stays down, short enough\n * that the queue is claiming again within a minute of it coming back.\n */\nconst maxClaimBackoff = 60_000;\n\ndeclare global {\n /**\n * The queue loop a `gemi dev` server is running over a driver that outlives\n * the process, kept where a `bun --hot` reload cannot lose it. A reload\n * re-evaluates every module and boots a new application, but the previous\n * application's loop is a live closure and keeps claiming — from the same\n * table, with the code it was loaded with. This is how the next\n * application finds it to stop it; see `startClaimingIfServing`.\n *\n * Typed by what is called on it, not as `QueueManager`: after a reload the\n * one held here is an instance of the previous module graph's class.\n */\n var __gemiDevQueue: { stop(): Promise<unknown> } | undefined;\n}\n\n/** What `drain` could not wait out. */\nexport type DrainResult = {\n /**\n * Jobs still running when the timeout elapsed. They keep running, and keep\n * their leases, until they settle or the process exits; with a driver that\n * outlives the process, an exit leaves them to be reclaimed once their lease\n * runs out. Waiting jobs are not listed — they were never taken, and are\n * wherever the driver keeps them.\n */\n unfinished: ClaimedJob[];\n};\n\nexport class QueueManager {\n static token = \"queue\";\n\n jobs: Record<string, new () => Job> = {};\n\n readonly config: Required<QueueConfig>;\n readonly driver: QueueDriver;\n\n private readonly application: Application | undefined;\n /**\n * Keyed by claim, `id:attempt`, not by job id. A lease that lapses while\n * its job is still running here — heartbeats failing through a database\n * blip, or an event loop blocked past the visibility timeout — lets this\n * same process claim the job again. Both runs are then really running, so\n * both take a slot. Keyed by id alone, the second claim overwrote the first\n * and the first one's `finally` then deleted it: the live run stopped being\n * counted, drained or heartbeated, so its lease lapsed in turn and the job\n * was claimed again into a slot that was never free.\n */\n private readonly inFlight = new Map<\n string,\n { job: ClaimedJob; done: Promise<void> }\n >();\n private state: \"idle\" | \"running\" | \"stopped\" = \"idle\";\n private looping = false;\n private claiming: Promise<unknown> | undefined;\n private woken = false;\n /** Consecutive `claim` rejections; see `sleep`. Cleared by a claim that returns. */\n private claimFailures = 0;\n private resume: (() => void) | undefined;\n private unsubscribe: (() => void) | undefined;\n private heartbeatTimer: ReturnType<typeof setInterval> | undefined;\n /** Unknown names already reported as left for another replica; see `run`. */\n private readonly releasedNames = new Set<string>();\n private lockManager: LockManager | undefined;\n\n /**\n * `application` is entered around every job, so `app()` inside one resolves\n * to it rather than to whichever application happens to be the static\n * fallback. The provider always passes it; a manager built bare, as the unit\n * tests do, runs jobs with no application entered.\n */\n constructor(\n config: QueueConfig = {},\n options: { application?: Application } = {},\n ) {\n this.config = withDefaults(queueConfigDefaults(), config);\n this.application = options.application;\n this.driver = resolveDriver(this.config.driver, this.application);\n this.useJobs(this.config.jobs);\n }\n\n /**\n * Replaces the registered set, for the provider to hand over what it found\n * under `app/jobs`.\n *\n * This exists because the two phases disagree about when the answer is\n * knowable. The manager is constructed in `register()`, which is synchronous\n * and must resolve nothing; reading a directory and importing what is in it is\n * neither. So the manager is built from whatever the config slice declared —\n * nothing, when the app left `jobs` out — and the discovered set arrives in\n * `boot()`.\n *\n * The consequence worth knowing: anything that constructs an application and\n * skips phase two sees an empty registry, and every dispatch against an empty\n * registry is dropped — loudly on stderr, but after `Job.dispatch` has already\n * returned, so no caller finds out. On a durable driver not even that is\n * immediate: the job is left for another replica, and the stderr line comes\n * only once `unknownJobGrace` has passed. An app that lists its jobs explicitly is\n * unaffected, because that list is already in place by the end of `register()`.\n */\n useJobs(jobs: Array<new () => Job>) {\n this.config.jobs = jobs;\n\n // Built one at a time rather than by `Object.fromEntries`, which resolves a\n // repeated key by keeping the last and saying nothing.\n //\n // Two classes with one name is the worst failure in this subsystem, and it\n // is worse than the dropped dispatch the rest of this file is about: the\n // registry is keyed by class name, a dispatch carries a class name, so\n // `SendEmail.dispatch(...)` on the one under `app/jobs/auth` would run the\n // body of the one under `app/jobs/billing`. Nothing is dropped and nothing\n // errors — the wrong work happens and reports success.\n //\n // Discovery is what makes it ordinary. A hand-written list forces an import\n // alias in one visible file the moment two names clash; a directory walk\n // does not, and `auth/SendEmail.ts` beside `billing/SendEmail.ts` is a\n // perfectly natural thing to write. So the first claim wins and the second\n // is refused out loud — the same rule `Scheduler.start` applies to a cron\n // name, and the reverse of the silent last-wins this replaces.\n this.jobs = {};\n for (const job of jobs) {\n this.claim(job);\n }\n }\n\n /**\n * Registers one more job beside whatever `useJobs` already took.\n *\n * This exists for the queued-listener adapter, which registers a synthetic\n * job per queued listener from `EventServiceProvider.boot()` — after the\n * queue's own `boot()` has filled the registry. `useJobs` cannot serve that\n * caller: it *replaces* the registry, so a second call would discard every\n * job the app wrote and leave the queue holding listeners alone. Reading\n * `registeredJobs` back, concatenating and calling `useJobs` again avoids\n * that and is still the wrong shape — it re-runs the collision check over\n * jobs that already passed it, so a duplicate the author has already been\n * told about is reported a second time on every boot.\n *\n * Same rule as `useJobs`, deliberately: the first claim on a name wins and\n * the second is refused out loud. And recorded in `config.jobs` either way,\n * so `registeredJobs` keeps reporting everything the manager was handed\n * rather than what it accepted — a refused job is visible to a test there.\n *\n * A new array rather than a `push`, because `useJobs` keeps the caller's\n * array by reference and appending to it would edit the config slice or the\n * discovered list under whoever else is holding it.\n */\n registerJob(job: new () => Job) {\n this.config.jobs = [...this.config.jobs, job];\n this.claim(job);\n }\n\n /**\n * Puts one job in the registry, or refuses it because the name is taken.\n *\n * The rule and the reason are in `useJobs` above; this is only the shape both\n * entry points share, so that a job arriving one at a time cannot be admitted\n * on terms the batch would have refused.\n */\n private claim(job: new () => Job) {\n if (this.jobs[job.name]) {\n console.error(\n `Two queued jobs are named \"${job.name}\" — the first is registered ` +\n `and this one is not, so dispatching either would have run one of ` +\n `them. A class name is the queue's key, so only one job can hold ` +\n `it. Rename one.`,\n );\n return;\n }\n this.jobs[job.name] = job;\n }\n\n /**\n * What the manager was handed, discovered or declared.\n *\n * The registry is keyed by name, and a name is exactly what a dispatch\n * carries, so \"is this job registered?\" is a question with a silent wrong\n * answer — `run()` drops an unknown name long after the caller moved on.\n * This is where a test asks it out loud.\n *\n * It reports what came in, not what the registry accepted, so a name claimed\n * twice appears twice here — deliberately, the same way `Scheduler.jobs`\n * does. A test walking this should see the collision rather than have it\n * tidied away. A copy, so that walk cannot edit the registry underneath it.\n */\n get registeredJobs(): ReadonlyArray<new () => Job> {\n return [...this.config.jobs];\n }\n\n dispatchJob(jobName: string, args: string) {\n if (this.jobs[jobName]) {\n const job = new this.jobs[jobName]();\n return job.run(JSON.parse(args));\n }\n }\n\n /**\n * Queues a job and resolves to its id once the driver has recorded it, then\n * makes sure the worker loop is running — unless `drain` stopped it, in which\n * case the job waits in the driver for whichever process claims next. With\n * the memory driver that is nobody; see `drain`. A durable driver's job also\n * waits there when this process does not claim: it is not a server, or it is\n * one started with `GEMI_QUEUE_CLAIM=off`; see `claimsInThisProcess`.\n *\n * Inside an open ORM transaction the job belongs to the transaction. A\n * driver that can write it on the transaction does (`joinsTransaction`);\n * for any other, it is held and recorded once the transaction commits, and\n * never if it rolls back. Either way no claimer can see the job before the\n * data it was dispatched about exists. A held dispatch resolves at once, to\n * the id the job will be recorded under: the transaction does not commit\n * until its callback returns, so a caller awaiting a promise that waited for\n * the commit would never return.\n */\n push(\n job: new () => Job,\n args: string,\n options: {\n /**\n * Whether a driver that cannot record the job is reported from here.\n * On by default, because the usual caller is a fire-and-forget\n * `Job.dispatch(...)` whose promise nobody reads. A caller with\n * something better to say — `EventManager`, which knows the event and\n * the listener — turns it off and says that instead. A held dispatch is\n * reported here whatever this says, because its promise has already\n * resolved and the caller has nothing left to catch.\n */\n reportFailure?: boolean;\n } = {},\n ): Promise<string> {\n // A unique job takes its key before it is recorded; see `pushUnique`.\n // Computed here so a `uniqueId` that throws does so on the caller's stack.\n const unique = this.uniqueKey(job, args);\n if (unique) return this.pushUnique(job, args, unique, options);\n\n // Asked before anything is written, and in the caller's async context,\n // which is where the transaction is. The held branch is the fallback\n // rather than the rule because it loses the job to a crash between the\n // commit and the enqueue; written on the transaction, the job commits\n // with the rows or not at all.\n const joins = this.driver.joinsTransaction?.() === true;\n if (!joins) {\n // `deferUntilCommit` is the ORM's commit hook, and the same one an\n // `afterCommit` event waits on: a savepoint that rolls back takes its\n // held dispatches with it, and the callback runs outside the\n // transaction, so the enqueue cannot be pulled back into it.\n const id = Bun.randomUUIDv7();\n const held = deferUntilCommit(() =>\n // Awaited, so the transaction's own promise waits for the enqueue and\n // a script that exits after its last transaction does not exit under\n // it. The rejection is swallowed only because `record` has already\n // reported it; rethrown, the drain would report it a second time.\n this.record(job, args, {\n id,\n reportFailure: true,\n committed: true,\n }).then(\n () => {},\n () => {},\n ),\n );\n if (held) return Promise.resolve(id);\n }\n return this.record(job, args, {\n reportFailure: options.reportFailure,\n joins,\n });\n }\n\n /**\n * The locks unique jobs, cron jobs' `withoutOverlapping` and `onOneServer`,\n * and the `Lock` facade use, over the store the `locks` setting names. Built\n * on first use, so an app that uses none of them never touches the store.\n */\n get locks(): LockManager {\n this.lockManager ??= new LockManager(this.resolveLockStore());\n return this.lockManager;\n }\n\n private resolveLockStore(): LockStore {\n const locks = this.config.locks;\n if (locks === \"auto\") {\n const store = this.driver.lockStore?.();\n if (store) return store;\n if (this.durable) {\n console.warn(\n `[gemi] The queue driver keeps no locks, so unique jobs, ` +\n `withoutOverlapping and onOneServer only hold within this process. ` +\n `Set the queue slice's \\`locks\\` to \"database\" or a LockStore.`,\n );\n }\n return new MemoryLockStore();\n }\n if (locks === \"memory\") return new MemoryLockStore();\n if (locks === \"database\" || typeof locks === \"function\") {\n if (!this.application && (locks === \"database\" || locks.length > 0)) {\n throw new Error(\n `The queue's locks need the application they belong to, and this ` +\n `QueueManager was built without one. Pass { application }.`,\n );\n }\n if (locks === \"database\") {\n return new DatabaseLockStore(this.application!.make(DatabaseManager));\n }\n return locks(this.application!);\n }\n if (typeof locks === \"string\") {\n throw new Error(\n `Unknown queue locks \"${locks}\". The queue slice's locks is \"auto\", ` +\n `\"memory\", \"database\", a LockStore, or a function returning one.`,\n );\n }\n return locks;\n }\n\n /**\n * The lock key a dispatch of a unique job takes, or `undefined` for a job\n * that does not declare `uniqueId` or a dispatch it returned nothing for.\n */\n private uniqueKey(job: new () => Job, args: string) {\n if (job.prototype.uniqueId === Job.prototype.uniqueId) return undefined;\n const instance = new job();\n const id = instance.uniqueId(...(JSON.parse(args) as unknown[]));\n if (id === undefined || id === null || id === \"\") return undefined;\n return { key: uniqueLockName(job.name, id), ttl: Math.max(1, instance.uniqueFor) };\n }\n\n /**\n * Records a unique job if its key is free, and otherwise resolves to the id\n * of the job holding it. The key's lock is owned by the job's id, which is\n * how the job frees it when it ends and how a duplicate finds it.\n *\n * Inside a transaction the whole dispatch waits for the commit, even on a\n * driver that could join the transaction. The key is taken outside it, so\n * written on the transaction the job could roll back and leave its key held\n * for `uniqueFor` with nothing queued under it. A held dispatch resolves to\n * the id the job will have, and is dropped at the commit if a job with the\n * same key is queued by then.\n */\n private pushUnique(\n job: new () => Job,\n args: string,\n unique: { key: string; ttl: number },\n options: { reportFailure?: boolean },\n ): Promise<string> {\n const id = Bun.randomUUIDv7();\n const held = deferUntilCommit(() =>\n this.recordUnique(job, args, unique, id, { report: true, committed: true }).then(\n () => {},\n () => {},\n ),\n );\n if (held) return Promise.resolve(id);\n return this.recordUnique(job, args, unique, id, {\n report: options.reportFailure !== false,\n committed: false,\n });\n }\n\n private recordUnique(\n job: new () => Job,\n args: string,\n unique: { key: string; ttl: number },\n id: string,\n options: { report: boolean; committed: boolean },\n ): Promise<string> {\n const store = this.locks.store;\n const recorded = (async () => {\n // A few rounds, for a key freed between a refused `acquire` and the\n // `holder` read that follows it.\n for (let round = 0; round < 5; round++) {\n if ((await store.acquire(unique.key, id, unique.ttl)) !== null) {\n try {\n return await this.record(job, args, { id, reportFailure: false });\n } catch (error) {\n await store.release(unique.key, id).catch(() => {});\n throw error;\n }\n }\n const holder = await store.holder(unique.key);\n if (holder) return holder.owner;\n }\n throw new Error(\n `The unique key \"${unique.key}\" kept changing hands, so ${job.name} ` +\n `was not queued.`,\n );\n })();\n recorded.catch((error: unknown) => {\n if (!options.report) return;\n console.error(\n `[gemi] The queue could not record the unique job ${job.name}` +\n (options.committed\n ? `, which was held until its transaction committed. The ` +\n `transaction stays committed; the job did not run and will ` +\n `not be retried.`\n : `, so it did not run and will not be retried.`),\n error,\n );\n });\n return recorded;\n }\n\n /**\n * Frees a unique job's key once the job has ended for good: completed or\n * dead-lettered. Released by the job's id, so a key a newer dispatch holds\n * after `uniqueFor` ran out is left alone.\n */\n private async releaseUnique(job: Job, args: unknown[], claimed: ClaimedJob) {\n if (job.uniqueId === Job.prototype.uniqueId) return;\n try {\n const id = job.uniqueId(...args);\n if (id === undefined || id === null || id === \"\") return;\n await this.locks.store.release(uniqueLockName(claimed.name, id), claimed.id);\n } catch (error) {\n console.error(\n `[gemi] Could not free the unique key of ${claimed.name} (${claimed.id}); ` +\n `it frees itself after uniqueFor.`,\n error,\n );\n }\n }\n\n /** `push`, once it is known that the job is recorded now. */\n private record(\n job: new () => Job,\n args: string,\n options: {\n id?: string;\n reportFailure?: boolean;\n /** Written on the caller's open transaction; see `push`. */\n joins?: boolean;\n /** Held until a transaction committed; changes only the error line. */\n committed?: boolean;\n },\n ): Promise<string> {\n const id = this.driver.enqueue({\n name: job.name,\n args,\n ...(options.id === undefined ? {} : { id: options.id }),\n });\n // A driver that outlives the process is shared with every other one, so\n // a script or console command that dispatches would otherwise claim up to\n // `concurrency` of the table's jobs — other replicas' included — and exit\n // under them, costing each an attempt and a lease's wait. There the job\n // waits in the driver for a server or a worker. A manager built by hand,\n // with no application, is its caller's to run and keeps starting.\n this.startIfIdle();\n // A driver without `subscribe` is only polled, so without the wake a job\n // dispatched here would wait up to `pollInterval` in a queue with room.\n // A spurious wake just claims nothing.\n //\n // A job written on the caller's transaction is not claimable until that\n // commits, so its wake waits for the commit too — woken now, the loop\n // would claim nothing and then sleep a whole `pollInterval` past it.\n // Registered here, synchronously, because the commit hook is found\n // through the caller's async context, and this is the one place that is\n // certainly still current.\n const wake = () =>\n !this.driver.subscribe && this.state === \"running\" && this.wake();\n const wakeAtCommit = options.joins === true && deferUntilCommit(wake);\n\n // The rejection is reported here unless the caller says it will do it\n // itself, and whatever the driver is. It used to depend on both, and was\n // lost either way: a polled driver got an empty rejection arm, which marks\n // `id` handled — and `id` is what `push` returns — so a fire-and-forget\n // `SendReceiptJob.dispatch(...)` whose INSERT failed (a failover, a pool\n // timeout, a table not migrated yet) produced no unhandled rejection and\n // no line anywhere, and the job simply never ran. A subscribing driver got\n // no arm at all, so the same failure surfaced as a bare unhandled\n // rejection. A caller that awaits still gets the rejection either way and\n // can still decide what to do about it.\n id.then(\n () => wakeAtCommit || wake(),\n (error: unknown) => {\n if (options.reportFailure === false) return;\n console.error(\n `[gemi] The queue driver could not record ${job.name}` +\n (options.committed\n ? `, which was held until its transaction committed. The ` +\n `transaction stays committed; the job did not run and will ` +\n `not be retried.`\n : `, so it did not run and will not be retried.`),\n error,\n );\n },\n );\n return id;\n }\n\n /**\n * Runs a dead-lettered job again, from attempt 1 with its whole\n * `maxAttempts`, and resolves to whether a dead job was found under `id`.\n * `false` for an id that is waiting, running, finished or unknown: only a\n * dead job is brought back, so this cannot restart one that is running\n * somewhere.\n *\n * The job is claimed the way a dispatch's is. On a server it is run here as\n * soon as there is room; from a console command it waits in the driver for\n * a server or a worker, which with a polling driver means up to\n * `pollInterval`.\n *\n * Refused for a driver without `retryDead`. The memory driver keeps nothing\n * of a dead job, so there is no id it could bring back, and resolving\n * `false` would read as \"no such job\" to someone holding an id they just saw\n * in a log.\n */\n async retryDead(id: string): Promise<boolean> {\n if (!this.driver.retryDead) {\n throw new Error(\n `This queue's driver keeps nothing of a dead-lettered job, so there ` +\n `is nothing to retry. The database driver keeps them.`,\n );\n }\n const found = await this.driver.retryDead(id);\n if (found) this.claimSoon();\n return found;\n }\n\n /**\n * Starts the loop unless `drain` stopped it or this process should leave a\n * shared driver's jobs to a server — the rule `push` explains.\n */\n private startIfIdle() {\n if (this.state === \"idle\" && (!this.durable || this.mayClaimHere())) {\n this.start();\n }\n }\n\n /**\n * For a job the driver has just made claimable: start the loop, or wake a\n * running one that only polls, as `push` does for a dispatch.\n */\n private claimSoon() {\n this.startIfIdle();\n if (!this.driver.subscribe && this.state === \"running\") this.wake();\n }\n\n /**\n * Starts claiming. Idempotent, and called by the first `push`, so an app\n * does not need to; it is public for a process that should run jobs it\n * never dispatched — ones a driver kept across a restart — and for resuming\n * after `drain`.\n */\n start() {\n if (this.state === \"running\") return;\n this.state = \"running\";\n // Only a development server's loop over a shared driver: a memory queue\n // is only ever fed by its own application, and a hand-built manager or a\n // test's is its caller's to stop.\n if (this.durable && this.application && this.mayClaimHere() && !isProduction()) {\n globalThis.__gemiDevQueue = this;\n }\n this.unsubscribe = this.driver.subscribe?.(() => this.wake());\n // A loop a `drain` has not yet seen off picks the new state up itself;\n // starting a second would claim twice per wake.\n if (this.looping) return this.wake();\n bootScope.runInAsyncScope(() => void this.loop());\n }\n\n /**\n * Stops claiming, waits up to `timeoutMs` for the jobs already running, and\n * reports the ones that did not finish.\n *\n * Nothing is cancelled — a job has no way to be told — so an unfinished job\n * keeps running after this resolves. What happens to it then is the\n * driver's: the memory driver loses it with the process, along with every\n * job still waiting; one that outlives the process lets another claim it\n * once the lease runs out.\n *\n * A later `push` records its job but does not restart the loop. `start()`\n * does.\n */\n async drain(timeoutMs = Infinity): Promise<DrainResult> {\n this.state = \"stopped\";\n if (globalThis.__gemiDevQueue === this) globalThis.__gemiDevQueue = undefined;\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n this.wake();\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<void>((resolve) => {\n if (timeoutMs !== Infinity) timer = setTimeout(resolve, timeoutMs);\n });\n // The claim in progress first, because what it returns is run: those jobs\n // are leased to this process already, and handing them back would cost\n // each of them an attempt it never made.\n const settled = (async () => {\n await this.claiming?.catch(() => {});\n await Promise.allSettled([...this.inFlight.values()].map((e) => e.done));\n })();\n await Promise.race([settled, deadline]);\n clearTimeout(timer);\n\n return {\n unfinished: [...this.inFlight.values()].map((e) => ({ ...e.job })),\n };\n }\n\n /** `drain(0)`: stop claiming, and report what is running without waiting. */\n stop(): Promise<DrainResult> {\n return this.drain(0);\n }\n\n /** How many jobs this process is running right now. */\n get running(): number {\n return this.inFlight.size;\n }\n\n /**\n * Claims while there is room, runs what it claims, and otherwise sleeps\n * until something changes: a job finishing frees a slot, or the driver's\n * `subscribe` says there is work. A driver without `subscribe` is polled\n * every `pollInterval` instead. This replaces a one-second timer that\n * spun while the queue was full.\n *\n * `woken` records a wake that arrived while a claim was in flight, when\n * there was nothing yet to resume; without it that job would wait for the\n * next unrelated wake.\n */\n private async loop() {\n this.looping = true;\n try {\n await this.claimWhileRunning();\n } finally {\n this.looping = false;\n }\n }\n\n private async claimWhileRunning() {\n while (this.state === \"running\") {\n // Once the server has been told to stop, a driver that outlives the\n // process gets nothing new taken from it: a job claimed now would likely\n // be cut off by the exit, and cost an attempt and a lease's wait, when\n // another replica can run it instead. The memory driver keeps claiming\n // until the provider's `drain()` stops it, because nobody else can run\n // its jobs — one waiting at the signal, or dispatched by a request\n // still draining, is run here or not at all.\n const room = this.durable && isShuttingDown()\n ? 0\n : this.config.concurrency - this.inFlight.size;\n\n if (room > 0) {\n this.woken = false;\n const claim = this.driver.claim(room, {\n ...this.lease(),\n registered: {\n names: Object.keys(this.jobs),\n graceMs: this.config.unknownJobGrace,\n },\n });\n this.claiming = claim;\n let claimed: ClaimedJob[] = [];\n try {\n claimed = await claim;\n this.claimFailures = 0;\n } catch (error) {\n this.claimFailures++;\n console.error(\n `The queue could not claim jobs from its driver; it will try ` +\n `again in ${this.retryDelay()}ms.`,\n error,\n );\n } finally {\n this.claiming = undefined;\n }\n for (const job of claimed) this.execute(job);\n\n // A full batch may have left more behind; the next pass either claims\n // it or, with no room left, sleeps until a slot frees.\n if (claimed.length === room) continue;\n if (this.woken && this.inFlight.size < this.config.concurrency) {\n continue;\n }\n }\n\n await this.sleep();\n }\n }\n\n /**\n * Waits for the next thing worth waking for: a slot freeing, a `subscribe`\n * driver saying there is work, or the poll timer a driver without\n * `subscribe` gets.\n *\n * A claim that rejected arms a timer whatever the driver does, and that is\n * the whole reason this is not just the `subscribe` test. The wake that\n * prompted the lost claim has already been consumed, so a driver that only\n * speaks up on an enqueue — LISTEN/NOTIFY, say — has nothing left to\n * announce: an idle worker whose one claim died on a connection reset would\n * sit there, with every job already waiting, until some unrelated process\n * enqueued again. On a busy worker an in-flight job's `wake` covers it; an\n * idle one has nothing to be rescued by.\n *\n * The delay doubles per consecutive failure so that storage which is\n * properly down is not asked once a `pollInterval` for as long as it stays\n * down, and the first claim that returns clears the count.\n */\n private sleep() {\n return new Promise<void>((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const delay = this.claimFailures > 0\n ? this.retryDelay()\n : this.driver.subscribe\n ? undefined\n : this.config.pollInterval;\n if (delay !== undefined) {\n // Unref'd: a polling queue alone should not hold the process open.\n timer = setTimeout(() => this.wake(), delay);\n timer.unref?.();\n }\n this.resume = () => {\n clearTimeout(timer);\n resolve();\n };\n });\n }\n\n /** `pollInterval`, doubled once per consecutive claim failure, capped. */\n private retryDelay() {\n const doublings = Math.min(Math.max(0, this.claimFailures - 1), 30);\n return Math.min(this.config.pollInterval * 2 ** doublings, maxClaimBackoff);\n }\n\n private wake() {\n this.woken = true;\n const resume = this.resume;\n this.resume = undefined;\n resume?.();\n }\n\n /**\n * Whether the driver keeps jobs past this process: anything but memory.\n * Only such a queue can be fed by one process and drained by another.\n */\n get durable() {\n return !(this.driver instanceof MemoryQueueDriver);\n }\n\n /** See `claimsInThisProcess`. A manager built by hand always may. */\n private mayClaimHere() {\n return !this.application || claimsInThisProcess();\n }\n\n private lease(): ClaimOptions {\n return { visibilityTimeoutMs: this.config.visibilityTimeout };\n }\n\n private execute(claimed: ClaimedJob) {\n const key = `${claimed.id}:${claimed.attempt}`;\n const attempt = () => this.run(claimed);\n const done = (\n this.application\n ? kernelContext.run(this.application, attempt)\n : attempt()\n )\n .catch((error) => {\n // Only the driver's own reports reach here. The claim was not ended,\n // so its lease runs out and the job is claimed again.\n console.error(\n `The queue could not record the outcome of ${claimed.name}.`,\n error,\n );\n })\n .finally(() => {\n this.inFlight.delete(key);\n this.wake();\n });\n this.inFlight.set(key, { job: claimed, done });\n this.heartbeat();\n }\n\n /**\n * Keeps every running job's lease alive, at a third of the visibility\n * timeout so one late tick does not lose it. Stops itself once nothing is\n * running, and is unref'd, like the poll.\n */\n private heartbeat() {\n const driver = this.driver;\n if (!driver.heartbeat || this.heartbeatTimer) return;\n this.heartbeatTimer = setInterval(\n () => {\n if (this.inFlight.size === 0) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n return;\n }\n const jobs = [...this.inFlight.values()].map((e) => e.job);\n driver.heartbeat!(jobs, this.lease()).catch((error) => {\n console.error(`The queue could not extend its job leases.`, error);\n });\n },\n Math.max(1, Math.floor(this.config.visibilityTimeout / 3)),\n );\n this.heartbeatTimer.unref?.();\n }\n\n /**\n * One attempt at one claimed job, ending the claim either way.\n *\n * `onSuccess` is inside the `try`, as it always was, so a throw from it is a\n * failed attempt and is retried. `onFail` and `onDeadletter` are not: a\n * throw from either is logged and the claim still ends, where it used to\n * escape the queue with the job's slot still counted as taken.\n */\n private async run(claimed: ClaimedJob) {\n const Job = this.jobs[claimed.name];\n\n if (!Job) {\n // A driver other processes share may have handed this out because it\n // cannot filter by name, and while a blue/green ramp has both releases\n // claiming from one table, a name this replica lacks is usually one only\n // the other release has. So it goes back, without spending an attempt,\n // for a replica that has the class — until `unknownJobGrace` has passed\n // since the dispatch, after which the name is taken to be gone. Measured\n // from `createdAt` because that is all a claim carries; it is the\n // driver's clock against this one, and the skew is seconds against a\n // window of minutes. A driver that does filter only hands out an unknown\n // name once it has waited out the same window, so it normally lands\n // below; with this clock a few seconds behind the driver's, one handed\n // out right at the boundary is released instead, and — its window now\n // restarting from the release — waits one more `unknownJobGrace`. Late,\n // not lost, so no slack is subtracted for it.\n const age = Date.now() - claimed.createdAt;\n if (this.durable && age < this.config.unknownJobGrace) {\n // Once per name: with a driver that cannot filter, every poll can\n // claim the same job again, and a line per poll would bury the one\n // that matters — a replica whose discovery missed a job, say.\n if (!this.releasedNames.has(claimed.name)) {\n this.releasedNames.add(claimed.name);\n console.error(\n `Left a queued \"${claimed.name}\" for another replica: nothing is ` +\n `registered here under that name. It is dead-lettered if no ` +\n `replica has run it within unknownJobGrace ` +\n `(${this.config.unknownJobGrace}ms) of its dispatch.`,\n );\n }\n // Off this replica's own rhythm, and differently each time. It claims\n // again the moment the release lands, finds nothing due, and then\n // polls every `pollInterval`; a delay of exactly one interval made the\n // job due at the instant of that next poll, so the replica that could\n // not run it won the race for it every time. Any fixed fraction has\n // the same flaw for a replica whose polls happen to fall just before\n // the due moment each cycle — it loses every race, for good. Random\n // within one interval, every other replica's next poll has a chance.\n await this.driver.release(claimed, {\n retryInMs: Math.round(\n this.config.pollInterval * (1 + Math.random()),\n ),\n });\n return;\n }\n\n // The one place this is observable. A dispatch carries a name, the\n // registry is keyed by name, and a name nobody registered matches\n // nothing — which is exactly the silence #322 is about, except here it\n // has already happened and the work is gone. Saying so is all that is\n // left to do about it. Dead-lettered, which for the memory driver is the\n // drop it always was and for a durable one leaves a record to find.\n console.error(\n `Dropped a queued job: nothing is registered under the name ` +\n `\"${claimed.name}\". If the class exists, it was not ` +\n `discovered — check that it is under the queue slice's jobsDir ` +\n `(app/jobs by default), or list it in app/config/queue.ts.`,\n );\n await this.driver.fail(claimed, {\n error: `No job is registered under the name \"${claimed.name}\".`,\n retryInMs: null,\n });\n return;\n }\n\n let job: InstanceType<typeof Job>;\n try {\n job = new Job();\n } catch (error) {\n // A constructor or a class field that throws — `private mailer =\n // app(Mailer)` with nothing bound, say. Every other failure in here is\n // reported against the instance, and this one has no instance to report\n // against, which is exactly why it has to be caught here rather than\n // left to the generic path: that path never ends the claim, so the lease\n // lapses, the job is claimed again, and the constructor throws again,\n // once per visibility timeout, forever. The `maxAttempts` guard below\n // would normally stop that, and it cannot — answering \"how many attempts\n // does this job get?\" needs the instance that cannot be built. So it is\n // dead-lettered unrun, like a job whose arguments are not JSON: no\n // attempt of it can ever do anything but this.\n console.error(\n `Dropped a queued ${claimed.name}: it could not be constructed.`,\n error,\n );\n await this.driver.fail(claimed, {\n error: `The job could not be constructed: ${String(error)}`,\n retryInMs: null,\n });\n return;\n }\n\n let args: any[];\n try {\n args = JSON.parse(claimed.args);\n } catch (error) {\n // Not something `Job.dispatch` writes, so a driver changed it. Left\n // alone, the claim would never end and the job would be reclaimed,\n // unparseable, forever.\n console.error(\n `Dropped a queued ${claimed.name}: its arguments are not JSON.`,\n error,\n );\n await this.driver.fail(claimed, {\n error: `The arguments are not JSON: ${String(error)}`,\n retryInMs: null,\n });\n return;\n }\n\n // Claimed past its last attempt: an earlier claim used that attempt up and\n // never reported, which is what a process exiting mid-run looks like from\n // here. Running it again would exceed `maxAttempts`, so it is dead-lettered\n // unrun. `Math.max` keeps a `maxAttempts` below one meaning what it always\n // did — one attempt.\n if (claimed.attempt > Math.max(1, job.maxAttempts)) {\n const error = new Error(\n `${claimed.name} was claimed for attempt ${claimed.attempt} of ` +\n `${job.maxAttempts}: an earlier attempt never finished, most likely ` +\n `because the process running it exited. Dead-lettered without ` +\n `running it again.`,\n );\n hook(`${claimed.name}.onDeadletter`, () =>\n job.onDeadletter(error, ...args),\n );\n await this.driver.fail(claimed, {\n error: error.message,\n retryInMs: null,\n });\n await this.releaseUnique(job, args, claimed);\n return;\n }\n\n // Per-key limits (#661), before the job runs. A job over a limit goes\n // back without spending an attempt: it is waiting for capacity, not\n // failing.\n const admitted = await this.admit(job, args, claimed);\n if (!admitted) return;\n\n let error: Error | undefined;\n let result: unknown;\n try {\n result = await (job.worker\n ? runInWorker(claimed.name, claimed.args)\n : job.run(...args));\n } catch (err) {\n error = err as Error;\n } finally {\n await admitted.done();\n }\n\n const outcome = job.$outcome;\n if (error === undefined && outcome?.kind === \"release\") {\n // `this.release(delay)`: back to waiting, the attempt taken back.\n await this.driver.release(claimed, { retryInMs: Math.max(0, outcome.delayMs) });\n return;\n }\n if (error === undefined && outcome?.kind === \"fail\") error = outcome.error;\n const retry = outcome?.kind === \"fail\" ? outcome.retry : true;\n\n if (error === undefined) {\n try {\n job.onSuccess(result, ...args);\n } catch (err) {\n error = err as Error;\n }\n }\n\n if (error !== undefined) {\n const recorded = String(error?.stack ?? error);\n hook(`${claimed.name}.onFail`, () => job.onFail(error, ...args));\n\n if (!retry || claimed.attempt >= job.maxAttempts) {\n hook(`${claimed.name}.onDeadletter`, () =>\n job.onDeadletter(error, ...args),\n );\n await this.driver.fail(claimed, { error: recorded, retryInMs: null });\n await this.releaseUnique(job, args, claimed);\n } else {\n await this.driver.fail(claimed, {\n error: recorded,\n retryInMs: backoffFor(job.backoff, claimed.attempt),\n });\n }\n return;\n }\n\n await this.driver.complete(claimed);\n await this.releaseUnique(job, args, claimed);\n }\n\n /**\n * Takes a slot under the job's `concurrency` key and a hit under each of\n * its `throttle` keys, all in the queue's lock store, so the limits hold\n * across every process sharing it. Resolves to what to undo after the run,\n * or `undefined` when the job was released to wait for capacity.\n *\n * Concurrency is `limit` slot locks per key, each leased for the\n * visibility timeout and renewed while the job runs, so a slot held by a\n * dead process frees itself like the job's own lease. Throttles are fixed\n * windows counted when a job is admitted, whether it then succeeds or not.\n */\n private async admit(\n job: Job,\n args: unknown[],\n claimed: ClaimedJob,\n ): Promise<{ done(): Promise<void> } | undefined> {\n const concurrency = job.concurrency(...args);\n const throttles = [job.throttle(...args) ?? []].flat();\n if (!concurrency && throttles.length === 0) return { done: async () => {} };\n\n const locks = this.locks;\n let slot: HeldLock | undefined;\n if (concurrency && concurrency.limit > 0) {\n const ttl = this.config.visibilityTimeout;\n for (let i = 0; i < concurrency.limit && !slot; i++) {\n slot =\n (await locks.acquire(`gemi:concurrency:${concurrency.key}:${i}`, {\n ttl,\n renew: true,\n })) ?? undefined;\n }\n if (!slot) {\n await this.driver.release(claimed, {\n retryInMs: Math.round(this.config.pollInterval * (1 + Math.random())),\n });\n return undefined;\n }\n }\n\n const counted: string[] = [];\n for (const throttle of throttles) {\n const name = `gemi:throttle:${throttle.key}`;\n const hit = await locks.store.hit(name, throttle.limit, throttle.window);\n if (!hit.allowed) {\n await Promise.all(counted.map((counted) => locks.store.refund(counted)));\n await slot?.release();\n await this.driver.release(claimed, { retryInMs: Math.max(1, hit.resetInMs) });\n return undefined;\n }\n counted.push(name);\n }\n\n return {\n done: async () => {\n await slot?.release().catch((error) => {\n console.error(`[gemi] Could not free a concurrency slot of ${claimed.name}.`, error);\n });\n },\n };\n }\n}\n\n/** The lock a unique job's key takes, namespaced by the job's name. */\nfunction uniqueLockName(job: string, id: string | number) {\n return `gemi:job:${job}:${String(id)}`;\n}\n\nfunction hook(name: string, fn: () => void) {\n try {\n fn();\n } catch (error) {\n console.error(`${name} threw; the job's claim was ended anyway.`, error);\n }\n}\n\nfunction isProduction() {\n return process.env.NODE_ENV === \"production\";\n}\n\n/**\n * Whether this process should claim from a driver shared with other\n * processes: a `gemi queue:work` worker, or a server's main thread unless\n * `GEMI_QUEUE_CLAIM=off` hands its jobs to workers. `ROOT_DIR` is set only by\n * a starting server or worker, so a console command, a seed\n * or a migration — which boot the same providers — is not one, and neither is\n * a `worker` job's thread, which clones the application and exits after it.\n *\n * A worker claims whatever `GEMI_QUEUE_CLAIM` says, because the variable is\n * for the web process, and a deploy that sets it once for every container\n * would otherwise leave nothing claiming at all.\n */\nexport function claimsInThisProcess() {\n if (!isMainThread) return false;\n if (isQueueWorker()) return true;\n return process.env.ROOT_DIR !== undefined && !claimingTurnedOff();\n}\n\n/**\n * `GEMI_QUEUE_CLAIM=off` (any case): this server dispatches into a shared\n * driver and leaves the claiming to `gemi queue:work` processes. Anything else\n * claims, as before. An environment variable rather than a config field for\n * the reason `GEMI_NO_SCHEDULE` is one: the web and the worker run the same\n * build with the same config, and only the process knows which it is.\n */\nexport function claimingTurnedOff(env: Record<string, string | undefined> = process.env) {\n return (env.GEMI_QUEUE_CLAIM ?? \"\").trim().toLowerCase() === \"off\";\n}\n\n// On `globalThis` under a registry symbol, like the shutdown flag, so the mark\n// the worker entry sets is the one this module reads if two copies of it are\n// loaded. Not an environment variable: a job that spawns a child process\n// would hand it on, and the child, a script, would start claiming.\nconst QUEUE_WORKER = Symbol.for(\"gemi.queue.worker\");\n\n/** Marks this process as a `gemi queue:work` worker; see `claimsInThisProcess`. */\nexport function markQueueWorker(worker = true) {\n (globalThis as { [QUEUE_WORKER]?: boolean })[QUEUE_WORKER] = worker;\n}\n\nexport function isQueueWorker(): boolean {\n return (globalThis as { [QUEUE_WORKER]?: boolean })[QUEUE_WORKER] === true;\n}\n\n/**\n * The delay before the retry that follows attempt `attempt`: the number\n * itself, or the array's entry for that retry with its last entry repeated.\n */\nexport function backoffFor(backoff: number | number[], attempt: number) {\n const delay = Array.isArray(backoff)\n ? backoff[Math.min(attempt, backoff.length) - 1]\n : backoff;\n return Math.max(0, delay ?? 0);\n}\n\nfunction resolveDriver(\n driver: Required<QueueConfig>[\"driver\"],\n application: Application | undefined,\n): QueueDriver {\n if (driver === \"memory\") return new MemoryQueueDriver();\n if (driver === \"database\" || typeof driver === \"function\") {\n // Only a manager built by hand has no application. A factory that takes\n // one would otherwise fail on `undefined` with a TypeError that names\n // neither the queue nor what is missing.\n if (!application && (driver === \"database\" || driver.length > 0)) {\n throw new Error(\n `The queue driver needs the application it belongs to, and this ` +\n `QueueManager was built without one. Pass { application }.`,\n );\n }\n if (driver === \"database\") {\n return new DatabaseQueueDriver(application!.make(DatabaseManager));\n }\n return driver(application!);\n }\n if (typeof driver === \"string\") {\n throw new Error(\n `Unknown queue driver \"${driver}\". The queue slice's driver is ` +\n `\"memory\", \"database\", a QueueDriver, or a function returning one.`,\n );\n }\n return driver;\n}\n",
9
- "import type { Application } from \"../../foundation/Application\";\nimport type { Job } from \"./Job\";\nimport type { LockStore } from \"../lock/LockStore\";\nimport type { QueueDriver } from \"./QueueDriver\";\n\n// Config key: `queue`. Derived from `QueueServiceProvider`.\nexport interface QueueConfig {\n /**\n * The `Job` subclasses this application dispatches, or nothing.\n *\n * ### Why \"or nothing\" is the point\n *\n * A dispatch that names a job the `QueueManager` has never heard of does not\n * fail — `run()` looks the name up in this list, and what is not there is\n * dropped with a line on stderr and nothing else. With the memory driver that\n * happens at once; with one other processes share (the database driver) the\n * job is first left for a replica that has the class, so the line comes only\n * after `unknownJobGrace` — an hour by default, of rows sitting pending with\n * no output at all. `Job.dispatch` has already\n * returned by then, so nothing upstream can be told. The list is therefore a\n * second spelling of `app/jobs`, kept in step by hand, and the cost of the\n * two disagreeing is paid by whatever was supposed to happen after the\n * dispatch.\n *\n * Leaving this out spells it once: the jobs are the classes under `jobsDir`.\n *\n * ### The rule, exactly\n *\n * **Declared wins, and `[]` is declared.** A `jobs` that is present is used\n * verbatim and no directory is read — that is the escape hatch for an app\n * whose jobs live somewhere this cannot walk, for one that deliberately\n * registers a subset, and for a deploy that ships no source. An empty array\n * means an app with no jobs and says so; it does not mean \"find some\".\n *\n * **Absent or `undefined` discovers.** `undefined` counts as absent for the\n * same reason `withDefaults` treats it that way everywhere else: a key spread\n * in from an optional value is an omission, not an instruction.\n */\n jobs?: Array<new () => Job>;\n\n /**\n * Where to look when `jobs` was not declared. Relative to the project root,\n * or absolute.\n *\n * Every `.ts`/`.tsx` file underneath it is imported at boot and every exported\n * class extending `Job` is registered, so this wants to be a directory of job\n * declarations rather than a directory that merely contains some. Moving the\n * jobs is what this field is for; pointing it at `app/` is not.\n */\n jobsDir?: string;\n\n concurrency?: number;\n\n /**\n * Where queued jobs are kept: `\"memory\"`, `\"database\"`, a `QueueDriver`, or\n * a function returning one.\n *\n * `\"memory\"` is the default and keeps them in this process — so **a restart,\n * a deploy or a crash loses every job that is waiting or running**, silently.\n * See `MemoryQueueDriver`.\n *\n * `\"database\"` keeps them in the `gemi_jobs` table of the default database\n * connection, which has to exist. See `DatabaseQueueDriver`.\n *\n * A function is called once per application, when the queue is first\n * resolved, with that application — so it can pick a named connection:\n * `(app) => new DatabaseQueueDriver(app.make(DatabaseManager).connection(\"jobs\"))`.\n * Prefer it to an instance: a config module is imported by more than the\n * server (tests building several applications, build tooling, a `worker`\n * job's thread), and an instance created at import is shared by every\n * application built from that config, or opened by a process that never\n * runs a job.\n */\n driver?:\n | \"memory\"\n | \"database\"\n | QueueDriver\n | ((application: Application) => QueueDriver);\n\n /**\n * How long a claimed job is leased for, in milliseconds, before a driver\n * that shares its storage between processes may hand it to another one. The\n * manager heartbeats every job it is running at a third of this, so it bounds\n * how long a dead process's jobs stay stuck, not how long a job may run. A\n * job that blocks the event loop for longer than this can be run twice.\n */\n visibilityTimeout?: number;\n\n /**\n * How often, in milliseconds, to ask a driver without `subscribe` for work.\n * The memory driver wakes the queue itself and is never polled.\n *\n * It is also how long the queue waits before retrying a `claim` that\n * rejected — for every driver, `subscribe` or not, because the wake that\n * prompted the lost claim is spent and a subscribing driver has no reason to\n * send another. That delay doubles per consecutive failure, up to a minute,\n * so storage that is down is not asked this often for as long as it is down.\n */\n pollInterval?: number;\n\n /**\n * How long, in milliseconds, a job under a name this process has no class\n * for is left for another process before it is dead-lettered. Default one\n * hour.\n *\n * Only a driver other processes share waits: during a blue/green ramp both\n * releases claim from one table, and a job only the new release has is\n * usually one of its replicas' to run, not a dead letter. The database driver\n * does not claim such a job at all until it has been claimable for this\n * long; a driver that cannot filter by name hands it out, and the queue gives\n * it back without spending an attempt. Past the window the name is taken to\n * be gone — a job class that was deleted or renamed — and the job is\n * dead-lettered with its error, as it always was.\n *\n * Longer than a ramp, then, and than a rollback that should find the other\n * release's jobs still waiting. `Infinity` never dead-letters an unknown\n * name. The memory driver ignores this: nothing else can run its jobs, so an\n * unknown name there is dead-lettered at once.\n */\n unknownJobGrace?: number;\n\n /**\n * Where locks are kept: the ones unique jobs, a cron job's\n * `withoutOverlapping` and `onOneServer`, and the `Lock` facade take.\n *\n * `\"auto\"`, the default, keeps them where the driver keeps jobs: with\n * `\"database\"` that is the `gemi_locks` table of the same connection, so\n * they hold across every process sharing it; with `\"memory\"` it is this\n * process. `\"memory\"` and `\"database\"` (the default connection) choose\n * explicitly, and a `LockStore`, or a function returning one, is used as\n * given. Nothing is read or written until one of those features is used.\n */\n locks?:\n | \"auto\"\n | \"memory\"\n | \"database\"\n | LockStore\n | ((application: Application) => LockStore);\n}\n\nexport function defineQueueConfig(config: QueueConfig): QueueConfig {\n return config;\n}\n\nexport function queueConfigDefaults(): Required<QueueConfig> {\n return {\n jobs: [],\n jobsDir: \"app/jobs\",\n concurrency: 1,\n driver: \"memory\",\n visibilityTimeout: 5 * 60_000,\n pollInterval: 1000,\n unknownJobGrace: 60 * 60_000,\n locks: \"auto\",\n };\n}\n",
10
- "import type {\n ClaimOptions,\n ClaimedJob,\n EnqueueJob,\n JobFailure,\n JobRelease,\n QueueDriver,\n} from \"./QueueDriver\";\n\ntype Entry = {\n job: ClaimedJob;\n /** Waiting: when it becomes due. Leased: when the lease runs out. */\n until: number;\n leased: boolean;\n /** Tie-break for `until`, so jobs that became claimable together stay FIFO. */\n seq: number;\n};\n\n/**\n * The default driver: a Map in this process's memory.\n *\n * **Everything in it is lost when the process exits.** A job that is waiting,\n * one waiting out a retry's backoff, and one halfway through `run` all vanish\n * with the process, and no hook fires for any of them — `Job.dispatch()`\n * resolved long ago, so nothing upstream is told. A deploy, a scale-in or a\n * crash is enough. Use it for development, tests, and work that is safe to\n * lose; anything that is not needs a driver backed by storage that outlives\n * the process.\n *\n * It implements the whole claim contract anyway — leases, reclaim on expiry,\n * stale-claim rejection — rather than the subset one process strictly needs.\n * That keeps it a faithful stand-in for a persistent driver in tests, and lets\n * the shared contract suite run against it unchanged. A lease can only lapse\n * here if the event loop was blocked for longer than the visibility timeout,\n * since the manager heartbeats every job it is running.\n *\n * It ignores `registered` in `claim` and hands out every name. Nothing but\n * this process can run its jobs, so a name this process does not know will\n * not be known by anyone, and the manager dead-letters it at once with a line\n * saying so; filtered out, it would wait here unseen until the process exited.\n *\n * One timer at most, for the next moment something becomes claimable, and it\n * is unref'd: a queue with nothing due does not hold the process open, and\n * neither does one waiting out a backoff — the same way the queue never has.\n */\nexport class MemoryQueueDriver implements QueueDriver {\n private entries = new Map<string, Entry>();\n private listeners = new Set<() => void>();\n private timer: ReturnType<typeof setTimeout> | undefined;\n private timerAt = Infinity;\n private seq = 0;\n\n /** Jobs waiting to be claimed, including ones not yet due. */\n get waiting(): number {\n let count = 0;\n for (const entry of this.entries.values()) if (!entry.leased) count++;\n return count;\n }\n\n /** Jobs currently claimed and not yet reported on. */\n get leased(): number {\n return this.entries.size - this.waiting;\n }\n\n async enqueue({\n name,\n args,\n delayMs = 0,\n id = crypto.randomUUID(),\n }: EnqueueJob): Promise<string> {\n const now = Date.now();\n this.entries.set(id, {\n job: { id, name, args, attempt: 0, createdAt: now },\n until: now + Math.max(0, delayMs),\n leased: false,\n seq: this.seq++,\n });\n this.changed();\n return id;\n }\n\n async claim(limit: number, options: ClaimOptions): Promise<ClaimedJob[]> {\n const now = Date.now();\n const due = [...this.entries.values()]\n .filter((entry) => entry.until <= now)\n .sort((a, b) => a.until - b.until || a.seq - b.seq)\n .slice(0, Math.max(0, limit));\n\n for (const entry of due) {\n entry.job = { ...entry.job, attempt: entry.job.attempt + 1 };\n entry.leased = true;\n entry.until = now + options.visibilityTimeoutMs;\n }\n if (due.length > 0) this.schedule();\n\n // Copies, so a caller holding one cannot move the record underneath us.\n return due.map((entry) => ({ ...entry.job }));\n }\n\n async complete(job: ClaimedJob): Promise<void> {\n if (!this.current(job)) return;\n this.entries.delete(job.id);\n this.schedule();\n }\n\n async fail(job: ClaimedJob, failure: JobFailure): Promise<void> {\n const entry = this.current(job);\n if (!entry) return;\n\n if (failure.retryInMs === null) {\n this.entries.delete(job.id);\n this.schedule();\n return;\n }\n\n entry.leased = false;\n entry.until = Date.now() + Math.max(0, failure.retryInMs);\n entry.seq = this.seq++;\n this.changed();\n }\n\n async release(job: ClaimedJob, release: JobRelease): Promise<void> {\n const entry = this.current(job);\n if (!entry) return;\n\n entry.job = { ...entry.job, attempt: entry.job.attempt - 1 };\n entry.leased = false;\n entry.until = Date.now() + Math.max(0, release.retryInMs);\n entry.seq = this.seq++;\n this.changed();\n }\n\n async heartbeat(jobs: ClaimedJob[], options: ClaimOptions): Promise<void> {\n const until = Date.now() + options.visibilityTimeoutMs;\n for (const job of jobs) {\n const entry = this.current(job);\n if (entry) entry.until = until;\n }\n this.schedule();\n }\n\n subscribe(wake: () => void): () => void {\n this.listeners.add(wake);\n return () => void this.listeners.delete(wake);\n }\n\n /**\n * The entry for a report, if the report is about the claim that currently\n * holds it. A stale claimer — one whose lease lapsed and was re-issued —\n * gets nothing, so its late `complete` cannot end the newer claim.\n */\n private current(job: ClaimedJob): Entry | undefined {\n const entry = this.entries.get(job.id);\n if (!entry || !entry.leased || entry.job.attempt !== job.attempt) {\n return undefined;\n }\n return entry;\n }\n\n private changed() {\n this.schedule();\n const now = Date.now();\n for (const entry of this.entries.values()) {\n if (entry.until <= now) {\n this.wake();\n return;\n }\n }\n }\n\n private wake() {\n for (const listener of [...this.listeners]) listener();\n }\n\n /** Points the one timer at the next moment something becomes claimable. */\n private schedule() {\n const now = Date.now();\n let next = Infinity;\n for (const entry of this.entries.values()) {\n if (entry.until > now && entry.until < next) next = entry.until;\n }\n if (next === this.timerAt) return;\n\n clearTimeout(this.timer);\n this.timer = undefined;\n this.timerAt = next;\n if (next === Infinity) return;\n\n this.timer = setTimeout(() => {\n this.timer = undefined;\n this.timerAt = Infinity;\n this.changed();\n }, next - now);\n this.timer.unref?.();\n }\n}\n",
11
- "import { app } from \"../../foundation/app\";\nimport { QueueManager } from \"./QueueManager\";\n\nexport class Job {\n static name = \"unset\";\n worker = false;\n maxAttempts = 3;\n\n /**\n * Milliseconds to wait before each retry. A number is used for every retry;\n * an array gives the delay before the first retry, the second, and so on,\n * repeating its last entry. `0`, the default, retries as soon as a slot is\n * free.\n */\n backoff: number | number[] = 0;\n\n /**\n * Makes the job unique per key, across every process sharing the queue's\n * storage. Return a key from the dispatch arguments, or `undefined` for a\n * dispatch that should not be unique. While a job with the same key is\n * waiting or running, another dispatch is not queued: it resolves to the\n * id of the job already there. The key is freed when that job completes or\n * is dead-lettered, and at the latest after `uniqueFor`.\n *\n * The default returns `undefined`: a job is not unique unless it says so.\n */\n uniqueId(..._args: any[]): string | number | undefined | null {\n return undefined;\n }\n\n /**\n * How long a unique job's key is held at most, in milliseconds, so a job\n * lost with its process does not block its key forever. Default one hour.\n */\n uniqueFor = 60 * 60_000;\n\n /**\n * Rate limits per key, computed from the dispatch arguments: at most\n * `limit` jobs admitted per fixed `window` (milliseconds) under each `key`,\n * across every process sharing the queue's storage. A job over a limit is\n * put back until the window resets, without spending an attempt. Counted\n * when the job is admitted to run, whether it then succeeds or not.\n */\n throttle(..._args: any[]): JobThrottle | JobThrottle[] | undefined | null {\n return undefined;\n }\n\n /**\n * At most `limit` jobs with this `key` running at once, across every process\n * sharing the queue's storage. A job with no free slot waits, without\n * spending an attempt.\n */\n concurrency(..._args: any[]): JobConcurrency | undefined | null {\n return undefined;\n }\n\n /**\n * Called from `run`: puts the job back to wait `delayMs` once `run`\n * returns, without counting the attempt or calling any hook. For a provider\n * that answered \"slow down\". Not available to `worker` jobs.\n */\n release(delayMs = 0): void {\n this.$outcome = { kind: \"release\", delayMs };\n }\n\n /**\n * Called from `run`: fails this attempt once `run` returns, as a throw\n * would. With `retry: false` it is dead-lettered at once, whatever\n * `maxAttempts` says: for work that must not be repeated, such as a send\n * whose acceptance is uncertain. A throw after `fail(..., { retry: false })`\n * is dead-lettered too. Not available to `worker` jobs.\n */\n fail(error: unknown, options: { retry?: boolean } = {}): void {\n this.$outcome = {\n kind: \"fail\",\n error: error instanceof Error ? error : new Error(String(error)),\n retry: options.retry ?? true,\n };\n }\n\n /** @internal What `release` or `fail` asked for during this run. */\n $outcome:\n | { kind: \"release\"; delayMs: number }\n | { kind: \"fail\"; error: Error; retry: boolean }\n | undefined;\n\n run(..._args: any[]): Promise<any> | any {}\n\n onFail(_error: Error, ..._args: any[]): void {}\n onSuccess(_result: any, ..._args: any[]): void {}\n onDeadletter(_error: Error, ..._args: any[]): void {}\n\n /**\n * Queues the job and resolves to its id once the driver has recorded it.\n *\n * Not `async`, deliberately: a missing name and arguments JSON cannot carry\n * still throw here, on the caller's stack, as they did when this returned\n * `void`. Only the driver's answer is asynchronous. Ignoring the promise is\n * fine with the memory driver, whose enqueue cannot fail; with a driver\n * that can, an ignored rejection is an unhandled one, so await it.\n */\n static dispatch<T extends Job>(\n this: new () => T,\n ...args: Parameters<T[\"run\"]>\n ): Promise<string> {\n if (this.name === \"unset\") {\n throw new Error(\"Cannot dispatch a job with no name\");\n }\n\n return app(QueueManager).push(this, JSON.stringify(args));\n }\n}\n\nexport type JobThrottle = {\n /** Shared by every job that counts against the same budget. */\n key: string;\n limit: number;\n /** The window, in milliseconds. */\n window: number;\n};\n\nexport type JobConcurrency = { key: string; limit: number };\n"
12
- ],
13
- "mappings": "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",
14
- "debugId": "22064B8F734D075464756E2164756E21",
15
- "names": []
16
- }
@@ -1,5 +0,0 @@
1
- // @bun
2
- import{AsyncLocalStorage as E}from"async_hooks";class Q{content={title:"Gemi App",description:null,openGraph:null};touched=!1;render(){return{title:this.content.title,description:this.content.description,openGraph:this.content.openGraph}}title(j){this.touched=!0,this.content.title=j}description(j){this.touched=!0,this.content.description=j}openGraph({title:j,description:B,type:z,url:G,image:J,imageAlt:U,imageWidth:V,imageHeight:X,twitterImage:Y=J,twitterImageAlt:D=U,twitterImageWidth:K=V,twitterImageHeight:M=X}){this.touched=!0;let Z=J,$=Y;if(J&&!J.startsWith("http"))Z=`${process.env.HOST_NAME}${J}`,$=`${process.env.HOST_NAME}${Y}`;this.content.openGraph=Object.fromEntries(Object.entries({title:j,description:B,type:z,url:G,image:Z,imageAlt:U,imageWidth:V,imageHeight:X,twitterImage:$,twitterImageAlt:D,twitterImageWidth:K,twitterImageHeight:M}).filter(([O,N])=>N!==void 0))}}function b(j,B,z={}){return[`${j}=${B}`,z.maxAge?`Max-Age=${z.maxAge}`:"",z.httpOnly?"HttpOnly":"",z.secure?"Secure":"",z.sameSite?`SameSite=${z.sameSite}`:"SameSite=Strict",z.path?`Path=${z.path}`:"Path=/",z.domain?`Domain=${z.domain}`:"",z.expires?`Expires=${z.expires.toUTCString()}`:"",z.partitioned?"Partitioned":""].filter((G)=>G!=="").join("; ")}var P=new E;class F{req;cookies=new Set;headers=new Headers;serverQueries=null;user=null;csrfHmac=null;locale=null;metadata=new Q;rangeRequest=null;sessionId=null;featureEvaluations=null;featureContext=null;featureBuckets=null;pendingWork=new Set;ended=!1;constructor(j){this.req=j}waitUntil(j){if(this.ended)return;let B=Promise.resolve(j).then(()=>{},()=>{});this.pendingWork.add(B),B.then(()=>this.pendingWork.delete(B))}hasPendingWork(){return this.pendingWork.size>0}async whenIdle(){while(this.pendingWork.size>0)await Promise.all(Array.from(this.pendingWork))}setLocale(j){this.locale=j}renderMeta(){return this.metadata.render()}setCSRFHmac(j){this.csrfHmac=j}setCookie(j,B,z={}){this.cookies.add(b(j,B,z))}deleteCookie(j,B={}){this.cookies.add(b(j,"",{...B,maxAge:-1}))}setHeaders(j,B){this.headers.set(j,B)}setUser(j){this.user=j}setRequest(j){this.req=j}destroy(){this.ended=!0,delete this.cookies,delete this.headers,this.serverQueries=null,this.sessionId=null,this.featureEvaluations=null,this.featureContext=null,this.featureBuckets=null,delete this.user}}var L="session_id";function H(){let j=T.getStore();if(!j)return null;if(j.sessionId)return j.sessionId;let B=j.req?.cookies?.get(L);if(B)j.sessionId=B;return j.sessionId}function W(){let j=T.getStore();if(!j)return null;let B=H();if(B)return B;let z=Bun.randomUUIDv7();return j.sessionId=z,j.setCookie(L,z,{httpOnly:!0,secure:Bun.env.NODE_ENV==="production",sameSite:"Lax",expires:new Date(Date.now()+31536000000)}),z}class T{static getStore(){return P.getStore()}static setRequest(j){P.getStore().req=j}static run(j,B,z){let G=new F(j);if(z)G.user=z.user;return P.run(G,B)}static runWith(j,B){return P.run(j,B)}static exit(j){return P.exit(j)}}
3
- export{H as Ug,W as Vg,T as Wg};
4
-
5
- //# debugId=F6B99A11E50DE5D164756E2164756E21
@@ -1,11 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../http/requestContext.ts", "../http/Metadata.ts"],
4
- "sourcesContent": [
5
- "import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { HttpRequest } from \"./HttpRequest\";\nimport { Metadata } from \"./Metadata\";\nimport type { ByteRange } from \"./range\";\n// Type-only: `services/router` imports from `http`, so a runtime import here\n// would be circular.\nimport type { ServerQueryStore } from \"../services/router/ServerQueryStore\";\n\nexport interface CreateCookieOptions {\n maxAge?: number;\n expires?: Date;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"Strict\" | \"Lax\";\n path?: string;\n domain?: string;\n partitioned?: boolean;\n}\n\nexport function createCookie(\n name: string,\n value: string,\n options: CreateCookieOptions = {},\n) {\n return [\n `${name}=${value}`,\n options.maxAge ? `Max-Age=${options.maxAge}` : \"\",\n options.httpOnly ? \"HttpOnly\" : \"\",\n options.secure ? \"Secure\" : \"\",\n options.sameSite ? `SameSite=${options.sameSite}` : \"SameSite=Strict\",\n options.path ? `Path=${options.path}` : \"Path=/\",\n options.domain ? `Domain=${options.domain}` : \"\",\n options.expires ? `Expires=${options.expires.toUTCString()}` : \"\",\n options.partitioned ? \"Partitioned\" : \"\",\n ]\n .filter((i) => i !== \"\")\n .join(\"; \");\n}\n\nconst requestContext = new AsyncLocalStorage<Store>();\n\n/**\n * What a route's request context takes over from the global middleware list's\n * (see `runGlobalMiddleware`): the user, so one the list resolved is not\n * looked up a second time.\n */\nexport interface CarriedContext {\n user: any;\n}\n\nclass Store {\n cookies: Set<string> = new Set();\n headers: Headers = new Headers();\n /**\n * Every server-side query of this request — prefetched or discovered during\n * the streaming render. Assigned by the view router (or lazily by the\n * `Query` facade); `null` for api requests, which must not query themselves.\n */\n serverQueries: ServerQueryStore | null = null;\n user: any = null;\n csrfHmac: string | null = null;\n locale: string | null = null;\n metadata = new Metadata();\n /**\n * The parsed `Range` of the in-flight request, picked up by\n * `FileStorage.read()` so a range reaches the storage backend without every\n * handler having to thread it through by hand.\n *\n * Only populated inside a `this.stream()` route, and cleared once that route\n * returns: a `FileStorage.read()` in an ordinary handler must never start\n * silently returning partial bytes.\n */\n rangeRequest: ByteRange | null = null;\n\n /**\n * This request's `session_id`, once read or minted.\n *\n * The cookie itself cannot answer for it. `setCookie` appends a *serialized*\n * `Set-Cookie` string to `cookies`, so a value written during this request is\n * not readable back out of it, and `req.cookies` still holds only what the\n * browser sent. Anything needing the id in the same request that mints it —\n * feature-flag bucketing, most obviously — has to read it from here.\n */\n sessionId: string | null = null;\n\n /**\n * This request's feature-flag evaluations, computed once.\n *\n * One map per request is what keeps a handler and the SSR payload from\n * disagreeing: a percentage rollout evaluated twice would be consistent\n * anyway — the bucketing is deterministic — but an attribute hook reading a\n * clock, or a `now`-based rule, would not be. Anything the page renders and\n * anything the server branched on have to be the same answer.\n */\n featureEvaluations: Map<string, unknown> | null = null;\n /** The context those evaluations were made against, built at most once. */\n featureContext: unknown = null;\n /** Bucket memo, shared across every flag evaluated for this request. */\n featureBuckets: Map<string, number> | null = null;\n\n /**\n * What `waitUntil` was handed and has not settled yet. Public only because\n * `req.ctx()`'s declared type spells this class out, which a private field\n * cannot be part of; nothing outside the store should touch either.\n */\n pendingWork = new Set<Promise<void>>();\n ended = false;\n\n constructor(public req: HttpRequest) {}\n\n /**\n * Keeps this request open until `work` settles: the api router holds\n * `onRequestEnd` and `destroy()` back until then, so code in `work` still\n * finds `user`, headers and cookies here.\n *\n * For work that outlives the response on purpose. An agent run is the\n * reason it exists: a client that disconnects has not stopped the run, and\n * its next tool call still reads the user who started it. Ignored once the\n * request has ended — there is nothing left to hold open.\n *\n * Only api routes honour this. A view request ends regardless, so work\n * handed to its store — a run started in a view loader — finds no user.\n */\n waitUntil(work: PromiseLike<unknown>) {\n if (this.ended) {\n return;\n }\n const settled = Promise.resolve(work).then(\n () => {},\n () => {},\n );\n this.pendingWork.add(settled);\n void settled.then(() => this.pendingWork.delete(settled));\n }\n\n /** Whether anything handed to `waitUntil` is still running. */\n hasPendingWork() {\n return this.pendingWork.size > 0;\n }\n\n /**\n * Settles once everything handed to `waitUntil` has, including work added\n * while waiting — a run can start another before it ends.\n */\n async whenIdle() {\n while (this.pendingWork.size > 0) {\n await Promise.all(Array.from(this.pendingWork));\n }\n }\n\n setLocale(locale: string) {\n this.locale = locale;\n }\n\n renderMeta() {\n return this.metadata.render();\n }\n\n setCSRFHmac(hmac: string) {\n this.csrfHmac = hmac;\n }\n\n setCookie(name: string, value: string, options: CreateCookieOptions = {}) {\n this.cookies.add(createCookie(name, value, options));\n }\n\n /**\n * A cookie set with a `domain` or `path` is only cleared by a deletion that\n * names the same ones.\n */\n deleteCookie(name: string, options: Pick<CreateCookieOptions, \"domain\" | \"path\"> = {}) {\n this.cookies.add(createCookie(name, \"\", { ...options, maxAge: -1 }));\n }\n\n setHeaders(name: string, value: string) {\n this.headers.set(name, value);\n }\n\n setUser(user: any) {\n this.user = user;\n }\n\n setRequest(req: HttpRequest<any, any>) {\n this.req = req;\n }\n\n destroy() {\n this.ended = true;\n delete this.cookies;\n delete this.headers;\n this.serverQueries = null;\n this.sessionId = null;\n this.featureEvaluations = null;\n this.featureContext = null;\n this.featureBuckets = null;\n delete this.user;\n }\n}\n\nexport const SESSION_ID_COOKIE = \"session_id\";\n\n/**\n * The `session_id` the browser sent, or the one minted earlier in this request.\n * Never mints one, so it is safe on an api request, where a `Set-Cookie` nobody\n * asked for would be a surprise.\n *\n * `null` outside a request, and for an anonymous visitor whose first request is\n * not a view — both meaning \"no stable subject\", which callers must handle\n * rather than paper over with a random value. A per-call random id would give\n * every request its own bucket, which is worse than no bucketing at all: a\n * percentage rollout would reshuffle on every page load.\n *\n * ## It is client-supplied, and unauthenticated\n *\n * Whatever the browser sent, unvalidated. It is a bucketing subject, not a\n * credential — never authenticate or authorize on it. A visitor can pick a value\n * that lands them in any percentage rollout, and signing it would not change\n * that: they can discard the cookie and collect fresh ones until one does. Load\n * bearing enough to state, because `.feature()` route gating is built on top of\n * evaluation — see its documentation for what may safely gate a route.\n */\nexport function sessionId(): string | null {\n const store = RequestContext.getStore();\n if (!store) {\n return null;\n }\n if (store.sessionId) {\n return store.sessionId;\n }\n const sent = store.req?.cookies?.get(SESSION_ID_COOKIE);\n if (sent) {\n store.sessionId = sent;\n }\n return store.sessionId;\n}\n\n/**\n * The same, but mints and sets the cookie when the browser sent none.\n *\n * Call this once per **view** request, early enough that everything downstream —\n * flag evaluation included — sees the same id the browser is about to be given.\n * It used to live inline in `ViewRouteDispatcher.onRequestEnd`, which ran only\n * on the document path and only after the response was decided, so a client-side\n * navigation minted nothing and a first-ever visitor had no id at the point\n * flags were evaluated.\n */\nexport function ensureSessionId(): string | null {\n const store = RequestContext.getStore();\n if (!store) {\n return null;\n }\n const existing = sessionId();\n if (existing) {\n return existing;\n }\n\n const minted = Bun.randomUUIDv7();\n store.sessionId = minted;\n store.setCookie(SESSION_ID_COOKIE, minted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n // `Lax`, not `Strict`, and the difference is load-bearing. `Strict`\n // withholds the cookie on cross-site *top-level navigation*, so a visitor\n // arriving from a search result or a shared link would arrive without it,\n // be minted a new id, and overwrite the old one — re-bucketing on every\n // external entry, which is how most anonymous traffic arrives. This is not\n // a credential (`access_token` stays `Strict`); it is an opaque bucketing\n // id, and `Lax` still blocks cross-site POST.\n sameSite: \"Lax\",\n expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),\n });\n return minted;\n}\n\nexport class RequestContext {\n static getStore() {\n return requestContext.getStore()!;\n }\n\n static setRequest(req: HttpRequest<any, any>) {\n requestContext.getStore().req = req;\n }\n\n /**\n * Opens a fresh request scope. `carried` seeds it with what the global\n * middleware list resolved for the same request; nothing else crosses over.\n */\n static run<T>(\n httpRequest: HttpRequest,\n fn: () => T,\n carried?: CarriedContext | null,\n ): T {\n const store = new Store(httpRequest);\n if (carried) {\n store.user = carried.user;\n }\n return requestContext.run(store, fn);\n }\n\n /**\n * Re-enters an *existing* request scope. Stream lifecycle callbacks fire\n * after the HTTP server has taken over the response body — outside the\n * AsyncLocalStorage scope the request ran in — so the view router captures\n * the live store and re-enters it around user-facing hooks, keeping\n * `req.ctx()` (and everything context-backed) working inside them.\n */\n static runWith<T>(store: Store, fn: () => T): T {\n return requestContext.run(store, fn);\n }\n\n /**\n * Runs `fn` outside any request scope, as the server's own `fetch` handler\n * is. For a request dispatched from inside another one, which must start from\n * where a client's request would, not from its initiator's store.\n */\n static exit<T>(fn: () => T): T {\n return requestContext.exit(fn);\n }\n}\n",
6
- "export type OpenGraphParams = {\n title: string;\n description?: string;\n type: string;\n url: string;\n image: string;\n imageAlt?: string;\n imageWidth?: number;\n imageHeight?: number;\n twitterImage?: string;\n twitterImageAlt?: string;\n twitterImageWidth?: number;\n twitterImageHeight?: number;\n};\nexport class Metadata {\n content: any = {\n title: \"Gemi App\",\n description: null,\n openGraph: null,\n };\n\n /**\n * Whether a handler set anything, as opposed to `content` still being the\n * defaults. A partially rendered response that touched no metadata sends\n * none, so the client keeps what the skipped segments put there.\n */\n touched = false;\n\n render() {\n return {\n title: this.content.title,\n description: this.content.description,\n openGraph: this.content.openGraph,\n };\n }\n\n title(title: string) {\n this.touched = true;\n this.content.title = title;\n }\n\n description(description: string) {\n this.touched = true;\n this.content.description = description;\n }\n\n openGraph({\n title,\n description,\n type,\n url,\n image,\n imageAlt,\n imageWidth,\n imageHeight,\n twitterImage = image,\n twitterImageAlt = imageAlt,\n twitterImageWidth = imageWidth,\n twitterImageHeight = imageHeight,\n }: OpenGraphParams) {\n this.touched = true;\n let _image = image;\n let _twitterImage = twitterImage;\n if (image && !image.startsWith(\"http\")) {\n _image = `${process.env.HOST_NAME}${image}`;\n _twitterImage = `${process.env.HOST_NAME}${twitterImage}`;\n }\n this.content.openGraph = Object.fromEntries(\n Object.entries({\n title,\n description,\n type,\n url,\n image: _image,\n imageAlt,\n imageWidth,\n imageHeight,\n twitterImage: _twitterImage,\n twitterImageAlt,\n twitterImageWidth,\n twitterImageHeight,\n }).filter(([_, value]) => value !== undefined),\n );\n }\n}\n"
7
- ],
8
- "mappings": "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",
9
- "debugId": "F6B99A11E50DE5D164756E2164756E21",
10
- "names": []
11
- }