@cortexkit/opencode-magic-context 0.49.6 → 0.49.8

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 (255) hide show
  1. package/dist/auto-search-worker.js +10 -7
  2. package/dist/compaction-marker-candidates-worker.js +2 -2
  3. package/dist/{compaction-marker-candidates-cz72pm1t.js → compaction-marker-candidates-xxz35vwp.js} +3 -3
  4. package/dist/{conflict-warning-hook-1jq1rxx7.js → conflict-warning-hook-w3crzrar.js} +8 -8
  5. package/dist/{context-ae3gxdye.js → context-x0ccfv0f.js} +88 -53
  6. package/dist/{derive-budgets-j4ry8j8n.js → derive-budgets-3nbxsyss.js} +2 -2
  7. package/dist/embedding-backfill-3h2m5n0q.js +21 -0
  8. package/dist/embedding-worker-process.js +6 -2
  9. package/dist/embedding-worker.js +6 -5
  10. package/dist/features/magic-context/compaction-marker-candidates.d.ts.map +1 -1
  11. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  12. package/dist/features/magic-context/dreamer/cron.d.ts +2 -0
  13. package/dist/features/magic-context/dreamer/cron.d.ts.map +1 -1
  14. package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts +6 -3
  15. package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
  16. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +7 -1
  17. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/map-memories.d.ts +2 -2
  19. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/primer-seed.d.ts +2 -0
  21. package/dist/features/magic-context/dreamer/primer-seed.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +1 -0
  23. package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
  24. package/dist/features/magic-context/dreamer/refresh-primers.d.ts +2 -2
  25. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  26. package/dist/features/magic-context/memory/embedding-worker-client.d.ts.map +1 -1
  27. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  28. package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/smart-notes/wake-plane.d.ts.map +1 -1
  31. package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
  32. package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -3
  33. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-replay-document.d.ts +4 -0
  36. package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
  37. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  38. package/dist/history-owner-ty29fz0f.js +244 -0
  39. package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/apply-operations.d.ts +7 -0
  41. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/edit-marker.d.ts +2 -0
  46. package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/module-transport.d.ts +1 -0
  52. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/note-nudger.d.ts +5 -3
  54. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/postprocess-read-cache.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/read-session-chunk.d.ts +15 -4
  58. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/replay-committed-drops.d.ts +9 -0
  60. package/dist/hooks/magic-context/replay-committed-drops.d.ts.map +1 -0
  61. package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts +2 -2
  62. package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/rust-mode-transform.d.ts +4 -0
  64. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/rust-served-raw.d.ts +29 -0
  67. package/dist/hooks/magic-context/rust-served-raw.d.ts.map +1 -0
  68. package/dist/hooks/magic-context/strip-content.d.ts +23 -24
  69. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
  71. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  72. package/dist/hooks/magic-context/tool-drop-target.d.ts +2 -0
  73. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  76. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts +5 -1
  77. package/dist/hooks/magic-context/unresolved-history-boundary.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  79. package/dist/{index-g9exahnx.js → index-0jz47061.js} +6 -3
  80. package/dist/{index-4mjhqadz.js → index-150zz6fh.js} +1 -1
  81. package/dist/{index-ch08qwgd.js → index-2rw244rh.js} +88 -81
  82. package/dist/{index-6q5h70h2.js → index-4hb8nt93.js} +3 -3
  83. package/dist/{index-j55vn694.js → index-7bjgyaj1.js} +4 -4
  84. package/dist/{index-h2wc01qk.js → index-8kk7yh6m.js} +2 -2
  85. package/dist/{index-vztbgcdk.js → index-9g1c4bt9.js} +304 -92
  86. package/dist/{index-7q1m1qjk.js → index-a43xq1pg.js} +5 -5
  87. package/dist/{index-tqf7yg27.js → index-a4b0gbyg.js} +1 -1
  88. package/dist/{index-vnjhmkwk.js → index-angx1s0k.js} +4 -3
  89. package/dist/{index-1xaw2pq8.js → index-b4gcb3rk.js} +8 -8
  90. package/dist/{index-z1tjp257.js → index-b64n4d0h.js} +1 -1
  91. package/dist/{index-1bt88epp.js → index-bd9cjab3.js} +1 -1
  92. package/dist/{index-gbmqa9x9.js → index-bmwfzz28.js} +9 -6
  93. package/dist/{index-qx53vezw.js → index-gjvjh960.js} +1 -1
  94. package/dist/{index-zgzdm6dg.js → index-gzg9ekc1.js} +155 -103
  95. package/dist/{index-vh3e2mvr.js → index-hxrvx6cy.js} +121 -51
  96. package/dist/{index-w23dh319.js → index-jw4gbfkv.js} +1 -1
  97. package/dist/index-knvkbfvh.js +25 -0
  98. package/dist/{index-vkbqdnkj.js → index-kvw6xew3.js} +6 -3
  99. package/dist/{index-bg5qqene.js → index-mp2kghkv.js} +3 -27
  100. package/dist/{index-nsjyysj0.js → index-mvvephtj.js} +5 -1
  101. package/dist/{index-cm4hmxyk.js → index-n78avt2p.js} +1 -1
  102. package/dist/{index-hr64sth2.js → index-pgy3h49n.js} +4 -4
  103. package/dist/{index-a796ct3s.js → index-pjgzdezw.js} +13 -9
  104. package/dist/{index-9m7bn21d.js → index-q28hsbpg.js} +15 -3
  105. package/dist/{index-ab3bgm2m.js → index-q76br5zn.js} +3 -3
  106. package/dist/{index-xqyj5c85.js → index-qgp5vnt4.js} +5 -5
  107. package/dist/{index-9s58tq95.js → index-rg1cezmj.js} +22 -22
  108. package/dist/{index-f3dnvnw6.js → index-s178h9z8.js} +4 -3
  109. package/dist/{index-mj1fgfrk.js → index-w1ejb9qr.js} +939 -160
  110. package/dist/{index-k6xvbj2e.js → index-w28y7d0z.js} +2 -0
  111. package/dist/{index-0nnd4bry.js → index-wafzvyhg.js} +63 -19
  112. package/dist/{index-ck6y3td2.js → index-xf6k8szp.js} +4 -4
  113. package/dist/{index-1j8jcjpa.js → index-xpn05jxq.js} +4 -4
  114. package/dist/{index-s0gsmthp.js → index-ydjgz36f.js} +7 -7
  115. package/dist/{index-besxhcsh.js → index-zf9nydbd.js} +50 -39
  116. package/dist/index.js +53 -52
  117. package/dist/{logger-ewak5q1s.js → logger-wn328tfg.js} +1 -1
  118. package/dist/{message-index-bounded-630j53ps.js → message-index-bounded-hxjp56qk.js} +16 -16
  119. package/dist/{message-index-c84wm2g0.js → message-index-zs882h8e.js} +16 -16
  120. package/dist/{message-time-backfill-8kj57qe3.js → message-time-backfill-nb5hhnrt.js} +3 -3
  121. package/dist/migration-worker.js +11 -11
  122. package/dist/plugin/boot-quiet.d.ts.map +1 -1
  123. package/dist/plugin/dream-timer.d.ts.map +1 -1
  124. package/dist/plugin/messages-transform.d.ts.map +1 -1
  125. package/dist/plugin/rpc-handlers.d.ts +2 -0
  126. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  127. package/dist/{project-embedding-registry-vv59t7ah.js → project-embedding-registry-vy7sv9jt.js} +11 -11
  128. package/dist/raw-ordinal-warmup-worker.js +17 -15
  129. package/dist/{read-session-chunk-t3vjd0h0.js → read-session-chunk-t9r7r0sg.js} +13 -11
  130. package/dist/{read-session-raw-gf7eytnm.js → read-session-raw-gm6xj30v.js} +3 -3
  131. package/dist/recomp-orchestrator-gr36zvd2.js +60 -0
  132. package/dist/{safe-notification-target-q0cyrx3x.js → safe-notification-target-jdynm677.js} +2 -2
  133. package/dist/{search-kth2p6cc.js → search-qjen1pmz.js} +24 -24
  134. package/dist/{send-session-notification-3j6krd4w.js → send-session-notification-q8ngbap3.js} +6 -6
  135. package/dist/shared/history-owner.d.ts +12 -0
  136. package/dist/shared/history-owner.d.ts.map +1 -0
  137. package/dist/shared/off-thread-migration-clock.d.ts +8 -1
  138. package/dist/shared/off-thread-migration-clock.d.ts.map +1 -1
  139. package/dist/shared/processed-image-eligibility.d.ts +22 -0
  140. package/dist/shared/processed-image-eligibility.d.ts.map +1 -0
  141. package/dist/shared/recovery-reasoning-prefix.d.ts +9 -0
  142. package/dist/shared/recovery-reasoning-prefix.d.ts.map +1 -0
  143. package/dist/shared/redaction.d.ts.map +1 -1
  144. package/dist/shared/rpc-server.d.ts.map +1 -1
  145. package/dist/shared/sqlite.d.ts +3 -1
  146. package/dist/shared/sqlite.d.ts.map +1 -1
  147. package/dist/shared/tag-transcript.d.ts +9 -0
  148. package/dist/shared/tag-transcript.d.ts.map +1 -1
  149. package/dist/shared/user-facing-codes.d.ts +2 -2
  150. package/dist/{sqlite-d7edbk1s.js → sqlite-4pqyynrs.js} +3 -1
  151. package/dist/{storage-memory-af5zdz98.js → storage-memory-j3v3hc83.js} +2 -2
  152. package/dist/{storage-tags-dyj7mmbq.js → storage-tags-a513rkj3.js} +5 -5
  153. package/dist/tools/ctx-expand/search-archive.d.ts +4 -0
  154. package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -0
  155. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  156. package/dist/{transform-postprocess-phase-bv3kb5s1.js → transform-postprocess-phase-yv463b7y.js} +30 -30
  157. package/dist/v2/auto-search-worker.js +10 -7
  158. package/dist/v2/{compaction-marker-candidates-6gqp5wrc.js → compaction-marker-candidates-a15w3mqh.js} +3 -3
  159. package/dist/v2/compaction-marker-candidates-worker.js +2 -2
  160. package/dist/v2/{context-p8f2shp1.js → context-wd82v9z6.js} +1133 -278
  161. package/dist/v2/{derive-budgets-enckmg7y.js → derive-budgets-0rmpmxjw.js} +3 -3
  162. package/dist/v2/embedding-backfill-vr7wdtrf.js +20 -0
  163. package/dist/v2/embedding-worker-process.js +6 -2
  164. package/dist/v2/embedding-worker.js +6 -5
  165. package/dist/v2/hidden-completion.d.ts.map +1 -1
  166. package/dist/v2/history-owner-2cr9py37.js +243 -0
  167. package/dist/v2/hooks/context.d.ts.map +1 -1
  168. package/dist/v2/hooks/payload.d.ts.map +1 -1
  169. package/dist/v2/hooks/store.d.ts +1 -0
  170. package/dist/v2/hooks/store.d.ts.map +1 -1
  171. package/dist/v2/{logger-pj02s7sr.js → logger-te6e1j72.js} +1 -1
  172. package/dist/v2/{message-index-bounded-yzxmf6vp.js → message-index-bounded-madg2kfr.js} +16 -16
  173. package/dist/v2/{message-index-3ws17x1g.js → message-index-yd94vt9n.js} +16 -16
  174. package/dist/v2/{message-time-backfill-5nf10c65.js → message-time-backfill-rz1pnpaj.js} +3 -3
  175. package/dist/v2/migration-worker.js +11 -11
  176. package/dist/v2/{project-embedding-registry-rsnf2263.js → project-embedding-registry-ja23aygk.js} +11 -11
  177. package/dist/v2/raw-ordinal-warmup-worker.js +17 -15
  178. package/dist/v2/{read-session-chunk-vtb0w2kd.js → read-session-chunk-7qs791f3.js} +13 -11
  179. package/dist/v2/{read-session-raw-gpcddemt.js → read-session-raw-h4azh85b.js} +3 -3
  180. package/dist/v2/recomp-orchestrator-ee91r9s6.js +59 -0
  181. package/dist/v2/{safe-notification-target-y3p1hce9.js → safe-notification-target-936374xx.js} +2 -2
  182. package/dist/v2/search-pn15rr52.js +46 -0
  183. package/dist/v2/{send-session-notification-5aj2j7vf.js → send-session-notification-6n7chhyp.js} +6 -6
  184. package/dist/v2/{server-09g9pkvx.js → server-08k4zkfk.js} +50 -39
  185. package/dist/v2/{server-ras2sxnc.js → server-44ryn9rb.js} +9 -6
  186. package/dist/v2/{server-158ehmk2.js → server-4easnf78.js} +19 -19
  187. package/dist/v2/{server-ww9nr8pe.js → server-4gz0xkrc.js} +5 -5
  188. package/dist/v2/{server-d6g4whc2.js → server-4qr18rfg.js} +3 -3
  189. package/dist/v2/{server-f9py7s7a.js → server-4rs8sm06.js} +5 -5
  190. package/dist/v2/{server-yezz0cp2.js → server-63ha7f0f.js} +3 -27
  191. package/dist/v2/{server-3t0q2cqx.js → server-7dv9wt9r.js} +1 -1
  192. package/dist/v2/{server-cw6521wn.js → server-7e087rvk.js} +2 -0
  193. package/dist/v2/{server-hb6xhh9q.js → server-988cqjme.js} +6 -3
  194. package/dist/v2/{server-keswwkvv.js → server-9a6bwc12.js} +4 -4
  195. package/dist/v2/{server-t4k4qexw.js → server-9e5r8y60.js} +5 -1
  196. package/dist/v2/{server-7ekna9zs.js → server-adfte03x.js} +2 -2
  197. package/dist/v2/{server-9zqewm6g.js → server-ak7w74j9.js} +4 -4
  198. package/dist/v2/{server-387vkhjq.js → server-bx59ezg6.js} +4 -3
  199. package/dist/v2/{server-676xw7at.js → server-ckrb5rm8.js} +1 -1
  200. package/dist/v2/{server-wrem2nmz.js → server-cxsr0ng6.js} +15 -3
  201. package/dist/v2/{server-t528t8qm.js → server-hn9fb3v0.js} +2 -2
  202. package/dist/v2/{server-qn23p4h7.js → server-j3dm74ey.js} +9 -9
  203. package/dist/v2/{server-yd01snxq.js → server-jjsf29aq.js} +4 -4
  204. package/dist/v2/server-knvkbfvh.js +25 -0
  205. package/dist/v2/{server-cm4hmxyk.js → server-n78avt2p.js} +1 -1
  206. package/dist/v2/{server-btgnsv7w.js → server-njnx39q2.js} +6 -3
  207. package/dist/v2/{server-6a9mxnb2.js → server-npm52h2q.js} +4 -3
  208. package/dist/v2/{server-vb7pbd9p.js → server-nveratm5.js} +1 -1
  209. package/dist/v2/{server-7skbfb81.js → server-q360kdb8.js} +121 -51
  210. package/dist/v2/{server-kzns2hbz.js → server-s6jqh8bj.js} +1 -1
  211. package/dist/v2/{server-6byqfbqf.js → server-shme41gj.js} +1 -1
  212. package/dist/v2/{server-0nnd4bry.js → server-wafzvyhg.js} +63 -19
  213. package/dist/v2/{server-fby0cxbq.js → server-xj4mewze.js} +304 -92
  214. package/dist/v2/{server-aa9w15cs.js → server-y5g32dhq.js} +88 -81
  215. package/dist/v2/{server-x2r63c0x.js → server-yak3sxya.js} +7 -7
  216. package/dist/v2/{server-4tn95znb.js → server-zrwaj29a.js} +2 -2
  217. package/dist/v2/server.js +9 -8
  218. package/dist/v2/{sqlite-d7edbk1s.js → sqlite-4pqyynrs.js} +3 -1
  219. package/dist/v2/{storage-memory-af5zdz98.js → storage-memory-j3v3hc83.js} +2 -2
  220. package/dist/v2/{storage-tags-d47bpaqa.js → storage-tags-spa92sgj.js} +5 -5
  221. package/dist/v2/store-reader.d.ts.map +1 -1
  222. package/dist/v2/{wrapup-orchestrator-cahk37n7.js → wrapup-orchestrator-5wxy1ggv.js} +38 -35
  223. package/dist/wrapup-orchestrator-gv08c6f9.js +47 -0
  224. package/package.json +3 -3
  225. package/src/config/compaction-accessor-guard.test.ts +3 -0
  226. package/src/features/magic-context/dreamer/cron.ts +8 -0
  227. package/src/shared/history-owner.test.ts +60 -0
  228. package/src/shared/history-owner.ts +43 -0
  229. package/src/shared/host-stall-profiler-node.ts +3 -1
  230. package/src/shared/host-stall-profiler.ts +3 -1
  231. package/src/shared/off-thread-migration-clock.test.ts +80 -10
  232. package/src/shared/off-thread-migration-clock.ts +37 -13
  233. package/src/shared/processed-image-eligibility.test.ts +68 -0
  234. package/src/shared/processed-image-eligibility.ts +41 -0
  235. package/src/shared/processed-image-position-review.json +20 -0
  236. package/src/shared/processed-image-position-review.test.ts +19 -0
  237. package/src/shared/recovery-reasoning-prefix.test.ts +34 -0
  238. package/src/shared/recovery-reasoning-prefix.ts +35 -0
  239. package/src/shared/redaction.test.ts +16 -0
  240. package/src/shared/redaction.ts +8 -1
  241. package/src/shared/root-async-context.probe.mjs +36 -0
  242. package/src/shared/root-async-context.test.ts +49 -0
  243. package/src/shared/rpc-server.ts +27 -23
  244. package/src/shared/sqlite-busy-budget-review.test.ts +291 -0
  245. package/src/shared/sqlite-foreground-wait.test.ts +253 -1
  246. package/src/shared/sqlite-writer-diagnostics.test.ts +31 -0
  247. package/src/shared/sqlite.ts +112 -30
  248. package/src/shared/tag-transcript.ts +118 -0
  249. package/src/shared/user-facing-codes.ts +2 -2
  250. package/dist/embedding-backfill-dtjmyktb.js +0 -21
  251. package/dist/recomp-orchestrator-1d08g57a.js +0 -60
  252. package/dist/v2/embedding-backfill-cg4rv90f.js +0 -20
  253. package/dist/v2/recomp-orchestrator-v64r4s4r.js +0 -59
  254. package/dist/v2/search-bpa4fxq1.js +0 -46
  255. package/dist/wrapup-orchestrator-mqp0v195.js +0 -47
