gemi 0.93.0 → 0.95.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 (212) hide show
  1. package/dist/ai/FallbackProvider.d.ts +154 -0
  2. package/dist/ai/FallbackProvider.d.ts.map +1 -0
  3. package/dist/ai/index.d.ts +2 -0
  4. package/dist/ai/index.d.ts.map +1 -1
  5. package/dist/ai/index.js +12 -12
  6. package/dist/ai/index.js.map +4 -3
  7. package/dist/app/index.js +1 -1
  8. package/dist/bun/plugin.js +1 -1
  9. package/dist/bun/preload.js +1 -1
  10. package/dist/{chunk-g2xbpr8r.js → chunk-1hnpr3xt.js} +2 -2
  11. package/dist/{chunk-g2xbpr8r.js.map → chunk-1hnpr3xt.js.map} +1 -1
  12. package/dist/{chunk-v51a4g6w.js → chunk-2jse74pn.js} +2 -2
  13. package/dist/{chunk-v51a4g6w.js.map → chunk-2jse74pn.js.map} +1 -1
  14. package/dist/{chunk-1yt4hjqx.js → chunk-2shkf2gs.js} +2 -2
  15. package/dist/{chunk-1yt4hjqx.js.map → chunk-2shkf2gs.js.map} +1 -1
  16. package/dist/{chunk-dve6ryfd.js → chunk-2yyr5tdc.js} +1 -1
  17. package/dist/{chunk-4k4mh3gh.js → chunk-323715e8.js} +2 -2
  18. package/dist/{chunk-4k4mh3gh.js.map → chunk-323715e8.js.map} +1 -1
  19. package/dist/{chunk-xk4r9372.js → chunk-37jb6q13.js} +2 -2
  20. package/dist/{chunk-xk4r9372.js.map → chunk-37jb6q13.js.map} +1 -1
  21. package/dist/{chunk-wd9zab3e.js → chunk-38k1kjyb.js} +2 -2
  22. package/dist/{chunk-wd9zab3e.js.map → chunk-38k1kjyb.js.map} +1 -1
  23. package/dist/{chunk-aq1hjk00.js → chunk-3fjkbcra.js} +1 -1
  24. package/dist/{chunk-6qyc4e22.js → chunk-3fr22rtg.js} +2 -2
  25. package/dist/{chunk-6qyc4e22.js.map → chunk-3fr22rtg.js.map} +1 -1
  26. package/dist/{chunk-jj0ynn5t.js → chunk-56gce2z7.js} +2 -2
  27. package/dist/{chunk-jj0ynn5t.js.map → chunk-56gce2z7.js.map} +1 -1
  28. package/dist/{chunk-qs9drxja.js → chunk-5en4ydde.js} +2 -2
  29. package/dist/{chunk-qs9drxja.js.map → chunk-5en4ydde.js.map} +1 -1
  30. package/dist/{chunk-z10c81a9.js → chunk-5px4rkzf.js} +2 -2
  31. package/dist/{chunk-z10c81a9.js.map → chunk-5px4rkzf.js.map} +1 -1
  32. package/dist/{chunk-bb8e7rhf.js → chunk-7dnb21s9.js} +2 -2
  33. package/dist/{chunk-bb8e7rhf.js.map → chunk-7dnb21s9.js.map} +1 -1
  34. package/dist/chunk-7hfv5n00.js +5 -0
  35. package/dist/{chunk-cyze295m.js.map → chunk-7hfv5n00.js.map} +4 -3
  36. package/dist/{chunk-p0px3ym0.js → chunk-7kedxtbp.js} +2 -2
  37. package/dist/{chunk-p0px3ym0.js.map → chunk-7kedxtbp.js.map} +1 -1
  38. package/dist/{chunk-vpcys0ty.js → chunk-7ktsceyz.js} +2 -2
  39. package/dist/{chunk-vpcys0ty.js.map → chunk-7ktsceyz.js.map} +1 -1
  40. package/dist/{chunk-s22afmm9.js → chunk-7s5nh5mx.js} +2 -2
  41. package/dist/{chunk-s22afmm9.js.map → chunk-7s5nh5mx.js.map} +1 -1
  42. package/dist/{chunk-fz805r97.js → chunk-7vjbebst.js} +2 -2
  43. package/dist/{chunk-fz805r97.js.map → chunk-7vjbebst.js.map} +1 -1
  44. package/dist/{chunk-a1y7pa5z.js → chunk-8g99gnsd.js} +2 -2
  45. package/dist/{chunk-a1y7pa5z.js.map → chunk-8g99gnsd.js.map} +1 -1
  46. package/dist/{chunk-khvnm11p.js → chunk-9g5cpkkk.js} +2 -2
  47. package/dist/{chunk-khvnm11p.js.map → chunk-9g5cpkkk.js.map} +1 -1
  48. package/dist/{chunk-v6vsjdp7.js → chunk-9xf2fx98.js} +2 -2
  49. package/dist/{chunk-v6vsjdp7.js.map → chunk-9xf2fx98.js.map} +1 -1
  50. package/dist/{chunk-9pcsm8g3.js → chunk-adm5g1v8.js} +2 -2
  51. package/dist/{chunk-9pcsm8g3.js.map → chunk-adm5g1v8.js.map} +1 -1
  52. package/dist/{chunk-ej5mkm7t.js → chunk-b8m6zs7e.js} +2 -2
  53. package/dist/{chunk-ej5mkm7t.js.map → chunk-b8m6zs7e.js.map} +1 -1
  54. package/dist/{chunk-1wdfjmkx.js → chunk-bedkys40.js} +2 -2
  55. package/dist/{chunk-1wdfjmkx.js.map → chunk-bedkys40.js.map} +1 -1
  56. package/dist/chunk-bk4f99s4.js +5 -0
  57. package/dist/{chunk-svzdv2rf.js.map → chunk-bk4f99s4.js.map} +5 -5
  58. package/dist/chunk-br9p390p.js +5 -0
  59. package/dist/chunk-br9p390p.js.map +11 -0
  60. package/dist/chunk-dhe96f7z.js +5 -0
  61. package/dist/{chunk-4bh0maq0.js.map → chunk-dhe96f7z.js.map} +1 -1
  62. package/dist/{chunk-3prm2e2t.js → chunk-epwaxctc.js} +2 -2
  63. package/dist/{chunk-3prm2e2t.js.map → chunk-epwaxctc.js.map} +1 -1
  64. package/dist/{chunk-pwchwgkc.js → chunk-eqtsfvvd.js} +2 -2
  65. package/dist/{chunk-pwchwgkc.js.map → chunk-eqtsfvvd.js.map} +1 -1
  66. package/dist/{chunk-ffpm75zs.js → chunk-et6thczw.js} +2 -2
  67. package/dist/{chunk-ffpm75zs.js.map → chunk-et6thczw.js.map} +1 -1
  68. package/dist/{chunk-3m10ve1n.js → chunk-f6e2t4vb.js} +3 -3
  69. package/dist/{chunk-3m10ve1n.js.map → chunk-f6e2t4vb.js.map} +1 -1
  70. package/dist/{chunk-6ak8jfhm.js → chunk-f6veb369.js} +2 -2
  71. package/dist/{chunk-6ak8jfhm.js.map → chunk-f6veb369.js.map} +1 -1
  72. package/dist/{chunk-5y9jk4h4.js → chunk-fq03tz45.js} +2 -2
  73. package/dist/{chunk-5y9jk4h4.js.map → chunk-fq03tz45.js.map} +1 -1
  74. package/dist/{chunk-0rs8a7re.js → chunk-fyxp4bn0.js} +2 -2
  75. package/dist/{chunk-0rs8a7re.js.map → chunk-fyxp4bn0.js.map} +1 -1
  76. package/dist/{chunk-vbfwvyb8.js → chunk-ghz3e8b7.js} +2 -2
  77. package/dist/{chunk-vbfwvyb8.js.map → chunk-ghz3e8b7.js.map} +1 -1
  78. package/dist/{chunk-qrczhr16.js → chunk-h24rv10h.js} +2 -2
  79. package/dist/{chunk-qrczhr16.js.map → chunk-h24rv10h.js.map} +1 -1
  80. package/dist/{chunk-g6byqmdq.js → chunk-j2nx0dtq.js} +2 -2
  81. package/dist/{chunk-6xh4e55j.js.map → chunk-j2nx0dtq.js.map} +1 -1
  82. package/dist/chunk-j9x86xsc.js +19 -0
  83. package/dist/{chunk-g8tgp3ja.js.map → chunk-j9x86xsc.js.map} +4 -5
  84. package/dist/{chunk-7jshf4ms.js → chunk-jrp35qrk.js} +1 -1
  85. package/dist/{chunk-sppjqk4n.js → chunk-jxhznqhe.js} +3 -3
  86. package/dist/{chunk-sppjqk4n.js.map → chunk-jxhznqhe.js.map} +1 -1
  87. package/dist/{chunk-5v2rx0rk.js → chunk-kzh8bkg5.js} +2 -2
  88. package/dist/{chunk-5v2rx0rk.js.map → chunk-kzh8bkg5.js.map} +1 -1
  89. package/dist/{chunk-dwkhs81y.js → chunk-m3z60v9d.js} +2 -2
  90. package/dist/{chunk-dwkhs81y.js.map → chunk-m3z60v9d.js.map} +1 -1
  91. package/dist/{chunk-d869bd41.js → chunk-mekw2ap4.js} +2 -2
  92. package/dist/{chunk-d869bd41.js.map → chunk-mekw2ap4.js.map} +1 -1
  93. package/dist/{chunk-7k7a6h9j.js → chunk-mf936r5e.js} +2 -2
  94. package/dist/{chunk-7k7a6h9j.js.map → chunk-mf936r5e.js.map} +1 -1
  95. package/dist/{chunk-sqd77ksy.js → chunk-mym0zxxp.js} +3 -3
  96. package/dist/{chunk-sqd77ksy.js.map → chunk-mym0zxxp.js.map} +1 -1
  97. package/dist/{chunk-qv2sdxbq.js → chunk-n4807648.js} +3 -3
  98. package/dist/{chunk-qv2sdxbq.js.map → chunk-n4807648.js.map} +1 -1
  99. package/dist/{chunk-mmfstb0z.js → chunk-nfhy3sp8.js} +2 -2
  100. package/dist/{chunk-mmfstb0z.js.map → chunk-nfhy3sp8.js.map} +1 -1
  101. package/dist/{chunk-nvbbgbdk.js → chunk-nqabsqty.js} +4 -4
  102. package/dist/{chunk-nvbbgbdk.js.map → chunk-nqabsqty.js.map} +1 -1
  103. package/dist/{chunk-6xh4e55j.js → chunk-p3mdtnvv.js} +2 -2
  104. package/dist/{chunk-g6byqmdq.js.map → chunk-p3mdtnvv.js.map} +1 -1
  105. package/dist/{chunk-y3hdh6q6.js → chunk-qj5ts9d5.js} +2 -2
  106. package/dist/{chunk-y3hdh6q6.js.map → chunk-qj5ts9d5.js.map} +1 -1
  107. package/dist/{chunk-aawtyngp.js → chunk-qpek76vn.js} +3 -3
  108. package/dist/{chunk-aawtyngp.js.map → chunk-qpek76vn.js.map} +1 -1
  109. package/dist/{chunk-vryvdzmk.js → chunk-qqev9e50.js} +2 -2
  110. package/dist/{chunk-vryvdzmk.js.map → chunk-qqev9e50.js.map} +1 -1
  111. package/dist/{chunk-241aehp3.js → chunk-qxda7pzm.js} +2 -2
  112. package/dist/{chunk-241aehp3.js.map → chunk-qxda7pzm.js.map} +1 -1
  113. package/dist/{chunk-za1ncjh0.js → chunk-qz2p0hv5.js} +2 -2
  114. package/dist/{chunk-za1ncjh0.js.map → chunk-qz2p0hv5.js.map} +1 -1
  115. package/dist/{chunk-7bmctvep.js → chunk-safv6h9x.js} +2 -2
  116. package/dist/{chunk-7bmctvep.js.map → chunk-safv6h9x.js.map} +1 -1
  117. package/dist/{chunk-pgn3yh5k.js → chunk-ssqxzcad.js} +3 -3
  118. package/dist/{chunk-pgn3yh5k.js.map → chunk-ssqxzcad.js.map} +1 -1
  119. package/dist/{chunk-3jpetvsj.js → chunk-tcefqn3d.js} +2 -2
  120. package/dist/{chunk-3jpetvsj.js.map → chunk-tcefqn3d.js.map} +1 -1
  121. package/dist/{chunk-t328955a.js → chunk-twhf7w1x.js} +2 -2
  122. package/dist/{chunk-t328955a.js.map → chunk-twhf7w1x.js.map} +1 -1
  123. package/dist/{chunk-knw4t9e6.js → chunk-w98tp5fa.js} +2 -2
  124. package/dist/{chunk-knw4t9e6.js.map → chunk-w98tp5fa.js.map} +1 -1
  125. package/dist/{chunk-z405ks42.js → chunk-x24davqq.js} +2 -2
  126. package/dist/{chunk-z405ks42.js.map → chunk-x24davqq.js.map} +1 -1
  127. package/dist/{chunk-dmvrx8nm.js → chunk-xtqxy492.js} +3 -3
  128. package/dist/{chunk-dmvrx8nm.js.map → chunk-xtqxy492.js.map} +1 -1
  129. package/dist/chunk-ykbehyp9.js +17 -0
  130. package/dist/{chunk-c37g1gzy.js.map → chunk-ykbehyp9.js.map} +4 -3
  131. package/dist/{chunk-0n8t329w.js → chunk-z16nyxb8.js} +2 -2
  132. package/dist/{chunk-0n8t329w.js.map → chunk-z16nyxb8.js.map} +1 -1
  133. package/dist/{chunk-fx02sqe2.js → chunk-z9ydpvc8.js} +3 -3
  134. package/dist/{chunk-fx02sqe2.js.map → chunk-z9ydpvc8.js.map} +1 -1
  135. package/dist/chunk-zyw42zny.js +217 -0
  136. package/dist/chunk-zyw42zny.js.map +16 -0
  137. package/dist/config/index.js +1 -1
  138. package/dist/console/run.js +2 -2
  139. package/dist/console/run.js.map +1 -1
  140. package/dist/container/index.js +2 -2
  141. package/dist/container/index.js.map +1 -1
  142. package/dist/database/index.js +2 -2
  143. package/dist/database/index.js.map +1 -1
  144. package/dist/email/index.js +2 -2
  145. package/dist/email/index.js.map +2 -2
  146. package/dist/facades/Lock.d.ts +24 -0
  147. package/dist/facades/Lock.d.ts.map +1 -0
  148. package/dist/facades/index.d.ts +1 -0
  149. package/dist/facades/index.d.ts.map +1 -1
  150. package/dist/facades/index.js +2 -2
  151. package/dist/facades/index.js.map +1 -1
  152. package/dist/foundation/index.js +2 -2
  153. package/dist/foundation/index.js.map +1 -1
  154. package/dist/http/index.js +2 -2
  155. package/dist/http/index.js.map +1 -1
  156. package/dist/i18n/dictionaryRuntime.js +2 -2
  157. package/dist/i18n/dictionaryRuntime.js.map +1 -1
  158. package/dist/i18n/index.js +2 -2
  159. package/dist/i18n/index.js.map +1 -1
  160. package/dist/kernel/index.js +2 -2
  161. package/dist/kernel/index.js.map +2 -2
  162. package/dist/orm/index.js +2 -2
  163. package/dist/orm/index.js.map +2 -2
  164. package/dist/server/index.js +2 -2
  165. package/dist/server/index.js.map +1 -1
  166. package/dist/services/cron/CronJob.d.ts +37 -1
  167. package/dist/services/cron/CronJob.d.ts.map +1 -1
  168. package/dist/services/cron/ScheduleServiceProvider.d.ts.map +1 -1
  169. package/dist/services/cron/Scheduler.d.ts +19 -1
  170. package/dist/services/cron/Scheduler.d.ts.map +1 -1
  171. package/dist/services/index.d.ts +5 -1
  172. package/dist/services/index.d.ts.map +1 -1
  173. package/dist/services/index.js +3 -3
  174. package/dist/services/index.js.map +2 -2
  175. package/dist/services/lock/DatabaseLockStore.d.ts +84 -0
  176. package/dist/services/lock/DatabaseLockStore.d.ts.map +1 -0
  177. package/dist/services/lock/LockManager.d.ts +93 -0
  178. package/dist/services/lock/LockManager.d.ts.map +1 -0
  179. package/dist/services/lock/LockStore.d.ts +87 -0
  180. package/dist/services/lock/LockStore.d.ts.map +1 -0
  181. package/dist/services/lock/MemoryLockStore.d.ts +28 -0
  182. package/dist/services/lock/MemoryLockStore.d.ts.map +1 -0
  183. package/dist/services/lock/lockStoreContract.d.ts +13 -0
  184. package/dist/services/lock/lockStoreContract.d.ts.map +1 -0
  185. package/dist/services/queue/DatabaseQueueDriver.d.ts +7 -0
  186. package/dist/services/queue/DatabaseQueueDriver.d.ts.map +1 -1
  187. package/dist/services/queue/Job.d.ts +66 -0
  188. package/dist/services/queue/Job.d.ts.map +1 -1
  189. package/dist/services/queue/QueueDriver.d.ts +9 -0
  190. package/dist/services/queue/QueueDriver.d.ts.map +1 -1
  191. package/dist/services/queue/QueueManager.d.ts +46 -0
  192. package/dist/services/queue/QueueManager.d.ts.map +1 -1
  193. package/dist/services/queue/QueueServiceProvider.d.ts.map +1 -1
  194. package/dist/services/queue/config.d.ts +13 -0
  195. package/dist/services/queue/config.d.ts.map +1 -1
  196. package/dist/services/queue/work.js +2 -2
  197. package/dist/services/queue/work.js.map +2 -2
  198. package/dist/support/index.js +2 -2
  199. package/dist/support/index.js.map +2 -2
  200. package/package.json +1 -1
  201. package/dist/chunk-4bh0maq0.js +0 -5
  202. package/dist/chunk-c37g1gzy.js +0 -17
  203. package/dist/chunk-cyze295m.js +0 -5
  204. package/dist/chunk-g8tgp3ja.js +0 -19
  205. package/dist/chunk-rgcfqq21.js +0 -5
  206. package/dist/chunk-rgcfqq21.js.map +0 -10
  207. package/dist/chunk-svzdv2rf.js +0 -5
  208. package/dist/chunk-t4x7q9x8.js +0 -142
  209. package/dist/chunk-t4x7q9x8.js.map +0 -13
  210. /package/dist/{chunk-dve6ryfd.js.map → chunk-2yyr5tdc.js.map} +0 -0
  211. /package/dist/{chunk-aq1hjk00.js.map → chunk-3fjkbcra.js.map} +0 -0
  212. /package/dist/{chunk-7jshf4ms.js.map → chunk-jrp35qrk.js.map} +0 -0
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../kernel/context.ts"],
4
- "sourcesContent": [
5
- "import { AsyncLocalStorage } from \"async_hooks\";\n\n/**\n * The kernel's AsyncLocalStorage. It holds the Application, and nothing else —\n * `Kernel.run()` enters it once per request or cron tick,\n * and `foundation/app.ts` reads it to resolve services.\n *\n * \"The framework's only AsyncLocalStorage\" is a claim that used to sit here. It\n * was already not true — the *kernel's* is the only one, but `http/` and now\n * the ORM each keep their own. Three in the shipped framework (a fourth\n * sits in `rfc/workflow.ts`, an unreferenced design sketch that is outside\n * `package.json`'s exports and that nothing imports):\n *\n * kernel kernel/context.ts the Application\n * request http/requestContext.ts req, user, cookies, locale\n * orm orm/context.ts an open transaction\n *\n * The pattern is one store per scope with one owner, entered by that owner and\n * read through its own accessor — `app()`, `RequestContext.getStore()`,\n * `currentTransaction()`. What matters is that this one holds the Application\n * and nothing else, because `foundation/app.ts` reads it unconditionally.\n *\n * The ORM's, added in iteration 5, holds an open transaction handle and its\n * nesting depth. It is deliberately separate rather than merged into this one:\n *\n * - A transaction scope nests *inside* an Application scope without replacing\n * it. Re-entering this store with a `{ app, tx }` wrapper would mean every\n * reader of it — `app()` above all — has to handle two shapes forever, and a\n * miss there is not a transaction bug, it is a service resolving off the\n * wrong Application.\n * - The alternative of hanging the handle off the Application is worse than a\n * style question: one Application serves every concurrent request, so two\n * overlapping transactions would overwrite each other's handle and statements\n * would land in the wrong one. That is silent data corruption.\n *\n * The cost is honest: one more store on the hot path of every query. The ORM's\n * is shallow — one small object, entered only inside `Model.transaction` or\n * `DB.transaction`, never per request — so a query outside a transaction pays\n * one `getStore()` returning undefined.\n *\n * If another is ever proposed, this is the note to read first: the bar is that\n * the state is genuinely per-async-scope *and* cannot live in an existing store\n * without making that store's readers ambiguous.\n */\nexport const kernelContext = new AsyncLocalStorage();\n"
6
- ],
7
- "mappings": ";AAAA,4BAAS,oBA4CF,IAAM,EAAgB,IAAI",
8
- "debugId": "A13B6F4D0664ED5F64756E2164756E21",
9
- "names": []
10
- }
@@ -1,5 +0,0 @@
1
- // @bun
2
- import{zb as qZ}from"./chunk-wc46df4w.js";import{Lb as m,Nb as M,Pb as XZ,Qb as e,Rb as HZ,Tb as KZ}from"./chunk-g8tgp3ja.js";import{Wb as o,Xb as n,Yb as J,Zb as t,_b as r,ac as a}from"./chunk-t4x7q9x8.js";import{dc as K}from"./chunk-p0px3ym0.js";import{Ad as g,Bd as v,Cc as JZ,Yc as WZ,Zc as c,bd as D,jd as d,kd as w,od as l,rc as IZ,vc as i,vd as T,zd as F}from"./chunk-c37g1gzy.js";import{Jd as GZ,Kd as h}from"./chunk-sppjqk4n.js";import{af as f,pf as u,rf as y,sf as k,tf as S}from"./chunk-sqd77ksy.js";import{Hf as zZ}from"./chunk-qv2sdxbq.js";import{pg as U}from"./chunk-6qyc4e22.js";import{vg as p}from"./chunk-fx02sqe2.js";import{Ag as j}from"./chunk-cyze295m.js";import{Bg as $Z}from"./chunk-rgcfqq21.js";async function LZ(Z){try{if(!await Z.shouldRun.call(Z))return}catch($){console.error(`Error evaluating shouldRun for cron job ${Z.name}:`,$);return}try{await Z.onTick.call(Z)}catch($){console.error(`Error executing cron job ${Z.name}:`,$)}try{await Z.callback.call(Z)}catch($){console.error(`Error in cron job ${Z.name}:`,$)}try{await Z.onComplete.call(Z)}catch($){console.error(`Error completing cron job ${Z.name}:`,$)}}class L{static token="scheduler";handles=[];resolved;inFlight=new Map;stopped=!1;constructor(Z){this.resolved=Z.jobs}get jobs(){return[...this.resolved]}useJobs(Z){this.resolved=Z}start(Z=($)=>$()){if(this.stopped=!1,this.resolved.length===0)return;let $=globalThis.__gemiCronJobs??=new Map,H=new Map;for(let W of this.resolved){let G=new W;if(!G.name){console.error(`Cron job must have a name \u2014 ${W.name||"an anonymous class"} has none, so it is not scheduled. If this is a base class that other jobs extend, move it out of the discovered directory: \`abstract\` does not survive to runtime, so a class under app/cron is indistinguishable from a job.`);continue}if(!G.cron){console.error(`Cron job must have an expression. Job: ${G.name}`);continue}let V=H.get(G.name);if(V!==void 0){console.error(`Two cron jobs claim the name "${G.name}" \u2014 ${V} is scheduled and ${W.name||"an anonymous class"} is not. A name is the scheduler's key, so only one job can hold it. Rename one.`);continue}H.set(G.name,W.name||"an anonymous class"),$.get(G.name)?.stop();let X=Bun.cron(G.cron,()=>{if(this.stopped)return;return this.track(G.name,()=>Z(()=>LZ(G)))});$.set(G.name,X),this.handles.push(X)}}stop(){this.stopped=!0;for(let Z of this.handles)Z.stop();this.handles=[]}async drain(Z=1/0){this.stop();let $,H=new Promise((W)=>{if(Z!==1/0)$=setTimeout(W,Z)});return await Promise.race([Promise.allSettled([...this.inFlight.values()].map((W)=>W.done)),H]),clearTimeout($),{unfinished:[...this.inFlight.values()].map((W)=>({...W.tick}))}}get running(){return this.inFlight.size}track(Z,$){let H={},W=Promise.resolve().then($).finally(()=>this.inFlight.delete(H));return this.inFlight.set(H,{tick:{name:Z,startedAt:new Date},done:W}),W}}import{isMainThread as QZ}from"worker_threads";var NZ=100,FZ=(Z)=>Math.min(NZ,Math.floor(Z/10));class xZ extends K{register(){this.app.singleton(J,()=>new J(this.app.config.get("queue",{}),{application:this.app}))}async boot(){let Z=this.app.config.get("queue",{});if(Z.jobs!==void 0)return;let{jobsDir:$}=U(o(),Z);this.app.make(J).useJobs(await e($))}async shutdown(Z){if(!this.app.resolved(J))return;let $=this.app.make(J);if($.running>0)console.log(`[gemi] Shutting down: waiting for ${$.running} running queued job(s).`);let H=Z?.timeoutMs,{unfinished:W}=await $.drain(H===void 0?1/0:Math.max(0,H-FZ(H)));if(W.length>0)return console.error("[gemi] Queued jobs still running at shutdown: "+W.map((G)=>`${G.name} (${G.id})`).join(", ")),{abandoned:!0}}}function z3(Z){if(EZ(Z),Bun.env.NODE_ENV!=="production")return TZ(Z);if(ZZ(Z))return;if(!t())return;if(!Z.bound(J))return;Z.make(J).start()}function ZZ(Z){let $=Z.config.get("queue",{}).driver??"memory";return $==="memory"||$ instanceof n}function SZ(Z){if(ZZ(Z))return!0;if(typeof Z.config.get("queue",{}).driver!=="function"||!Z.bound(J))return!1;try{return!Z.make(J).durable}catch{return!1}}function EZ(Z){let $=process.env.GEMI_QUEUE_CLAIM;if($===void 0||a())return;if(process.env.ROOT_DIR===void 0||!QZ)return;if(r()){if(!SZ(Z))return;console.warn('[gemi] GEMI_QUEUE_CLAIM=off is ignored: the memory queue driver keeps jobs in the process that dispatched them, so no worker could run them. This server runs its own jobs. Use a driver other processes can read, such as "database", to hand jobs to `gemi queue:work`.')}else if($.trim().toLowerCase()!=="on")console.warn(`[gemi] GEMI_QUEUE_CLAIM="${$}" is not "off" or "on", so this server claims jobs. Set it to "off" to leave them to \`gemi queue:work\`.`)}function TZ(Z){let $=globalThis.__gemiDevQueue;if(!$)return;if(globalThis.__gemiDevQueue=void 0,$.stop(),Z.bound(J))Z.make(J).start()}class RZ extends K{register(){this.app.singleton(v,()=>new v(this.app.config.get("auth",{})))}}var kZ=100,DZ=(Z)=>Math.min(kZ,Math.floor(Z/10));class jZ extends K{register(){this.app.singleton(L,()=>new L(U(m(),this.app.config.get("schedule",{}))))}async boot(){let Z=this.app.make(L),$=this.app.config.get("schedule",{});if($.jobs===void 0){let{jobsDir:W}=U(m(),$);Z.useJobs(await HZ(W))}if(process.env.GEMI_NO_SCHEDULE==="1")return;let H=this.app;Z.start((W)=>$Z.run(H,W))}async shutdown(Z){if(!this.app.resolved(L))return;let $=this.app.make(L);if($.running>0)console.log(`[gemi] Shutting down: waiting for ${$.running} running cron tick(s).`);let H=Z?.timeoutMs,{unfinished:W}=await $.drain(H===void 0?1/0:Math.max(0,H-DZ(H)));if(W.length>0)return console.error("[gemi] Cron jobs still running at shutdown: "+W.map((G)=>`${G.name} (started ${G.startedAt.toISOString()})`).join(", ")),{abandoned:!0}}}class N extends WZ{modelName;constructor(Z="FeatureFlag"){super();this.modelName=Z}get available(){return GZ(this.modelName)}async load(){if(!this.available)throw new c(this.modelName);let Z=h(this.modelName);return await Z.asSystem(()=>Z.findMany())}async write(Z,$,H){if(!this.available)throw new c(this.modelName);let W=h(this.modelName),G=this.hasActorColumn(W),V={active:$};if(G)V.updatedBy=H.actor;return await W.asSystem(()=>W.upsert({where:{key:Z},create:{key:Z,...V},update:V})),{actorRecorded:G}}hasActorColumn(Z){let $=Z?.$schema?.fields?.updatedBy;return $!==void 0&&$.type==="String"}}function x3(Z){return Z}function VZ(){return{features:void 0,enabled:!0,source:new N,model:"FeatureFlag",ttl:30,context:void 0,onEvaluate:void 0,maxClientFlags:200}}class MZ extends K{register(){this.app.singleton(D,()=>{let Z=this.app.config.get("features",{}),$=U(VZ(),Z);if(Z?.model!==void 0&&Z?.source===void 0)$.source=new N(Z.model);else if(Z?.model!==void 0&&Z.source instanceof N&&Z.source.modelName!==Z.model)k.warning(`app/config/features.ts sets \`model: "${Z.model}"\` and a source reading "${Z.source.modelName}". The source wins; drop \`model\` to silence this.`);return new D($,(H)=>k.warning(H))})}async boot(){let Z=this.app.make(D);if(!Z.config.enabled)return;try{await Z.refresh()}catch($){k.error(`Could not warm the feature flag cache at boot. ${$ instanceof Error?$.message:String($)}`)}}}class yZ extends K{register(){this.app.singleton(p,()=>new p(this.app.config.get("mail",{})))}}function wZ(Z){return`listener:${Z}`}function YZ(Z,$){fZ(Z);let{maxAttempts:H,backoff:W,worker:G}=$,V=Z.event.name,X=class extends XZ{maxAttempts=H;backoff=W;worker=G;async run(...Y){let[C,B]=bZ(Y),I=j(Q).rehydrate(C,B);await new Z().handle(I)}onFail(Y){console.error(`The listener ${Z.name} threw while handling ${V} off the queue. The queue will retry it up to ${H} times in total; nothing the dispatcher did is waiting on it.`,Y)}onDeadletter(Y){console.error(`The listener ${Z.name} failed every one of its ${H} attempts at ${V} and has been dead-lettered, so this side effect did not happen and will not be retried.`,Y)}};return Object.defineProperty(X,"name",{value:wZ(Z.name),writable:!0}),X}function fZ(Z){if(Object.getOwnPropertyDescriptor(Z,"name")?.writable)return;throw Error(`The listener ${Z.name} sets \`queued = true\` but does not declare \`static name\`, so it would be registered with the queue `+"under its class name \u2014 which a production build renames while the "+`payload that names it does not. Add: static name = "${Z.name}";`)}function bZ(Z){return typeof Z[0]==="string"?Z:Z[0]}class Q{static token="events";listeners={};events={};warnedFor=new Set;warnedAboutWaiting=new Set;config;constructor(Z={}){this.config=U(M(),Z),this.useListeners(this.config.listeners)}useListeners(Z){this.config.listeners=Z,this.listeners={},this.events={};let $=new Set;for(let H of Z){if(!H.event){console.error(`Listener ${H.name} does not declare \`static event\`, so there is no event name to register it under and its handle will never run. Add: static event = SomeEvent;`);continue}if($.has(H.name)){console.error(`Two event listeners are named "${H.name}" \u2014 the first is `+"registered and this one is not. A listener's name identifies it across the framework, so only one class can hold it. Rename one.");continue}$.add(H.name);let W=H.event.name,G=new H;this.events[W]??=H.event,(this.listeners[W]??=[]).push({listener:H,job:G.queued?YZ(H,G):null})}}get queuedListenerJobs(){return Object.values(this.listeners).flat().flatMap(({job:Z})=>Z?[Z]:[])}get registeredListeners(){return[...this.config.listeners]}dispatch(Z,$){if(this.deferToCommit(Z,$,!1))return;this.runListeners(Z,$)}async dispatchAndWait(Z,$){if(this.deferToCommit(Z,$,!0))return;return this.runListeners(Z,$)}deferToCommit(Z,$,H){let{name:W,afterCommit:G}=Z.constructor;if(!G)return!1;let V=zZ(()=>this.runListeners(Z,$));if(V&&H)this.warnNothingIsWaitedFor(W);return V}async runListeners(Z,$){let H=Z.constructor.name,W=this.listeners[H];if(!W?.length){this.warnNothingIsListening(H);return}for(let{listener:G,job:V}of W){if(V){this.enqueue(V,G,H,$);continue}try{await new G().handle(Z)}catch(X){console.error(`The listener ${G.name} threw while handling ${H}. The `+"listeners after it still ran \u2014 a listener's failure is its own.",X)}}}rehydrate(Z,$=[]){let H=this.events[Z];if(!H)throw Error(`Cannot rebuild the event "${Z}" that came off the queue: no registered listener binds to an event of that name, so there is no class to construct. A queued listener runs in an application that `+"discovered its own listeners \u2014 check that this process sees the "+`same app/listeners as the one that dispatched, and that the event declares \`static name = "${Z}"\`.`);return new H(...$)}enqueue(Z,$,H,W){if(W===void 0){console.error(`The listener ${$.name} is queued, but ${H} reached the dispatcher without its constructor arguments, so there is nothing for the queue to rebuild it from and the listener was skipped. Dispatch through ${H}.dispatch(...) or ${H}.dispatchAndWait(...), which carry them.`);return}let G=(V)=>console.error(`The listener ${$.name} could not be queued for ${H}, so it did not run and will not be retried. The listeners after it still ran.`,V);try{j(J).push(Z,JSON.stringify([H,W]),{reportFailure:!1}).catch(G)}catch(V){G(V)}}warnNothingIsWaitedFor(Z){if(Bun.env.NODE_ENV==="production")return;if(this.warnedAboutWaiting.has(Z))return;this.warnedAboutWaiting.add(Z),console.warn(`[gemi] ${Z}.dispatchAndWait() was called inside a transaction and ${Z} declares \`static afterCommit = true\`, so it resolved `+"immediately with no listener having run \u2014 they run when the "+"transaction commits. Await the transaction instead, or drop afterCommit if the caller needs the side effects first. Development only.")}warnNothingIsListening(Z){if(Bun.env.NODE_ENV==="production")return;if(this.warnedFor.has(Z))return;this.warnedFor.add(Z),console.warn(`[gemi] ${Z} was dispatched and nothing is listening for it. A listener registers under the name its event declares, so check that app/listeners holds a Listener with \`static event = ${Z}\` and that ${Z} declares \`static name = "${Z}"\`. Development only.`)}}class gZ extends K{register(){this.app.singleton(Q,()=>new Q(this.app.config.get("events",{})))}async boot(){let Z=this.app.make(Q),$=this.app.config.get("events",{});if($.listeners===void 0){let{listenersDir:G}=U(M(),$);Z.useListeners(await KZ(G))}let H=Z.queuedListenerJobs;if(H.length===0)return;let W=this.app.make(J);for(let G of H)W.registerJob(G)}}class vZ extends K{register(){this.app.singleton(u,()=>new u(this.app.config.get("filesystem",{})))}}class mZ extends K{register(){this.app.singleton(d,()=>new d(this.app.config.get("image",{})))}}class hZ extends K{register(){this.app.instance(l,new l)}}function U4(Z){return Z}function UZ(){return{maxFileSize:10485760,onLogFileClosed:()=>{},onLogCreated:()=>{}}}class cZ extends K{register(){this.app.singleton(y,()=>new y(U(UZ(),this.app.config.get("log",{}))))}async boot(){if(!process.env.ROOT_DIR)return;await this.app.make(y).boot()}}class pZ extends K{register(){this.app.singleton(w,()=>new w(U(IZ(),this.app.config.get("middleware",{}))))}boot(){this.app.make(w).assertGlobalMiddleware()}}function F4(Z){return Z}function CZ(){return{driver:new JZ,limit:1000,window:60}}class uZ extends K{register(){this.app.singleton(i,()=>new i(U(CZ(),this.app.config.get("ratelimiter",{}))))}}function D4(Z){return Z}function BZ(){return{url:process.env.REDIS_URL,options:void 0}}class dZ extends K{register(){this.app.singleton(S,()=>new S(U(BZ(),this.app.config.get("redis",{}))))}shutdown(){if(!this.app.resolved(S))return;this.app.make(S).close()}}class P extends qZ{status;constructor(Z,$){super(Z);this.status=$;this.name="McpToolError"}}var lZ=/^[A-Za-z0-9_-]{1,64}$/,iZ=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,PZ=4000,s=1e5,sZ=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i;class E{dispatcher;static token="router.mcp";plans=new Map;constructor(Z,$){this.dispatcher=$;for(let[H,W]of Object.entries(Z.routes??{}))this.plans.set(H,this.plan(H,W))}list(Z,$){return _Z(Z),this.descriptors($)}descriptors(Z){let $=[];for(let{descriptor:H}of this.plans.values()){if(Z?.names&&!Z.names.includes(H.name))continue;if(Z?.tags&&!H.tags.some((W)=>Z.tags.includes(W)))continue;$.push(H)}return $}async execute(Z,$,H,W){_Z(Z);let G=this.plans.get($);if(!G)throw Error(`McpRegistry: there is no tool named "${$}".`);let{descriptor:V}=G,X=V.inputSchema.safeParse(H);if(X.ok===!1)throw new P(`Invalid arguments for "${$}": ${X.errors.join(", ")}`);let Y=X.value,C=await this.fillPath(G,Y,Z.req),{path:B}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${C}`));if(B!==V.url)throw new P(`"${$}" has nothing at ${C}.`,404);let I={};for(let A of G.jsonKeys)if(Y[A]!==void 0)I[A]=Y[A];let _,O="";if(G.fileFields.length>0)_=await this.formData(G,Y,I,W);else if(V.method==="GET")O=rZ(I);else if(G.jsonKeys.length>0)_=I;let x;try{x=await this.dispatcher.dispatchAs(Z.req,V.method,O?`${C}?${O}`:C,_)}catch(A){throw console.error(`[gemi/mcp] Dispatching "${$}" failed:`,A),new P(`"${$}" failed on the server.`,500)}return await aZ($,x)}plan(Z,$){if(!lZ.test(Z))throw Error(`McpRouter: "${Z}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let H=`McpRouter: "${Z}" (${$?.method} ${$?.url})`;if(!($ instanceof Object)||$.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${Z}" is not a route. Declare it with this.fromApiRoute(...).`);let{method:W,url:G,meta:V}=$;if(G.startsWith("/__gemi__"))throw Error(`${H} is a framework route, not an app route.`);let X=this.dispatcher.flatRoutes[G]?.[W];if(!X)throw Error(`${H} names no route of this app. Expose a route by the path and verb it is mounted at.`);let Y=[...G.matchAll(iZ)].map(([,z,q])=>({name:z,modifier:q})),C=V.params??{};for(let z of Y)if(!(z.name in C))throw Error(`${H}: the path param "${z.name}" must be bound or declared "input".`);for(let[z,q]of Object.entries(C)){if(!Y.some((R)=>R.name===z))throw Error(`${H}: "${z}" in params is not a param of the url.`);OZ(H,`params.${z}`,q)}let B=[],I;if(V.input){if(I=V.input.toJSONSchema(),I.type!=="object"||!I.properties)throw Error(`${H}: input must be an s.object(...), the fields of the request body.`);B=Object.keys(I.properties)}let _=Object.entries(V.files??{}).map(([z,q])=>({name:z,binder:q}));for(let z of _)OZ(H,`files.${z.name}`,z.binder);if(_.length>0&&W==="GET")throw Error(`${H}: a GET has no body to carry a file.`);let O={},x=new Map;for(let z of Y){let q=C[z.name];if(q==="input"){let R=f.string().describe(`The ":${z.name}" segment of the url.`);O[z.name]=z.modifier==="?"||z.modifier==="*"?R.optional():R}else x.set(z.name,q)}for(let z of _)if(z.binder==="input")O[z.name]=f.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field.");let A=[...Object.keys(O),..._.map((z)=>z.name)];for(let z of new Set(A))if(B.includes(z))throw Error(`${H}: "${z}" is both an input field and a param or file, and the model can only send one.`);return{descriptor:Object.freeze({name:Z,description:V.description,method:W,url:G,inputSchema:nZ(V.input,I,f.object(O)),annotations:oZ(W),tags:Object.freeze([...V.tags??[]]),requiresApproval:V.requiresApproval===!0,...X.source?{source:X.source}:{}}),params:Y,paramBinders:x,fileFields:_,jsonKeys:B}}async fillPath(Z,$,H){let W=Z.descriptor.url;for(let G of Z.params){let V=Z.paramBinders.get(G.name),X;if(V){if(X=await this.bind(Z,`the param "${G.name}"`,()=>V(H)),X===void 0||X===null||X==="")throw console.error(`[gemi/mcp] The binder for "${G.name}" of "${Z.descriptor.name}" returned ${JSON.stringify(X)}.`),new P(`"${Z.descriptor.name}" failed on the server.`,500)}else X=$[G.name];let Y=`:${G.name}${G.modifier}`;if(X===void 0||X===null||X===""){if(G.modifier!=="?"&&G.modifier!=="*")throw new P(`"${G.name}" of "${Z.descriptor.name}" must not be empty.`);W=W.replace(`/${Y}`,"");continue}W=W.replace(Y,tZ(Z.descriptor.name,G,String(X)))}return W}async formData(Z,$,H,W){let G=Z.descriptor.name;if(!W)throw Error(`McpRegistry: "${G}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let V=new FormData;for(let[X,Y]of Object.entries(H))AZ(V,X,Y);for(let X of Z.fileFields){let Y=X.binder==="input"?$[X.name]:await this.bind(Z,`the file "${X.name}"`,()=>X.binder(W));if(typeof Y!=="string"||Y==="")throw new P(`"${G}" needs a file for "${X.name}", and this turn has none. Ask the user to attach one.`);V.append(X.name,await W.attachments.file(Y))}return V}async bind(Z,$,H){try{return await H()}catch(W){throw console.error(`[gemi/mcp] Binding ${$} of "${Z.descriptor.name}" failed:`,W),new P(`"${Z.descriptor.name}" failed on the server.`,500)}}}function _Z(Z){if(Z?.kind!=="local")throw Error("McpRegistry: only local callers are implemented. A remote caller needs a bearer-token resolver, which v1 does not have \u2014 and until it does there is no remote surface to call from.")}function OZ(Z,$,H){if(H!=="input"&&typeof H!=="function")throw Error(`${Z}: ${$} must be a function or "input".`)}function oZ(Z){switch(Z){case"GET":return Object.freeze({readOnlyHint:!0});case"DELETE":return Object.freeze({destructiveHint:!0});case"PUT":return Object.freeze({idempotentHint:!0});default:return Object.freeze({})}}function nZ(Z,$,H){let W=H.toJSONSchema(),G={...$??{},type:"object",properties:{...$?.properties??{},...W.properties},required:[...$?.required??[],...W.required??[]],additionalProperties:!1},V=(X)=>{if(typeof X!=="object"||X===null||Array.isArray(X))return{ok:!1,errors:["expected an object"]};let Y=[],C={};if(Z){let I=Z.safeParse(X);if(I.ok===!1)Y.push(...I.errors);else C={...I.value}}let B=H.safeParse(X);if(B.ok===!1)Y.push(...B.errors);else C={...C,...B.value};return Y.length>0?{ok:!1,errors:Y}:{ok:!0,value:C}};return{toJSONSchema:()=>G,safeParse:V,parse(X){let Y=V(X);if(Y.ok===!1)throw Error(Y.errors.join(", "));return Y.value}}}function tZ(Z,$,H){let W=$.modifier==="*"||$.modifier==="+"?H.split("/"):[H];if(W.some((G)=>G==="."||G===".."))throw new P(`"${H}" is not a valid value for "${$.name}" of "${Z}".`);return W.map(encodeURIComponent).join("/")}function AZ(Z,$,H){if(H===void 0||H===null)return;if(Array.isArray(H)){for(let W of H)AZ(Z,$,W);return}Z.append($,typeof H==="object"?JSON.stringify(H):String(H))}function rZ(Z){let $=new URLSearchParams,H=(W,G)=>{if(G===void 0||G===null)return;if(Array.isArray(G)){for(let V of G)H(W,V);return}$.append(W,typeof G==="object"?JSON.stringify(G):String(G))};for(let[W,G]of Object.entries(Z))H(W,G);return $.toString()}async function aZ(Z,$){if($.status>=200&&$.status<300){let W=$.headers.get("Content-Type");if(W&&!sZ.test(W)){let V=(await $.arrayBuffer()).byteLength;return`"${Z}" answered with ${V} bytes of ${W}, which is not shown.`}let G=await $.text();if(G==="")return null;if(G.length>s)return`${G.slice(0,s)}\u2026 [cut at ${s} of ${G.length} characters]`;try{return JSON.parse(G)}catch{return G}}let H=await $.text();if($.status>=400&&$.status<500){let W=H.length>PZ?`${H.slice(0,PZ)}\u2026`:H;throw new P(`"${Z}" was refused with ${$.status}${W?`: ${W}`:"."}`,$.status)}if($.status<500)console.error(`[gemi/mcp] "${Z}" answered ${$.status}${$.headers.get("Location")?` to ${$.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new P(`"${Z}" failed on the server.`,$.status)}function b(Z,$){let H=Z.get($);if(!H)throw Error(`Missing "${$}" configuration. Route dispatching needs a root router \u2014 declare it in "app/config/route.ts".`);return H}class eZ extends K{register(){this.app.singleton(F,()=>new F(b(this.app.config,"route.api"))),this.app.singleton(T,()=>new T(b(this.app.config,"route.view"))),this.app.singleton(g,()=>new g({api:this.app.make(F),view:this.app.make(T)},{api:b(this.app.config,"route.api"),view:b(this.app.config,"route.view"),domains:this.app.config.get("route.domains")})),this.app.singleton(E,()=>{let Z=this.app.config.get("route.mcp");if(!Z?.router)throw Error('No MCP router is configured. Declare one as `mcp: { router }` in "app/config/route.ts".');return new E(new Z.router,this.app.make(F))})}boot(){if(this.app.make(F),this.app.make(T),this.app.make(g),this.app.config.get("route.mcp"))this.app.make(E)}}
3
- export{L as Za,xZ as _a,z3 as $a,RZ as ab,jZ as bb,N as cb,x3 as db,VZ as eb,MZ as fb,yZ as gb,Q as hb,gZ as ib,vZ as jb,mZ as kb,hZ as lb,U4 as mb,UZ as nb,cZ as ob,pZ as pb,F4 as qb,CZ as rb,uZ as sb,D4 as tb,BZ as ub,dZ as vb,P as wb,E as xb,eZ as yb};
4
-
5
- //# debugId=D08A5EEDD7782BCE64756E2164756E21
@@ -1,142 +0,0 @@
1
- // @bun
2
- import{Df as W,Ef as S,Hf as A,If as U}from"./chunk-qv2sdxbq.js";import{eg as E}from"./chunk-vbfwvyb8.js";import{pg as I}from"./chunk-6qyc4e22.js";import{Bg as C}from"./chunk-rgcfqq21.js";import{Eg as N}from"./chunk-ffpm75zs.js";class K{table;sql;dialect;connection;busyTimeout;configured;constructor(z,G={}){let V=G.table??"gemi_jobs";if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(V))throw Error(`The queue table name "${V}" is not a plain identifier. Use letters, digits and underscores.`);let Y=G.busyTimeout??1000;if(!Number.isInteger(Y)||Y<0)throw Error(`The queue's busyTimeout must be a whole number of milliseconds, 0 or more; got ${Y}.`);this.table=V,this.sql=z.sql,this.dialect=z.dialect,this.connection=z.name,this.busyTimeout=Y}configure(){if(this.dialect!=="sqlite"||this.busyTimeout===0)return Promise.resolve();return this.configured??=(async()=>{let[z]=await this.sql.unsafe("PRAGMA busy_timeout");if(Number(z?.timeout??0)!==0)return;await this.sql.unsafe(`PRAGMA busy_timeout = ${this.busyTimeout}`)})().catch((z)=>{throw this.configured=void 0,z}),this.configured}async createTable(){await this.configure();for(let z of R(this.dialect,this.table))await this.sql.unsafe(z)}enqueue(z){let G=this.transaction(),V=this.insert(G??this.sql,z);if(G)U(V);return V}async insert(z,{name:G,args:V,delayMs:Y=0,id:Z=Bun.randomUUIDv7()}){await this.configure();let $=this.now(z);return await z`
3
- INSERT INTO ${this.name(z)}
4
- (id, name, payload, status, attempts, available_at, created_at, updated_at)
5
- VALUES
6
- (${Z}, ${G}, ${V}, 'pending', 0, ${$} + ${this.ms(z,Y)}, ${$}, ${$})
7
- `,Z}joinsTransaction(){return this.transaction()!==void 0}transaction(){if(this.dialect==="sqlite"||this.connection===void 0)return;let z=W();if(z===void 0||S()!==this.connection)return;return z}async claim(z,G){let V=Math.max(0,Math.floor(z));if(V===0)return[];return await this.configure(),(this.dialect==="mysql"||this.dialect==="mariadb"?await this.claimLocking(V,G):await this.claimUpdating(V,G)).map((Z)=>({order:Number(Z.available_at),job:{id:String(Z.id),name:String(Z.name),args:String(Z.payload),attempt:Number(Z.attempts),createdAt:Number(Z.created_at)}})).sort((Z,$)=>Z.order-$.order||(Z.job.id<$.job.id?-1:1)).map(({job:Z})=>Z)}async complete(z){await this.configure();let G=this.sql;await G`
8
- DELETE FROM ${this.name(G)}
9
- WHERE id = ${z.id} AND status = 'claimed' AND attempts = ${z.attempt}
10
- `}async fail(z,G){await this.configure();let V=this.sql,Y=this.now(V);if(G.retryInMs===null){await V`
11
- UPDATE ${this.name(V)}
12
- SET status = 'dead', lease_expires_at = NULL, last_error = ${G.error},
13
- updated_at = ${Y}
14
- WHERE id = ${z.id} AND status = 'claimed' AND attempts = ${z.attempt}
15
- `;return}await V`
16
- UPDATE ${this.name(V)}
17
- SET status = 'pending', available_at = ${Y} + ${this.ms(V,G.retryInMs)},
18
- lease_expires_at = NULL, last_error = ${G.error}, updated_at = ${Y}
19
- WHERE id = ${z.id} AND status = 'claimed' AND attempts = ${z.attempt}
20
- `}async release(z,G){let V=this.sql,Y=this.now(V);await V`
21
- UPDATE ${this.name(V)}
22
- SET status = 'pending', attempts = attempts - 1,
23
- available_at = ${Y} + ${this.ms(V,G.retryInMs)},
24
- lease_expires_at = NULL, updated_at = ${Y}
25
- WHERE id = ${z.id} AND status = 'claimed' AND attempts = ${z.attempt}
26
- `}async heartbeat(z,G){await this.configure();let V=this.sql;await Promise.all(z.map((Y)=>{let Z=this.now(V);return V`
27
- UPDATE ${this.name(V)}
28
- SET lease_expires_at = ${Z} + ${this.ms(V,G.visibilityTimeoutMs)},
29
- updated_at = ${Z}
30
- WHERE id = ${Y.id} AND status = 'claimed' AND attempts = ${Y.attempt}
31
- `}))}async retryDead(z){await this.configure();let G=this.sql,V=this.now(G),Y=await G`
32
- UPDATE ${this.name(G)}
33
- SET status = 'pending', attempts = 0, available_at = ${V}, lease_expires_at = NULL,
34
- updated_at = ${V}
35
- WHERE id = ${z} AND status = 'dead'
36
- `;return J(Y)>0}async prune(z){await this.configure();let G=this.sql,V=await G`
37
- DELETE FROM ${this.name(G)}
38
- WHERE status = 'dead' AND updated_at <= ${this.now(G)} - ${this.ms(G,z)}
39
- `;return J(V)}async claimUpdating(z,G){let V=this.sql,Y=this.now(V),Z=this.ms(V,G.visibilityTimeoutMs),$=this.name(V);if(this.dialect==="postgres")return await V`
40
- WITH next AS (
41
- SELECT id FROM ${$}
42
- WHERE ${this.claimable(V,G)}
43
- ORDER BY available_at, id
44
- LIMIT ${z}
45
- FOR UPDATE SKIP LOCKED
46
- )
47
- UPDATE ${$} AS job
48
- SET status = 'claimed', attempts = job.attempts + 1, claimed_at = ${Y},
49
- lease_expires_at = ${Y} + ${Z}, updated_at = ${Y}
50
- FROM next
51
- WHERE job.id = next.id
52
- RETURNING job.id, job.name, job.payload, job.attempts, job.available_at, job.created_at
53
- `;return await V`
54
- UPDATE ${$}
55
- SET status = 'claimed', attempts = attempts + 1, claimed_at = ${Y},
56
- lease_expires_at = ${Y} + ${Z}, updated_at = ${Y}
57
- WHERE id IN (
58
- SELECT id FROM ${$}
59
- WHERE ${this.claimable(V,G)}
60
- ORDER BY available_at, id
61
- LIMIT ${z}
62
- )
63
- RETURNING id, name, payload, attempts, available_at, created_at
64
- `}async claimLocking(z,G){let V=await this.sql.reserve(),[Y]=await V`
65
- SELECT @@transaction_isolation AS level
66
- `;await V`SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED`;try{return await this.claimLockingOn(V,z,G)}finally{let Z=Y?.level?.replaceAll("-"," ");if(Z&&/^[A-Z ]+$/.test(Z))await V.unsafe(`SET SESSION TRANSACTION ISOLATION LEVEL ${Z}`);V.release()}}async claimLockingOn(z,G,V){return await z.begin(async(Y)=>{let Z=this.name(Y),$=Y.unsafe("id, name, payload, attempts, available_at, created_at"),B=await Y`
67
- SELECT ${$} FROM ${Z}
68
- WHERE status = 'pending' AND available_at <= ${this.now(Y)}
69
- AND ${this.runnable(Y,V,"available_at")}
70
- ORDER BY available_at, id
71
- LIMIT ${G}
72
- FOR UPDATE SKIP LOCKED
73
- `;if(B.length<G){let H=await Y`
74
- SELECT ${$} FROM ${Z}
75
- WHERE status = 'claimed' AND lease_expires_at <= ${this.now(Y)}
76
- AND ${this.runnable(Y,V,"lease_expires_at")}
77
- ORDER BY lease_expires_at, id
78
- LIMIT ${G-B.length}
79
- FOR UPDATE SKIP LOCKED
80
- `;B.push(...H)}for(let H of B){let F=this.now(Y);await Y`
81
- UPDATE ${Z}
82
- SET status = 'claimed', attempts = attempts + 1, claimed_at = ${F},
83
- lease_expires_at = ${F} + ${this.ms(Y,V.visibilityTimeoutMs)},
84
- updated_at = ${F}
85
- WHERE id = ${H.id}
86
- `}return B.map((H)=>({...H,attempts:Number(H.attempts)+1}))})}claimable(z,G){let V=this.now(z);return z`(
87
- (status = 'pending' AND available_at <= ${V}
88
- AND ${this.runnable(z,G,"available_at")})
89
- OR (status = 'claimed' AND lease_expires_at <= ${V}
90
- AND ${this.runnable(z,G,"lease_expires_at")})
91
- )`}runnable(z,G,V){let Y=G.registered;if(!Y)return z`1 = 1`;let Z=Y.names.length?z`name IN (${Y.names.map((B)=>z`${B}`).reduce((B,H)=>z`${B}, ${H}`)})`:z`1 = 0`;if(!Number.isFinite(Y.graceMs))return Z;let $=z.unsafe(V);return z`(${Z} OR ${$} <= ${this.now(z)} - ${this.ms(z,Y.graceMs)})`}name(z){let G=this.dialect==="mysql"||this.dialect==="mariadb";return z.unsafe(G?`\`${this.table}\``:`"${this.table}"`)}now(z){switch(this.dialect){case"postgres":return z`floor(extract(epoch from statement_timestamp()) * 1000)::bigint`;case"mysql":case"mariadb":return z`(TIMESTAMPDIFF(MICROSECOND, '1970-01-01 00:00:00', UTC_TIMESTAMP(6)) DIV 1000)`;case"sqlite":return z`CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)`}}ms(z,G){let V=Math.max(0,Math.round(G));return this.dialect==="mysql"||this.dialect==="mariadb"?z`CAST(${V} AS SIGNED)`:z`CAST(${V} AS BIGINT)`}}function J(z){let G=z;return Math.max(Number(G.count??0),Number(G.affectedRows??0))}function R(z,G){if(z==="mysql"||z==="mariadb")return[`CREATE TABLE IF NOT EXISTS \`${G}\` (
92
- \`id\` VARCHAR(191) NOT NULL,
93
- \`name\` VARCHAR(191) NOT NULL,
94
- \`payload\` LONGTEXT NOT NULL,
95
- \`status\` VARCHAR(191) NOT NULL,
96
- \`attempts\` INTEGER NOT NULL DEFAULT 0,
97
- \`available_at\` BIGINT NOT NULL,
98
- \`claimed_at\` BIGINT NULL,
99
- \`lease_expires_at\` BIGINT NULL,
100
- \`last_error\` LONGTEXT NULL,
101
- \`created_at\` BIGINT NOT NULL,
102
- \`updated_at\` BIGINT NOT NULL,
103
-
104
- INDEX \`${G}_status_available_at_idx\`(\`status\`, \`available_at\`),
105
- INDEX \`${G}_status_lease_expires_at_idx\`(\`status\`, \`lease_expires_at\`),
106
- PRIMARY KEY (\`id\`)
107
- ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`];let V=z==="sqlite"?'"id" TEXT NOT NULL PRIMARY KEY,':'"id" TEXT NOT NULL,',Y=z==="sqlite"?"":`,
108
-
109
- CONSTRAINT "${G}_pkey" PRIMARY KEY ("id")`;return[`CREATE TABLE IF NOT EXISTS "${G}" (
110
- ${V}
111
- "name" TEXT NOT NULL,
112
- "payload" TEXT NOT NULL,
113
- "status" TEXT NOT NULL,
114
- "attempts" INTEGER NOT NULL DEFAULT 0,
115
- "available_at" BIGINT NOT NULL,
116
- "claimed_at" BIGINT,
117
- "lease_expires_at" BIGINT,
118
- "last_error" TEXT,
119
- "created_at" BIGINT NOT NULL,
120
- "updated_at" BIGINT NOT NULL${Y}
121
- )`,`CREATE INDEX IF NOT EXISTS "${G}_status_available_at_idx" ON "${G}"("status", "available_at")`,`CREATE INDEX IF NOT EXISTS "${G}_status_lease_expires_at_idx" ON "${G}"("status", "lease_expires_at")`]}function j(z){return z}function _(){return{jobs:[],jobsDir:"app/jobs",concurrency:1,driver:"memory",visibilityTimeout:300000,pollInterval:1000,unknownJobGrace:3600000}}class X{entries=new Map;listeners=new Set;timer;timerAt=1/0;seq=0;get waiting(){let z=0;for(let G of this.entries.values())if(!G.leased)z++;return z}get leased(){return this.entries.size-this.waiting}async enqueue({name:z,args:G,delayMs:V=0,id:Y=crypto.randomUUID()}){let Z=Date.now();return this.entries.set(Y,{job:{id:Y,name:z,args:G,attempt:0,createdAt:Z},until:Z+Math.max(0,V),leased:!1,seq:this.seq++}),this.changed(),Y}async claim(z,G){let V=Date.now(),Y=[...this.entries.values()].filter((Z)=>Z.until<=V).sort((Z,$)=>Z.until-$.until||Z.seq-$.seq).slice(0,Math.max(0,z));for(let Z of Y)Z.job={...Z.job,attempt:Z.job.attempt+1},Z.leased=!0,Z.until=V+G.visibilityTimeoutMs;if(Y.length>0)this.schedule();return Y.map((Z)=>({...Z.job}))}async complete(z){if(!this.current(z))return;this.entries.delete(z.id),this.schedule()}async fail(z,G){let V=this.current(z);if(!V)return;if(G.retryInMs===null){this.entries.delete(z.id),this.schedule();return}V.leased=!1,V.until=Date.now()+Math.max(0,G.retryInMs),V.seq=this.seq++,this.changed()}async release(z,G){let V=this.current(z);if(!V)return;V.job={...V.job,attempt:V.job.attempt-1},V.leased=!1,V.until=Date.now()+Math.max(0,G.retryInMs),V.seq=this.seq++,this.changed()}async heartbeat(z,G){let V=Date.now()+G.visibilityTimeoutMs;for(let Y of z){let Z=this.current(Y);if(Z)Z.until=V}this.schedule()}subscribe(z){return this.listeners.add(z),()=>void this.listeners.delete(z)}current(z){let G=this.entries.get(z.id);if(!G||!G.leased||G.job.attempt!==z.attempt)return;return G}changed(){this.schedule();let z=Date.now();for(let G of this.entries.values())if(G.until<=z){this.wake();return}}wake(){for(let z of[...this.listeners])z()}schedule(){let z=Date.now(),G=1/0;for(let V of this.entries.values())if(V.until>z&&V.until<G)G=V.until;if(G===this.timerAt)return;if(clearTimeout(this.timer),this.timer=void 0,this.timerAt=G,G===1/0)return;this.timer=setTimeout(()=>{this.timer=void 0,this.timerAt=1/0,this.changed()},G-z),this.timer.unref?.()}}import{AsyncResource as P}from"async_hooks";import{isMainThread as Q}from"worker_threads";function M(){let z=process.env.APP_DIR,G=process.env.ROOT_DIR,V=Bun.env.NODE_ENV==="production"?`${G}/dist/server/bootstrap.mjs`:`${z}/bootstrap.ts`,Y=new File([`
122
- import { app } from "${V}"
123
- self.onmessage = async (event) => {
124
- const clone = app.clone()
125
- let result = null;
126
- let error = null;
127
- try {
128
- result = await clone.dispatchJob(event.data.jobName, event.data.args)
129
- } catch (err) {
130
- error = err
131
- }
132
- clone.destroy()
133
- if(error) {
134
- self.postMessage({error});
135
- } else {
136
- self.postMessage({result});
137
- }
138
- };
139
- `],"worker.ts"),Z=URL.createObjectURL(Y);return new Worker(Z)}async function T(z,G){let V=M();return V.postMessage({jobName:z,args:G}),await new Promise((Y,Z)=>{V.onmessage=($)=>{let B=$.data;if("error"in B)Z(B.error);else Y(B.result)}})}var D=new P("gemi.queue"),k=60000;class x{static token="queue";jobs={};config;driver;application;inFlight=new Map;state="idle";looping=!1;claiming;woken=!1;claimFailures=0;resume;unsubscribe;heartbeatTimer;releasedNames=new Set;constructor(z={},G={}){this.config=I(_(),z),this.application=G.application,this.driver=q(this.config.driver,this.application),this.useJobs(this.config.jobs)}useJobs(z){this.config.jobs=z,this.jobs={};for(let G of z)this.claim(G)}registerJob(z){this.config.jobs=[...this.config.jobs,z],this.claim(z)}claim(z){if(this.jobs[z.name]){console.error(`Two queued jobs are named "${z.name}" \u2014 the first is registered `+"and this one is not, so dispatching either would have run one of them. A class name is the queue's key, so only one job can hold it. Rename one.");return}this.jobs[z.name]=z}get registeredJobs(){return[...this.config.jobs]}dispatchJob(z,G){if(this.jobs[z])return new this.jobs[z]().run(JSON.parse(G))}push(z,G,V={}){let Y=this.driver.joinsTransaction?.()===!0;if(!Y){let Z=Bun.randomUUIDv7();if(A(()=>this.record(z,G,{id:Z,reportFailure:!0,committed:!0}).then(()=>{},()=>{})))return Promise.resolve(Z)}return this.record(z,G,{reportFailure:V.reportFailure,joins:Y})}record(z,G,V){let Y=this.driver.enqueue({name:z.name,args:G,...V.id===void 0?{}:{id:V.id}});this.startIfIdle();let Z=()=>!this.driver.subscribe&&this.state==="running"&&this.wake(),$=V.joins===!0&&A(Z);return Y.then(()=>$||Z(),(B)=>{if(V.reportFailure===!1)return;console.error(`[gemi] The queue driver could not record ${z.name}`+(V.committed?", which was held until its transaction committed. The transaction stays committed; the job did not run and will not be retried.":", so it did not run and will not be retried."),B)}),Y}async retryDead(z){if(!this.driver.retryDead)throw Error("This queue's driver keeps nothing of a dead-lettered job, so there is nothing to retry. The database driver keeps them.");let G=await this.driver.retryDead(z);if(G)this.claimSoon();return G}startIfIdle(){if(this.state==="idle"&&(!this.durable||this.mayClaimHere()))this.start()}claimSoon(){if(this.startIfIdle(),!this.driver.subscribe&&this.state==="running")this.wake()}start(){if(this.state==="running")return;if(this.state="running",this.durable&&this.application&&this.mayClaimHere()&&!v())globalThis.__gemiDevQueue=this;if(this.unsubscribe=this.driver.subscribe?.(()=>this.wake()),this.looping)return this.wake();D.runInAsyncScope(()=>void this.loop())}async drain(z=1/0){if(this.state="stopped",globalThis.__gemiDevQueue===this)globalThis.__gemiDevQueue=void 0;this.unsubscribe?.(),this.unsubscribe=void 0,this.wake();let G,V=new Promise((Z)=>{if(z!==1/0)G=setTimeout(Z,z)}),Y=(async()=>{await this.claiming?.catch(()=>{}),await Promise.allSettled([...this.inFlight.values()].map((Z)=>Z.done))})();return await Promise.race([Y,V]),clearTimeout(G),{unfinished:[...this.inFlight.values()].map((Z)=>({...Z.job}))}}stop(){return this.drain(0)}get running(){return this.inFlight.size}async loop(){this.looping=!0;try{await this.claimWhileRunning()}finally{this.looping=!1}}async claimWhileRunning(){while(this.state==="running"){let z=this.durable&&N()?0:this.config.concurrency-this.inFlight.size;if(z>0){this.woken=!1;let G=this.driver.claim(z,{...this.lease(),registered:{names:Object.keys(this.jobs),graceMs:this.config.unknownJobGrace}});this.claiming=G;let V=[];try{V=await G,this.claimFailures=0}catch(Y){this.claimFailures++,console.error(`The queue could not claim jobs from its driver; it will try again in ${this.retryDelay()}ms.`,Y)}finally{this.claiming=void 0}for(let Y of V)this.execute(Y);if(V.length===z)continue;if(this.woken&&this.inFlight.size<this.config.concurrency)continue}await this.sleep()}}sleep(){return new Promise((z)=>{let G,V=this.claimFailures>0?this.retryDelay():this.driver.subscribe?void 0:this.config.pollInterval;if(V!==void 0)G=setTimeout(()=>this.wake(),V),G.unref?.();this.resume=()=>{clearTimeout(G),z()}})}retryDelay(){let z=Math.min(Math.max(0,this.claimFailures-1),30);return Math.min(this.config.pollInterval*2**z,k)}wake(){this.woken=!0;let z=this.resume;this.resume=void 0,z?.()}get durable(){return!(this.driver instanceof X)}mayClaimHere(){return!this.application||g()}lease(){return{visibilityTimeoutMs:this.config.visibilityTimeout}}execute(z){let G=`${z.id}:${z.attempt}`,V=()=>this.run(z),Y=(this.application?C.run(this.application,V):V()).catch((Z)=>{console.error(`The queue could not record the outcome of ${z.name}.`,Z)}).finally(()=>{this.inFlight.delete(G),this.wake()});this.inFlight.set(G,{job:z,done:Y}),this.heartbeat()}heartbeat(){let z=this.driver;if(!z.heartbeat||this.heartbeatTimer)return;this.heartbeatTimer=setInterval(()=>{if(this.inFlight.size===0){clearInterval(this.heartbeatTimer),this.heartbeatTimer=void 0;return}let G=[...this.inFlight.values()].map((V)=>V.job);z.heartbeat(G,this.lease()).catch((V)=>{console.error("The queue could not extend its job leases.",V)})},Math.max(1,Math.floor(this.config.visibilityTimeout/3))),this.heartbeatTimer.unref?.()}async run(z){let G=this.jobs[z.name];if(!G){let Z=Date.now()-z.createdAt;if(this.durable&&Z<this.config.unknownJobGrace){if(!this.releasedNames.has(z.name))this.releasedNames.add(z.name),console.error(`Left a queued "${z.name}" for another replica: nothing is registered here under that name. It is dead-lettered if no replica has run it within unknownJobGrace (${this.config.unknownJobGrace}ms) of its dispatch.`);await this.driver.release(z,{retryInMs:Math.round(this.config.pollInterval*(1+Math.random()))});return}console.error(`Dropped a queued job: nothing is registered under the name "${z.name}". If the class exists, it was not `+"discovered \u2014 check that it is under the queue slice's jobsDir "+"(app/jobs by default), or list it in app/config/queue.ts."),await this.driver.fail(z,{error:`No job is registered under the name "${z.name}".`,retryInMs:null});return}let V;try{V=new G}catch(Z){console.error(`Dropped a queued ${z.name}: it could not be constructed.`,Z),await this.driver.fail(z,{error:`The job could not be constructed: ${String(Z)}`,retryInMs:null});return}let Y;try{Y=JSON.parse(z.args)}catch(Z){console.error(`Dropped a queued ${z.name}: its arguments are not JSON.`,Z),await this.driver.fail(z,{error:`The arguments are not JSON: ${String(Z)}`,retryInMs:null});return}if(z.attempt>Math.max(1,V.maxAttempts)){let Z=Error(`${z.name} was claimed for attempt ${z.attempt} of ${V.maxAttempts}: an earlier attempt never finished, most likely because the process running it exited. Dead-lettered without running it again.`);L(`${z.name}.onDeadletter`,()=>V.onDeadletter(Z,...Y)),await this.driver.fail(z,{error:Z.message,retryInMs:null});return}try{let Z=await(V.worker?T(z.name,z.args):V.run(...Y));V.onSuccess(Z,...Y)}catch(Z){let $=Z,B=String($?.stack??$);if(L(`${z.name}.onFail`,()=>V.onFail($,...Y)),z.attempt>=V.maxAttempts)L(`${z.name}.onDeadletter`,()=>V.onDeadletter($,...Y)),await this.driver.fail(z,{error:B,retryInMs:null});else await this.driver.fail(z,{error:B,retryInMs:f(V.backoff,z.attempt)});return}await this.driver.complete(z)}}function L(z,G){try{G()}catch(V){console.error(`${z} threw; the job's claim was ended anyway.`,V)}}function v(){return Bun.env.NODE_ENV==="production"}function g(){if(!Q)return!1;if(h())return!0;return process.env.ROOT_DIR!==void 0&&!y()}function y(z=process.env){return(z.GEMI_QUEUE_CLAIM??"").trim().toLowerCase()==="off"}var O=Symbol.for("gemi.queue.worker");function o(z=!0){globalThis[O]=z}function h(){return globalThis[O]===!0}function f(z,G){let V=Array.isArray(z)?z[Math.min(G,z.length)-1]:z;return Math.max(0,V??0)}function q(z,G){if(z==="memory")return new X;if(z==="database"||typeof z==="function"){if(!G&&(z==="database"||z.length>0))throw Error("The queue driver needs the application it belongs to, and this QueueManager was built without one. Pass { application }.");if(z==="database")return new K(G.make(E));return z(G)}if(typeof z==="string")throw Error(`Unknown queue driver "${z}". The queue slice's driver is "memory", "database", a QueueDriver, or a function returning one.`);return z}
140
- export{K as Ub,j as Vb,_ as Wb,X as Xb,x as Yb,g as Zb,y as _b,o as $b,h as ac};
141
-
142
- //# debugId=BC3C62703517187B64756E2164756E21
@@ -1,13 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../services/queue/DatabaseQueueDriver.ts", "../services/queue/config.ts", "../services/queue/MemoryQueueDriver.ts", "../services/queue/QueueManager.ts"],
4
- "sourcesContent": [
5
- "import type { SQL, TransactionSQL } from \"bun\";\n\nimport type { DatabaseConnection } from \"../../database/Connection\";\nimport type { Dialect } from \"../../database/dialect\";\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 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",
6
- "import type { Application } from \"../../foundation/Application\";\nimport type { Job } from \"./Job\";\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\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 };\n}\n",
7
- "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",
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 { 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\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 // 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 /** `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 return;\n }\n\n try {\n const result = await (job.worker\n ? runInWorker(claimed.name, claimed.args)\n : job.run(...args));\n\n job.onSuccess(result, ...args);\n } catch (err) {\n const error = err as Error;\n const recorded = String(error?.stack ?? error);\n hook(`${claimed.name}.onFail`, () => job.onFail(error, ...args));\n\n if (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 } 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 }\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
- ],
10
- "mappings": "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",
11
- "debugId": "BC3C62703517187B64756E2164756E21",
12
- "names": []
13
- }