@@ -1,4 +1,4 @@
1
- import { expect, test } from "bun:test";
1
+ import { expect, spyOn, test } from "bun:test";
2
2
  import { rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
@@ -113,6 +113,220 @@ for (const holdMs of [1000, 2500]) {
113
113
  }, 30_000);
114
114
  }
115
115
 
116
+ for (const holdMs of [1000, 2500]) {
117
+ test(`slow uncontended SQL preserves admission for a ${holdMs}ms autocommit lock`, async () => {
118
+ const { db, path, close } = fixture();
119
+ let locker: SqliteWriteLocker | undefined;
120
+ let served = false;
121
+ let refusals = 0;
122
+ const output = {
123
+ messages: [
124
+ {
125
+ info: { id: "u", sessionID: "slow", role: "user" },
126
+ parts: [{ type: "text", text: "raw" }],
127
+ },
128
+ ],
129
+ };
130
+ const handler = createMessagesTransformHandler({
131
+ onStorageBusyRefusal: async () => {
132
+ refusals++;
133
+ },
134
+ magicContext: {
135
+ "experimental.chat.messages.transform": async (_input, result) => {
136
+ // Spend three seconds inside successful SQL, not between writes.
137
+ // Recursive work keeps this independent of CPU speed and UDF APIs.
138
+ const cte =
139
+ "WITH RECURSIVE n(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM n WHERE x<1000000)";
140
+ const slowWrite = db.prepare(
141
+ `${cte} INSERT INTO result SELECT sum(x) FROM n RETURNING value`,
142
+ );
143
+ const slowRead = db.prepare(`${cte} SELECT sum(x) FROM n`);
144
+ const started = performance.now();
145
+ do {
146
+ slowWrite.all();
147
+ slowRead.all();
148
+ } while (performance.now() - started < 3000);
149
+ locker = await startSqliteWriteLocker(path, holdMs);
150
+ db.prepare("INSERT INTO result VALUES ('managed')").run();
151
+ (result.messages[0].parts[0] as { text: string }).text = "managed";
152
+ },
153
+ },
154
+ });
155
+ try {
156
+ const pass = handler({}, output as Parameters<typeof handler>[1]).then(() => {
157
+ served = true;
158
+ });
159
+ if (holdMs === 1000) {
160
+ await pass;
161
+ expect(served).toBe(true);
162
+ expect(refusals).toBe(0);
163
+ expect(output.messages[0].parts[0].text).toBe("managed");
164
+ } else {
165
+ await expect(pass).rejects.toBeInstanceOf(StorageBusyRefusalError);
166
+ expect(served).toBe(false);
167
+ expect(refusals).toBe(1);
168
+ expect(output.messages[0].parts[0].text).toBe("raw");
169
+ }
170
+ expect(
171
+ db.prepare("SELECT count(*) AS n FROM result WHERE value = 'managed'").get(),
172
+ ).toEqual({ n: holdMs === 1000 ? 1 : 0 });
173
+ } finally {
174
+ if (locker) expect(await locker.exited).toBe(0);
175
+ close();
176
+ }
177
+ }, 30_000);
178
+ }
179
+
180
+ test("BUSY autocommit probes do not double-apply INSERT OR IGNORE, UPSERT or RETURNING", async () => {
181
+ const { db, path, close } = fixture();
182
+ db.exec(
183
+ "CREATE TABLE counter(id INTEGER PRIMARY KEY, n INTEGER); INSERT INTO counter VALUES (1, 0)",
184
+ );
185
+ const forms = [
186
+ ["INSERT OR IGNORE INTO counter VALUES (2, 1)", "run"],
187
+ ["INSERT INTO counter VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET n = n + 1", "run"],
188
+ ["UPDATE counter SET n = n + 1 WHERE id = 1 RETURNING n", "get"],
189
+ [
190
+ "INSERT INTO counter VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET n = n + 1 RETURNING n",
191
+ "all",
192
+ ],
193
+ ] as const;
194
+ try {
195
+ for (const [sql, method] of forms) {
196
+ const blocker = new Database(path);
197
+ blocker.exec("BEGIN IMMEDIATE");
198
+ db.exec("PRAGMA busy_timeout=0");
199
+ const before = db.prepare("SELECT * FROM counter ORDER BY id").all();
200
+ try {
201
+ expect(() => db.prepare(sql)[method]()).toThrow("locked");
202
+ expect(db.prepare("SELECT * FROM counter ORDER BY id").all()).toEqual(before);
203
+ expect(db.inTransaction).toBe(false);
204
+ } finally {
205
+ blocker.exec("ROLLBACK");
206
+ blocker.close();
207
+ }
208
+ const locker = await startSqliteWriteLocker(path, 100);
209
+ try {
210
+ await withSqliteTransformPass(() => db.prepare(sql)[method]());
211
+ } finally {
212
+ expect(await locker.exited).toBe(0);
213
+ }
214
+ }
215
+ expect(db.prepare("SELECT * FROM counter ORDER BY id").all()).toEqual([
216
+ { id: 1, n: 3 },
217
+ { id: 2, n: 1 },
218
+ ]);
219
+ } finally {
220
+ close();
221
+ }
222
+ }, 30_000);
223
+
224
+ test("many contended autocommit writes share at most two seconds of lock waiting", async () => {
225
+ const { db, path, close } = fixture();
226
+ const blocker = new Database(path);
227
+ blocker.exec("BEGIN IMMEDIATE");
228
+ const timeouts: number[] = [];
229
+ const nativeExec = db.exec.bind(db);
230
+ const exec = spyOn(db, "exec").mockImplementation((sql) => {
231
+ const timeout = /^PRAGMA busy_timeout=(\d+)$/.exec(sql);
232
+ if (timeout) timeouts.push(Number(timeout[1]));
233
+ return nativeExec(sql);
234
+ });
235
+ try {
236
+ await withSqliteTransformPass(() => {
237
+ // Use real BUSY failures but a controlled accounting clock: each
238
+ // retry costs 400ms regardless of runner scheduling. The deadline
239
+ // keeps the real waits short without hiding lease exhaustion.
240
+ guardSqliteTransformPass({ assert() {}, remainingMs: () => 10 });
241
+ let now = 0;
242
+ const clock = spyOn(performance, "now").mockImplementation(() => (now += 400));
243
+ try {
244
+ for (let i = 0; i < 8; i++) {
245
+ expect(() => db.prepare("INSERT INTO result VALUES ('one')").run()).toThrow(
246
+ SqliteAcquisitionBusyError,
247
+ );
248
+ }
249
+ } finally {
250
+ clock.mockRestore();
251
+ }
252
+ // Five charged retries exhaust 2000ms. All later retries must have
253
+ // zero timeout even though the deadline still offers another 10ms.
254
+ expect(timeouts).toEqual([
255
+ ...Array.from({ length: 5 }, () => [0, 10, 5000]).flat(),
256
+ ...Array.from({ length: 3 }, () => [0, 0, 5000]).flat(),
257
+ ]);
258
+ blocker.exec("ROLLBACK");
259
+ db.prepare("INSERT INTO result VALUES ('uncontended')").run();
260
+ });
261
+ expect(db.prepare("SELECT * FROM result").all()).toEqual([{ value: "uncontended" }]);
262
+ } finally {
263
+ exec.mockRestore();
264
+ if (blocker.inTransaction) blocker.exec("ROLLBACK");
265
+ blocker.close();
266
+ close();
267
+ }
268
+ }, 30_000);
269
+
270
+ test("an exec script charges its single attempt without a zero-wait probe", () => {
271
+ const { db, path, close } = fixture();
272
+ const blocker = new Database(path);
273
+ const timeouts: number[] = [];
274
+ const nativeExec = db.exec.bind(db);
275
+ const exec = spyOn(db, "exec").mockImplementation((sql) => {
276
+ const timeout = /^PRAGMA busy_timeout=(\d+)$/.exec(sql);
277
+ if (timeout) timeouts.push(Number(timeout[1]));
278
+ return nativeExec(sql);
279
+ });
280
+ try {
281
+ withSqliteTransformPass(() => {
282
+ let now = 0;
283
+ const clock = spyOn(performance, "now").mockImplementation(() => (now += 2500));
284
+ try {
285
+ db.exec(
286
+ "INSERT INTO result VALUES ('prefix'); INSERT INTO result VALUES ('suffix')",
287
+ );
288
+ } finally {
289
+ clock.mockRestore();
290
+ }
291
+ blocker.exec("BEGIN IMMEDIATE");
292
+ expect(() => db.prepare("INSERT INTO result VALUES ('blocked')").run()).toThrow(
293
+ SqliteAcquisitionBusyError,
294
+ );
295
+ });
296
+ expect(timeouts).toEqual([2000, 5000, 0, 0, 5000]);
297
+ expect(db.prepare("SELECT * FROM result").all()).toEqual([
298
+ { value: "prefix" },
299
+ { value: "suffix" },
300
+ ]);
301
+ } finally {
302
+ exec.mockRestore();
303
+ if (blocker.inTransaction) blocker.exec("ROLLBACK");
304
+ blocker.close();
305
+ close();
306
+ }
307
+ });
308
+
309
+ test("an explicit transaction keeps its timeout and never probes or retries", () => {
310
+ const { db, path, close } = fixture();
311
+ const other = new Database(path);
312
+ db.exec("PRAGMA busy_timeout=0; BEGIN; SELECT * FROM result");
313
+ other.exec("INSERT INTO result VALUES ('other')");
314
+ const exec = spyOn(db, "exec");
315
+ try {
316
+ withSqliteTransformPass(() => {
317
+ expect(() => db.prepare("INSERT INTO result VALUES ('stale')").run()).toThrow("locked");
318
+ });
319
+ expect(exec).not.toHaveBeenCalled();
320
+ expect(db.inTransaction).toBe(true);
321
+ expect(db.prepare("PRAGMA busy_timeout").get()).toEqual({ timeout: 0 });
322
+ } finally {
323
+ exec.mockRestore();
324
+ if (db.inTransaction) db.exec("ROLLBACK");
325
+ other.close();
326
+ close();
327
+ }
328
+ });
329
+
116
330
  for (const mode of ["BEGIN", "autocommit"]) {
117
331
  test(`foreground ${mode} wait is capped by the pass deadline`, () => {
118
332
  const { db, path, close } = fixture();
@@ -138,3 +352,41 @@ for (const mode of ["BEGIN", "autocommit"]) {
138
352
  }
139
353
  });
140
354
  }
355
+
356
+ test("an autocommit exec script never replays a committed prefix after BUSY", () => {
357
+ const main = fixture();
358
+ const auxiliary = fixture();
359
+ const free = fixture();
360
+ try {
361
+ main.db.prepare("ATTACH DATABASE ? AS aux").run(auxiliary.path);
362
+ main.db.prepare("ATTACH DATABASE ? AS free").run(free.path);
363
+ auxiliary.db.exec("BEGIN IMMEDIATE");
364
+ withSqliteTransformPass(() => {
365
+ guardSqliteTransformPass({ assert() {}, remainingMs: () => 100 });
366
+ expect(() =>
367
+ main.db.exec(
368
+ "INSERT INTO main.result VALUES ('prefix'); INSERT INTO aux.result VALUES ('blocked')",
369
+ ),
370
+ ).toThrow(SqliteAcquisitionBusyError);
371
+ expect(() =>
372
+ main.db.exec(
373
+ "CREATE TABLE applied_once(id); INSERT INTO aux.result VALUES ('blocked')",
374
+ ),
375
+ ).toThrow(SqliteAcquisitionBusyError);
376
+ expect(() =>
377
+ main.db.exec(
378
+ "CREATE TABLE free.applied_once(id); INSERT INTO aux.result VALUES ('blocked')",
379
+ ),
380
+ ).toThrow(SqliteAcquisitionBusyError);
381
+ });
382
+ expect(main.db.prepare("SELECT * FROM main.result").all()).toEqual([{ value: "prefix" }]);
383
+ expect(
384
+ main.db.prepare("SELECT name FROM sqlite_schema WHERE name='applied_once'").all(),
385
+ ).toHaveLength(1);
386
+ } finally {
387
+ auxiliary.db.exec("ROLLBACK");
388
+ auxiliary.close();
389
+ main.close();
390
+ free.close();
391
+ }
392
+ });
@@ -39,6 +39,22 @@ const scenario = `
39
39
  hold();
40
40
  writer.exec("COMMIT");
41
41
 
42
+ writer.exec("BEGIN IMMEDIATE");
43
+ now += 101;
44
+ writer.exec("ROLLBACK");
45
+
46
+ writer.exec("CREATE TABLE result(value)");
47
+ const statementTimes = [now, now + 101];
48
+ performance.now = () => statementTimes.shift() ?? now;
49
+ writer.prepare("INSERT INTO result VALUES (1)").run();
50
+ now += 101;
51
+
52
+ let clockReads = 0;
53
+ performance.now = () => { clockReads++; return now; };
54
+ writer.exec("BEGIN IMMEDIATE");
55
+ writer.exec("COMMIT");
56
+ if (clockReads !== 2) throw new Error("fast unscoped transaction clock reads=" + clockReads);
57
+
42
58
  logger.flushLogger();
43
59
  writer.close();
44
60
  holder.close();
@@ -106,6 +122,21 @@ describe("sqlite writer diagnostics", () => {
106
122
  expect(consoleOutput).not.toContain("sqlite writer");
107
123
  });
108
124
 
125
+ test("unscoped rollback names the host, pid and caller at the 100ms threshold", () => {
126
+ const rolledBack = logLines.find((line) => line.includes("outcome=rolled_back"));
127
+ expect(rolledBack).toContain("lane=background");
128
+ expect(rolledBack).toContain("hold_ms=101");
129
+ expect(rolledBack).toMatch(/pid=\d+ host=opencode/);
130
+ expect(rolledBack).toContain("caller=");
131
+ });
132
+
133
+ test("native autocommit time is labelled as including acquisition, not lock ownership", () => {
134
+ const statement = logLines.find((line) => line.includes("site=autocommit"));
135
+ expect(statement).toContain("elapsed_ms=101");
136
+ expect(statement).toContain("timing=statement_including_acquisition");
137
+ expect(statement).not.toContain("hold_ms=");
138
+ });
139
+
109
140
  test("a writer that waited for the lock reports the wait, not its short hold", () => {
110
141
  const waited = logLines.find((line) => line.includes("attempts=2"));
111
142
  if (!waited) throw new Error(`no retried acquisition logged:\n${logLines.join("\n")}`);
@@ -39,7 +39,7 @@
39
39
  * `run()` → {changes,lastInsertRowid}) is identical and was verified directly.
40
40
  */
41
41
 
42
- import { AsyncLocalStorage } from "node:async_hooks";
42
+ import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
43
43
  import { statSync } from "node:fs";
44
44
 
45
45
  // Type import only — runtime is loaded dynamically below. @types/better-sqlite3
@@ -48,6 +48,16 @@ import { statSync } from "node:fs";
48
48
  // node:sqlite at runtime (both expose prepare/run/get/all/exec/close).
49
49
  import type BetterSqlite3 from "better-sqlite3";
50
50
 
51
+ // Capture the root before creating request-local AsyncLocalStorage scopes.
52
+ // Shared workers and timers must not inherit callbacks that reject stale
53
+ // database writes: those callbacks can retain an obsolete request's inputs.
54
+ const resourceRoot = new AsyncResource("magic-context:root");
55
+
56
+ /** Create host-owned resources without inheriting request-local state. */
57
+ export function inRootAsyncContext<T>(operation: () => T): T {
58
+ return resourceRoot.runInAsyncScope(operation);
59
+ }
60
+
51
61
  /**
52
62
  * Slow-write reporting is injected rather than imported: this module is also
53
63
  * executed directly by Node (the `node:sqlite` smoke in CI, Pi, Desktop), and
@@ -72,14 +82,21 @@ export function registerSlowWriteReporter(reporter: SlowWriteReporter): void {
72
82
  */
73
83
  type SqliteDiagnosticSink = (message: string) => void;
74
84
  let sqliteDiagnosticSink: SqliteDiagnosticSink | undefined;
85
+ let sqliteDiagnosticHost: () => string = () => "unknown";
75
86
 
76
- export function registerSqliteDiagnosticSink(sink: SqliteDiagnosticSink): void {
87
+ export function registerSqliteDiagnosticSink(
88
+ sink: SqliteDiagnosticSink,
89
+ host?: () => string,
90
+ ): void {
77
91
  sqliteDiagnosticSink = sink;
92
+ if (host) sqliteDiagnosticHost = host;
78
93
  }
79
94
 
80
95
  function reportSqliteDiagnostic(message: string): void {
81
96
  try {
82
- sqliteDiagnosticSink?.(`[magic-context] ${message}`);
97
+ sqliteDiagnosticSink?.(
98
+ `[magic-context] ${message} pid=${process.pid} host=${sqliteDiagnosticHost()}`,
99
+ );
83
100
  } catch {
84
101
  // Diagnostics must never change the outcome of the write they describe.
85
102
  }
@@ -393,9 +410,18 @@ function installTransactionRouting(db: BetterSqlite3.Database, readonly: boolean
393
410
  // A transaction that ended without passing through exec (for
394
411
  // example an automatic rollback) must not be timed as this one.
395
412
  openWriterTransactions.delete(db);
396
- if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore())
413
+ if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore()) {
397
414
  acquireShort(db, () => nativeExec(sql), sql.trim());
398
- else nativeExec(sql);
415
+ } else {
416
+ nativeExec(sql);
417
+ openWriterTransactions.set(db, {
418
+ site: sql.trim(),
419
+ lane: "background",
420
+ acquireMs: 0,
421
+ attempts: 1,
422
+ acquiredAt: performance.now(),
423
+ });
424
+ }
399
425
  return db;
400
426
  }
401
427
  if (WRITER_TRANSACTION_END.test(sql) && openWriterTransactions.has(db))
@@ -404,7 +430,9 @@ function installTransactionRouting(db: BetterSqlite3.Database, readonly: boolean
404
430
  !/^\s*(?:SELECT|EXPLAIN|PRAGMA|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\b/i.test(sql)
405
431
  ) {
406
432
  try {
407
- return withAutocommitTimeout(db, () => nativeExec(sql));
433
+ // exec can commit a script prefix before BUSY. Run it once;
434
+ // only prepared statements are safe to probe and retry.
435
+ return withAutocommitTimeout(db, () => nativeExec(sql), false);
408
436
  } finally {
409
437
  if (mayChangeTagIdentity(sql)) tagIdentityWriteGeneration += 1;
410
438
  }
@@ -571,7 +599,9 @@ function trackSqliteConnection(
571
599
 
572
600
  const TrackedDatabase = new Proxy(DatabaseImpl, {
573
601
  construct(target, args) {
574
- const db = Reflect.construct(target, args, target) as BetterSqlite3.Database;
602
+ const db = inRootAsyncContext(
603
+ () => Reflect.construct(target, args, target) as BetterSqlite3.Database,
604
+ );
575
605
  return trackSqliteConnection(db, args[0], args[1]);
576
606
  },
577
607
  }) as typeof BetterSqlite3;
@@ -943,7 +973,7 @@ const SHORT_BUSY_TIMEOUT_MS = 25;
943
973
  const FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS = 2_000;
944
974
  const FOREGROUND_ACQUISITION_BUDGET_MS = 16_500;
945
975
  /** Waiting this long for the writer lock, or holding it this long, is logged. */
946
- const SLOW_WRITER_LOG_MS = 250;
976
+ const SLOW_WRITER_LOG_MS = 100;
947
977
  const WRITER_TRANSACTION_END = /^\s*(?:COMMIT|END|ROLLBACK)(?:\s+TRANSACTION)?\s*;?\s*$/i;
948
978
 
949
979
  type WriterLane = "foreground" | "background";
@@ -984,32 +1014,67 @@ function foregroundWaitLease() {
984
1014
  : undefined;
985
1015
  }
986
1016
 
987
- /** Bound implicit writer admission too, including prepared INSERT OR IGNORE and
988
- * writes with RETURNING. Charge successful execution time conservatively: the
989
- * native API cannot separate lock waiting from statement execution. Exhaustion
990
- * switches to zero-wait acquisition, not a refusal when the writer is free. */
991
- function withAutocommitTimeout<T>(db: Database, operation: () => T): T {
1017
+ /** Probe a prepared statement's writer lock without charging uncontended work
1018
+ * against the foreground lock-wait budget. An exec script may commit some of
1019
+ * its statements before failing, so it cannot be retried without duplicating
1020
+ * writes; conservatively charge its single attempt's full execution time. */
1021
+ function withAutocommitTimeout<T>(db: Database, operation: () => T, singleStatement = true): T {
992
1022
  assertTransformWrite();
993
1023
  const lease = foregroundWaitLease();
994
- if (isInTransaction(db) || (!lease && !backgroundWriterScope.getStore())) return operation();
995
- const previous = pragmaNumber(db, "busy_timeout");
996
- db.exec(
997
- `PRAGMA busy_timeout=${lease ? Math.floor(Math.min(lease.remainingWaitMs, lease.guard?.remainingMs() ?? Infinity)) : SHORT_BUSY_TIMEOUT_MS}`,
998
- );
1024
+ if (isInTransaction(db)) return operation();
1025
+ const scoped = lease || backgroundWriterScope.getStore();
1026
+ const previous = scoped ? pragmaNumber(db, "busy_timeout") : null;
999
1027
  const started = performance.now();
1028
+ let completed = false;
1029
+ const execute = () => {
1030
+ const result = operation();
1031
+ completed = true;
1032
+ return result;
1033
+ };
1000
1034
  try {
1001
- return operation();
1035
+ if (!scoped) return execute();
1036
+ if (singleStatement) {
1037
+ db.exec("PRAGMA busy_timeout=0");
1038
+ try {
1039
+ return execute();
1040
+ } catch (error) {
1041
+ if (!isTransientSqliteError(error) || isInTransaction(db)) throw error;
1042
+ }
1043
+ assertTransformWrite();
1044
+ }
1045
+ const timeout = Math.floor(
1046
+ Math.min(
1047
+ lease?.remainingWaitMs ?? SHORT_BUSY_TIMEOUT_MS,
1048
+ lease?.guard?.remainingMs() ?? Infinity,
1049
+ ),
1050
+ );
1051
+ db.exec(`PRAGMA busy_timeout=${timeout}`);
1052
+ const waitStarted = performance.now();
1053
+ try {
1054
+ return execute();
1055
+ } finally {
1056
+ // Deduct the prepared statement's contended retry or the exec
1057
+ // script's execution from the remaining foreground wait budget;
1058
+ // the native API cannot separate lock waiting from execution.
1059
+ if (lease)
1060
+ lease.remainingWaitMs = Math.max(
1061
+ 0,
1062
+ lease.remainingWaitMs - (performance.now() - waitStarted),
1063
+ );
1064
+ }
1002
1065
  } catch (error) {
1003
1066
  if (lease && isTransientSqliteError(error))
1004
1067
  throw new SqliteAcquisitionBusyError(error, "autocommit");
1005
1068
  throw error;
1006
1069
  } finally {
1007
- if (lease)
1008
- lease.remainingWaitMs = Math.max(
1009
- 0,
1010
- lease.remainingWaitMs - (performance.now() - started),
1011
- );
1070
+ const elapsed = performance.now() - started;
1012
1071
  if (previous !== null) db.exec(`PRAGMA busy_timeout=${previous}`);
1072
+ // Native autocommit does not expose the instant its lock was acquired.
1073
+ // Label the upper bound so a waiting statement is not blamed as a holder.
1074
+ if (elapsed >= SLOW_WRITER_LOG_MS)
1075
+ reportSqliteDiagnostic(
1076
+ `sqlite writer site=autocommit lane=${lease ? "foreground" : "background"} elapsed_ms=${Math.round(elapsed)} timing=statement_including_acquisition outcome=${completed ? "completed" : "failed"} caller=${writerCaller()}`,
1077
+ );
1013
1078
  }
1014
1079
  }
1015
1080
 
@@ -1071,20 +1136,18 @@ function acquireShort(db: Database, acquire: () => unknown, site: string): void
1071
1136
  * `acquireShort`, then report how long the writer waited for the lock
1072
1137
  * (`acquire_ms`) and how long it then held it (`hold_ms`) when either crosses
1073
1138
  * the threshold. A long hold blocks every other process's writer, so it is
1074
- * worth seeing even when the lock itself came quickly. The native end statement's
1075
- * `end_ms` is reported separately, so a slow FTS merge or checkpoint at COMMIT
1076
- * is not mistaken for acquisition time.
1139
+ * worth seeing even when the lock itself came quickly. This interval includes
1140
+ * COMMIT: an automatic PASSIVE checkpoint can run after writer-lock release,
1141
+ * so hold_ms is a conservative upper bound, not proof of continuous ownership.
1077
1142
  */
1078
1143
  function endWriterTransaction(db: Database, end: () => unknown, sql: string): unknown {
1079
1144
  let ended = false;
1080
- const endStarted = performance.now();
1081
1145
  try {
1082
1146
  const result = end();
1083
1147
  ended = true;
1084
1148
  return result;
1085
1149
  } finally {
1086
1150
  const open = openWriterTransactions.get(db);
1087
- const endMs = performance.now() - endStarted;
1088
1151
  // A COMMIT that failed with the transaction still open has not ended it.
1089
1152
  if (open && !isInTransaction(db)) {
1090
1153
  openWriterTransactions.delete(db);
@@ -1096,13 +1159,32 @@ function endWriterTransaction(db: Database, end: () => unknown, sql: string): un
1096
1159
  ? "committed"
1097
1160
  : "failed";
1098
1161
  reportSqliteDiagnostic(
1099
- `sqlite writer site=${open.site} lane=${open.lane} acquire_ms=${Math.round(open.acquireMs)} hold_ms=${Math.round(holdMs)} end_ms=${Math.round(endMs)} attempts=${open.attempts} outcome=${outcome}`,
1162
+ `sqlite writer site=${open.site} lane=${open.lane} acquire_ms=${Math.round(open.acquireMs)} hold_ms=${Math.round(holdMs)} attempts=${open.attempts} outcome=${outcome} timing=through_commit_upper_bound caller=${writerCaller()}`,
1100
1163
  );
1101
1164
  }
1102
1165
  }
1103
1166
  }
1104
1167
  }
1105
1168
 
1169
+ // Capture a stack only for slow writes. Synchronous COMMIT retains the writer's
1170
+ // caller, so fast transactions need neither stack allocation nor source mapping.
1171
+ function writerCaller(): string {
1172
+ return (
1173
+ new Error().stack
1174
+ ?.split("\n")
1175
+ .find(
1176
+ (line) =>
1177
+ line.includes(" at ") &&
1178
+ !/[/\\]sqlite\.[tj]s[:)]/.test(line) &&
1179
+ !/\b(?:writerCaller|endWriterTransaction|withAutocommitTimeout|value)\b/.test(
1180
+ line,
1181
+ ),
1182
+ )
1183
+ ?.trim()
1184
+ .replace(/\s+/g, "_") ?? "unknown"
1185
+ );
1186
+ }
1187
+
1106
1188
  /** Begin an immediate transaction with bounded asynchronous retries on writer contention.
1107
1189
  * Callers must recheck any lease or source snapshot after this function returns.
1108
1190
  * Resolves with the `performance.now()` time at which BEGIN IMMEDIATE succeeded,