@harperfast/harper 5.2.5 → 5.2.7

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 (327) hide show
  1. package/bin/restart.ts +48 -23
  2. package/bin/restartExitWatchdog.ts +99 -0
  3. package/bin/run.ts +16 -10
  4. package/dist/bin/restart.js +39 -21
  5. package/dist/bin/restart.js.map +1 -1
  6. package/dist/bin/restartExitWatchdog.d.ts +5 -0
  7. package/dist/bin/restartExitWatchdog.js +100 -0
  8. package/dist/bin/restartExitWatchdog.js.map +1 -0
  9. package/dist/bin/run.d.ts +2 -0
  10. package/dist/bin/run.js +17 -10
  11. package/dist/bin/run.js.map +1 -1
  12. package/dist/resources/DatabaseTransaction.d.ts +18 -1
  13. package/dist/resources/DatabaseTransaction.js +63 -1
  14. package/dist/resources/DatabaseTransaction.js.map +1 -1
  15. package/dist/resources/LMDBTransaction.d.ts +2 -0
  16. package/dist/resources/LMDBTransaction.js +7 -1
  17. package/dist/resources/LMDBTransaction.js.map +1 -1
  18. package/dist/resources/PrimaryRocksDatabase.js +2 -3
  19. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  20. package/dist/resources/RecordEncoder.d.ts +16 -0
  21. package/dist/resources/RecordEncoder.js +93 -3
  22. package/dist/resources/RecordEncoder.js.map +1 -1
  23. package/dist/resources/Resource.js +5 -4
  24. package/dist/resources/Resource.js.map +1 -1
  25. package/dist/resources/ResourceInterface.d.ts +5 -0
  26. package/dist/resources/ResourceInterface.js.map +1 -1
  27. package/dist/resources/Table.js +106 -9
  28. package/dist/resources/Table.js.map +1 -1
  29. package/dist/resources/graphql.js +1 -1
  30. package/dist/resources/graphql.js.map +1 -1
  31. package/dist/resources/tracked.js +5 -0
  32. package/dist/resources/tracked.js.map +1 -1
  33. package/dist/resources/transaction.js +1 -1
  34. package/dist/resources/transaction.js.map +1 -1
  35. package/dist/server/threads/manageThreads.d.ts +1 -0
  36. package/dist/server/threads/manageThreads.js +20 -3
  37. package/dist/server/threads/manageThreads.js.map +1 -1
  38. package/dist/utility/processManagement/processManagement.js +21 -3
  39. package/dist/utility/processManagement/processManagement.js.map +1 -1
  40. package/npm-shrinkwrap.json +6 -6
  41. package/package.json +2 -2
  42. package/resources/DESIGN.md +24 -19
  43. package/resources/DatabaseTransaction.ts +63 -2
  44. package/resources/LMDBTransaction.ts +7 -1
  45. package/resources/PrimaryRocksDatabase.ts +3 -3
  46. package/resources/RecordEncoder.ts +85 -3
  47. package/resources/Resource.ts +6 -5
  48. package/resources/ResourceInterface.ts +5 -0
  49. package/resources/Table.ts +117 -9
  50. package/resources/graphql.ts +1 -1
  51. package/resources/tracked.ts +4 -0
  52. package/resources/transaction.ts +2 -2
  53. package/server/DESIGN.md +6 -0
  54. package/server/threads/manageThreads.js +17 -3
  55. package/studio/web/assets/{Chat-Lgj1d-8d.js → Chat-etJeJv4a.js} +71 -95
  56. package/studio/web/assets/FloatingChat-BvmHsuVo.js +23 -0
  57. package/studio/web/assets/abnfDiagram-VCTEODGH-C0_BAZyO.js +1 -0
  58. package/studio/web/assets/{alertDialog-BaBrZQ4M.js → alertDialog-DIHt7Z0r.js} +1 -1
  59. package/studio/web/assets/apiToken-Bl5LC8EH.js +1 -0
  60. package/studio/web/assets/applications-Dta6Y0Rg.js +296 -0
  61. package/studio/web/assets/arc-CVRByUzA.js +1 -0
  62. package/studio/web/assets/architecture-7GRP2DOG-DNdx5tEU.js +1 -0
  63. package/studio/web/assets/architectureDiagram-5GKGNRK7-BWzrASgm.js +36 -0
  64. package/studio/web/assets/authStore-qKmCZcaf.js +3 -0
  65. package/studio/web/assets/blockDiagram-NRAW4CY4-BdJX9Khj.js +129 -0
  66. package/studio/web/assets/{button-DfBmT4rc.js → button-DhiX-njv.js} +1 -1
  67. package/studio/web/assets/c4Diagram-UCG6FXSJ-CI6MzGmQ.js +38 -0
  68. package/studio/web/assets/channel-DtCV8PTL.js +1 -0
  69. package/studio/web/assets/chevron-up-Df2c8uoU.js +1 -0
  70. package/studio/web/assets/chunk-75Z2AOVW-BT8tVmks.js +2 -0
  71. package/studio/web/assets/chunk-DU6HZSFF-9kAOOmI6.js +127 -0
  72. package/studio/web/assets/{chunk-2GRJ4B5K-DtwG__pv.js → chunk-F27PBJKO-BW7ao8AY.js} +1 -1
  73. package/studio/web/assets/{chunk-KEIR6QF5-BICK3FdT.js → chunk-FOHPRMQF-DHwB1DNv.js} +2 -2
  74. package/studio/web/assets/{chunk-5RXB4S5H-D3c7okHC.js → chunk-G27WJ6UU-COyLMcgK.js} +2 -2
  75. package/studio/web/assets/chunk-GMAD6QVW-BNyXpoQO.js +72 -0
  76. package/studio/web/assets/chunk-JQ64N6SF-Cyz1IeLf.js +1 -0
  77. package/studio/web/assets/{chunk-GF5L2VYU-DreJ7Snz.js → chunk-LCL6LL3I-HOzK_ppE.js} +3 -3
  78. package/studio/web/assets/chunk-OBVCFTLP-D4wWOqDQ.js +1 -0
  79. package/studio/web/assets/chunk-P2QGCYS3-DmIFY4d7.js +1 -0
  80. package/studio/web/assets/{chunk-KBJHAD2P-CLDPbAA-.js → chunk-POPQ4Y6H-BPrvMyKz.js} +1 -1
  81. package/studio/web/assets/{chunk-4I5QYGJK-D1FhvtQq.js → chunk-PWAF6VOD-2zB6IW9i.js} +1 -1
  82. package/studio/web/assets/chunk-R7TYR2AO-Irip67yr.js +10 -0
  83. package/studio/web/assets/chunk-RHFEMEQ7-2FgyI8YU.js +168 -0
  84. package/studio/web/assets/{chunk-6Q2QTUOP-QviMM6bR.js → chunk-SVP7TREG-FwtbH2QC.js} +1 -1
  85. package/studio/web/assets/chunk-TEH6E4GO-P87k5mNi.js +62 -0
  86. package/studio/web/assets/{chunk-XXDRQBXY-CaP_jixK.js → chunk-XXDRQBXY-BoGZu3HH.js} +1 -1
  87. package/studio/web/assets/classDiagram-DTDB5LWJ-DbO_dCNE.js +1 -0
  88. package/studio/web/assets/classDiagram-v2-JRS7N3AN-DbO_dCNE.js +1 -0
  89. package/studio/web/assets/{cose-bilkent-JH36ORCC-BVTNhTKD.js → cose-bilkent-JH36ORCC-Cuuf67Bh.js} +1 -1
  90. package/studio/web/assets/{createLucideIcon-BMkDbMrz.js → createLucideIcon-BKGPfjm2.js} +1 -1
  91. package/studio/web/assets/cssMode-IAbO_2KK.js +1 -0
  92. package/studio/web/assets/cynefin-OW5HDTMX-BRkpLFQV.js +1 -0
  93. package/studio/web/assets/cynefinDiagram-5FMLGOSQ-CHT1DaX6.js +62 -0
  94. package/studio/web/assets/cytoscape.esm-C2x7inlt.js +321 -0
  95. package/studio/web/assets/dagre-3AP2YEHR-DpUXBh63.js +4 -0
  96. package/studio/web/assets/dagre-PrKaheQc.js +1 -0
  97. package/studio/web/assets/diagram-S7CK7UJ4-BuymVFZT.js +30 -0
  98. package/studio/web/assets/diagram-UQ7AKVKN-CyP148RM.js +41 -0
  99. package/studio/web/assets/diagram-VSXAHHWV-CoCAg3M9.js +3 -0
  100. package/studio/web/assets/diagram-VX7I27RA-BpOqCFca.js +24 -0
  101. package/studio/web/assets/diagram-Z3DM3KII-Bfpw7Vbj.js +24 -0
  102. package/studio/web/assets/dialog-CBf0Mr1d.js +1 -0
  103. package/studio/web/assets/{dist-CeoU7cfW.js → dist-kYfyPZP9.js} +1 -1
  104. package/studio/web/assets/{dist-cY0BPcz0.js → dist-lkA3O3eM.js} +1 -1
  105. package/studio/web/assets/download-BtTOBem-.js +1 -0
  106. package/studio/web/assets/ebnfDiagram-PWID7BFC-DS_6aWqL.js +1 -0
  107. package/studio/web/assets/editor-CeRXEhRl.js +1 -0
  108. package/studio/web/assets/erDiagram-SSCWMZ5O-DJNk6Fgw.js +99 -0
  109. package/studio/web/assets/eventmodeling-NTZA5JFV-5jbe4A5P.js +1 -0
  110. package/studio/web/assets/flowDiagram-A5DVABFB-Dp9Ezlow.js +1 -0
  111. package/studio/web/assets/{ganttDiagram-PKOTCBZU-Cit41Zp3.js → ganttDiagram-EL5Y4UJY-2pOExxMY.js} +2 -2
  112. package/studio/web/assets/{getAnalytics-rTxZLIM5.js → getAnalytics-D4LKGeVy.js} +1 -1
  113. package/studio/web/assets/gitGraph-4MIJSDKK-CH5ZxwzF.js +1 -0
  114. package/studio/web/assets/gitGraphDiagram-WWUBYQGX-DVIsIhbO.js +106 -0
  115. package/studio/web/assets/html-DOV4s_YT.js +1 -0
  116. package/studio/web/assets/htmlMode-CdqjnAh4.js +1 -0
  117. package/studio/web/assets/index-CTqasm6g.js +824 -0
  118. package/studio/web/assets/index-Cxj2_wsl.css +1 -0
  119. package/studio/web/assets/index.lazy-VmcKgiEy.js +2 -0
  120. package/studio/web/assets/info-A6RAGUB7-CPQfTnaG.js +1 -0
  121. package/studio/web/assets/infoDiagram-RXCK75RN-DlwLYlwm.js +2 -0
  122. package/studio/web/assets/{ishikawaDiagram-WSZJBQD7-BJYRB-hU.js → ishikawaDiagram-5VMMS53U-BRXRp29U.js} +2 -2
  123. package/studio/web/assets/{javascript-CGzSV8Sd.js → javascript-Dj7yXfx5.js} +1 -1
  124. package/studio/web/assets/{journeyDiagram-NVQOT4AX-B8K4wuMz.js → journeyDiagram-EYS64GPL-B0ou8k0n.js} +1 -1
  125. package/studio/web/assets/jsonMode-khk65qzx.js +1 -0
  126. package/studio/web/assets/{kanban-definition-27J2QSJJ-DKFV1ovM.js → kanban-definition-3QL26DDD-uYg7iYzp.js} +11 -11
  127. package/studio/web/assets/languageServices-D_06p7dw.js +1 -0
  128. package/studio/web/assets/linear-Cnt8506Q.js +1 -0
  129. package/studio/web/assets/lspLanguageFeatures-DTA6wyQw.js +4 -0
  130. package/studio/web/assets/mermaid-parser.core-Ck-fC8b7.js +7 -0
  131. package/studio/web/assets/mermaid.core-CP8aNNYm.js +44 -0
  132. package/studio/web/assets/{mindmap-definition-FAOFIHXS-CKL4PdAv.js → mindmap-definition-FBJOCRG2-CgTZ-rit.js} +25 -25
  133. package/studio/web/assets/notifications-Bah46LXX.js +1 -0
  134. package/studio/web/assets/notifications-CBZXgxVc.js +1 -0
  135. package/studio/web/assets/packet-AYTQ26CC-DEyoPtPb.js +1 -0
  136. package/studio/web/assets/pegDiagram-XKGWAZYB-DrD-7sD9.js +1 -0
  137. package/studio/web/assets/pie-WAS4IAKB-wjj-EI1d.js +1 -0
  138. package/studio/web/assets/pieDiagram-E7YTZNPT-GntqDCzv.js +39 -0
  139. package/studio/web/assets/profile-QspOun_j.js +1 -0
  140. package/studio/web/assets/{quadrantDiagram-CIZ2JOQS-CSbsuWtH.js → quadrantDiagram-AXDQQJYC-0UeqXQGd.js} +1 -1
  141. package/studio/web/assets/radar-RG4KPBEZ-DFSA5h7k.js +1 -0
  142. package/studio/web/assets/railroad-74A4TZTK-CaOUG9wR.js +1 -0
  143. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bc0Qi0WH.js +1 -0
  144. package/studio/web/assets/railroad-ebnf-LZEXJU2U-G8rVVZ2C.js +1 -0
  145. package/studio/web/assets/railroad-peg-WCYAUIDC-CrehKBhC.js +1 -0
  146. package/studio/web/assets/railroadDiagram-O6MQD6OU-yHUELZaV.js +1 -0
  147. package/studio/web/assets/regions-QdUi7bQ9.js +1 -0
  148. package/studio/web/assets/{editor.api-eUd8PqEu.js → register-Buvoxmb8.js} +139 -138
  149. package/studio/web/assets/{requirementDiagram-LRYGKXZP-ZIF0Wlho.js → requirementDiagram-EFPCY7ZU-DNEGFjuW.js} +2 -2
  150. package/studio/web/assets/rolldown-runtime-hePW80VL.js +1 -0
  151. package/studio/web/assets/safeParse-DeaoCCWH.js +1 -0
  152. package/studio/web/assets/{sankeyDiagram-W5VNT64P-BcRESt9x.js → sankeyDiagram-P5KCCOFB-DpyAmSVR.js} +1 -1
  153. package/studio/web/assets/{sequenceDiagram-SI44F4Z6-OpgTeJNU.js → sequenceDiagram-WJ2MYXX4-TyaT7xNk.js} +1 -1
  154. package/studio/web/assets/setComponentFile-BI4E_iDe.js +1 -0
  155. package/studio/web/assets/setLocalStorage-D_kflv4U.js +1 -0
  156. package/studio/web/assets/setSessionStorage-B0bf71m4.js +1 -0
  157. package/studio/web/assets/setup-Cop6LBQN.js +2 -0
  158. package/studio/web/assets/src-CMc7m_Ut.js +1 -0
  159. package/studio/web/assets/stateDiagram-HBIQ2CUA-CeEdTArZ.js +1 -0
  160. package/studio/web/assets/stateDiagram-v2-4QOOHH4V-D4tuw9Su.js +1 -0
  161. package/studio/web/assets/status-DRu3zRgl.js +61 -0
  162. package/studio/web/assets/step-vX8bovaY.js +1 -0
  163. package/studio/web/assets/swimlanes-XN3QIQJK-B54FmF46.js +1 -0
  164. package/studio/web/assets/swimlanesDiagram-VK2B7HYN-XOhmNEvq.js +8 -0
  165. package/studio/web/assets/tabs-B_G5zscN.js +1 -0
  166. package/studio/web/assets/{time-D55sDum-.js → time-Cclkb7kQ.js} +1 -1
  167. package/studio/web/assets/{timeline-definition-Z64GVDOM-Dy5A_tK2.js → timeline-definition-24CTP7MA-D-a9ujbo.js} +5 -5
  168. package/studio/web/assets/toggleHighContrast-C0dJLmZ0.js +49 -0
  169. package/studio/web/assets/treeView-Q6P3EWNA-CrW_6JnS.js +1 -0
  170. package/studio/web/assets/treemap-WGGIJYW6-BxyYLdP_.js +1 -0
  171. package/studio/web/assets/tsMode-dbDiapTA.js +11 -0
  172. package/studio/web/assets/typescript-CT1826cL.js +1 -0
  173. package/studio/web/assets/useEntityRestURL-D1u1X_gE.js +1 -0
  174. package/studio/web/assets/useLocalStorage-Dtj1QS8_.js +1 -0
  175. package/studio/web/assets/{vendor-core-CddkYGh5.js → vendor-core-RCcadM3e.js} +1 -1
  176. package/studio/web/assets/vendor-datadog-BRv-mOv1.js +6 -0
  177. package/studio/web/assets/{vendor-react-BhazFIp8.js → vendor-react-Dyj4O3HE.js} +1 -1
  178. package/studio/web/assets/vendor-tanstack-BiFWSB3W.js +1 -0
  179. package/studio/web/assets/{vendor-ui-BLIveqaL.js → vendor-ui-vhu-UHhF.js} +11 -11
  180. package/studio/web/assets/{vennDiagram-T6HMQDX7-DRvMIiSo.js → vennDiagram-4TSXK5OY-Cy7s7Mpy.js} +3 -3
  181. package/studio/web/assets/wardley-WFR3VGLG-BeBL35g2.js +1 -0
  182. package/studio/web/assets/{wardleyDiagram-T6FBY63Y-B04FSA0i.js → wardleyDiagram-VM6X3IG4-BylmIGSg.js} +3 -3
  183. package/studio/web/assets/workers-FpR9_D2K.js +3 -0
  184. package/studio/web/assets/x-B9o9hsep.js +1 -0
  185. package/studio/web/assets/xml-ZjKUMjth.js +1 -0
  186. package/studio/web/assets/{xychartDiagram-ELKLHX3M-BE0X7RA7.js → xychartDiagram-S5SC5T6Z-CoKALMXr.js} +3 -3
  187. package/studio/web/assets/yaml-B6gXbZU6.js +1 -0
  188. package/studio/web/assets/{yaml.worker-Iyy2Pf1T.js → yaml.worker-BDL8_iuk.js} +153 -153
  189. package/studio/web/harper-studio_black.png +0 -0
  190. package/studio/web/index.html +18 -24
  191. package/utility/processManagement/processManagement.js +19 -3
  192. package/studio/web/assets/FloatingChat-jLWvR4kI.js +0 -23
  193. package/studio/web/assets/abnfDiagram-N423BO3Z-BRuH0ywn.js +0 -1
  194. package/studio/web/assets/apiToken-DYI4DqWa.js +0 -1
  195. package/studio/web/assets/applications-DCmc9-lk.js +0 -296
  196. package/studio/web/assets/arc-3Xw6uabx.js +0 -1
  197. package/studio/web/assets/architecture-TIHT7OUA-CsHfPErM.js +0 -1
  198. package/studio/web/assets/architectureDiagram-T3A2C74G-DrwbS7Gp.js +0 -36
  199. package/studio/web/assets/array-BifhSqXX.js +0 -1
  200. package/studio/web/assets/authStore-B3IhXrn6.js +0 -3
  201. package/studio/web/assets/blockDiagram-VBNYF7ZC-DKN1mht5.js +0 -132
  202. package/studio/web/assets/c4Diagram-5PPSVZJV-CT9bmjm_.js +0 -10
  203. package/studio/web/assets/card-ByrPS_wa.js +0 -1
  204. package/studio/web/assets/channel-mnKBfsP-.js +0 -1
  205. package/studio/web/assets/chevron-right-CY-tr2sG.js +0 -1
  206. package/studio/web/assets/chunk-3NCLNEKW-BVj72ixc.js +0 -1
  207. package/studio/web/assets/chunk-5FCAYU7R-Dh5nylU8.js +0 -1
  208. package/studio/web/assets/chunk-5HE753X5-Bw-yg4kO.js +0 -1
  209. package/studio/web/assets/chunk-5JV3BV7I-Cy5pj3QY.js +0 -1
  210. package/studio/web/assets/chunk-5TONJI2A-DQOY3q1H.js +0 -2
  211. package/studio/web/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
  212. package/studio/web/assets/chunk-7Z6QIM7H-BJhESwQK.js +0 -10
  213. package/studio/web/assets/chunk-BIQX33UG-DZEsGuSn.js +0 -1
  214. package/studio/web/assets/chunk-CQNSW5MT-B8S09OIh.js +0 -1
  215. package/studio/web/assets/chunk-CYSBUYHQ-C1Rf4art.js +0 -1
  216. package/studio/web/assets/chunk-EMLP6XTP-RLD4nXpI.js +0 -1
  217. package/studio/web/assets/chunk-I66GZJ75-DHPYnKNr.js +0 -125
  218. package/studio/web/assets/chunk-J7OUQ5F2-DcY_kb6i.js +0 -2
  219. package/studio/web/assets/chunk-JG7HCLWE-BYfiSaVt.js +0 -2
  220. package/studio/web/assets/chunk-JQJVKLGR-DtEmbHZj.js +0 -156
  221. package/studio/web/assets/chunk-MOZMSUNE-XVucA-n6.js +0 -1
  222. package/studio/web/assets/chunk-NSK5VX7P-DDFNzCU_.js +0 -2
  223. package/studio/web/assets/chunk-OSBZ3O6U-CrKZOUOr.js +0 -1
  224. package/studio/web/assets/chunk-QBLGF6JB-CxAyS2mW.js +0 -1
  225. package/studio/web/assets/chunk-QR6OTTB3-Df7OCENk.js +0 -62
  226. package/studio/web/assets/chunk-R7FJI6CG-Cnn3gE-Y.js +0 -1
  227. package/studio/web/assets/chunk-RYQCIY6F-N0MDKk5G.js +0 -1
  228. package/studio/web/assets/chunk-U6XO7XAA-BFxjP5U3.js +0 -2
  229. package/studio/web/assets/chunk-UBXNYLIW-rn0yw1mp.js +0 -1
  230. package/studio/web/assets/chunk-W5SLKNZC-DKMlAWZk.js +0 -1
  231. package/studio/web/assets/chunk-WRU74C26-Dg10j8Pf.js +0 -70
  232. package/studio/web/assets/chunk-YOTPTUD7-D0vRKqYp.js +0 -1
  233. package/studio/web/assets/chunk-ZIRB5QZD-O39ZBUk3.js +0 -32
  234. package/studio/web/assets/classDiagram-JCYQIIEL-BrXNP4fl.js +0 -1
  235. package/studio/web/assets/classDiagram-v2-OCEON4UE-BrXNP4fl.js +0 -1
  236. package/studio/web/assets/cssMode-DipmMR_-.js +0 -1
  237. package/studio/web/assets/cynefin-VYW2F7L2-Ds2Fw2Y0.js +0 -1
  238. package/studio/web/assets/cynefinDiagram-MW4NZA55-DcTzrsC5.js +0 -62
  239. package/studio/web/assets/cytoscape.esm-B-NFISlW.js +0 -321
  240. package/studio/web/assets/dagre-Buvkdvvj.js +0 -1
  241. package/studio/web/assets/dagre-VZM6K2ZE-CMyJ9QPU.js +0 -4
  242. package/studio/web/assets/diagram-7IWD3JNH-jEGuzdp3.js +0 -30
  243. package/studio/web/assets/diagram-B4RE2ZJO-Ba7wC7iw.js +0 -3
  244. package/studio/web/assets/diagram-LBJQPF4R-Bm7Wj_dF.js +0 -24
  245. package/studio/web/assets/diagram-Q27KOJAE-C-gdktLC.js +0 -24
  246. package/studio/web/assets/diagram-UB23O5K3-COBVIEEk.js +0 -41
  247. package/studio/web/assets/ebnfDiagram-BXEA7PRR-G-t56mVV.js +0 -1
  248. package/studio/web/assets/editor-BDfNodZS.js +0 -1
  249. package/studio/web/assets/erDiagram-JOGREHBK-DpHzpqx1.js +0 -85
  250. package/studio/web/assets/eventmodeling-45OFAUF4-CLEiVx5s.js +0 -1
  251. package/studio/web/assets/flowDiagram-UKHOOZJN-CIxxUUxn.js +0 -1
  252. package/studio/web/assets/getRegistrationInfo--NLLrics.js +0 -1
  253. package/studio/web/assets/gitGraph-TEB2WS4Q-CZFBqwW-.js +0 -1
  254. package/studio/web/assets/gitGraphDiagram-DS77QQ5N-DuGnRv2R.js +0 -106
  255. package/studio/web/assets/html-DmSSmgE8.js +0 -1
  256. package/studio/web/assets/htmlMode-B2LqJw1z.js +0 -1
  257. package/studio/web/assets/index-BO3NaEw8.css +0 -1
  258. package/studio/web/assets/index-BnYZFLIB.css +0 -1
  259. package/studio/web/assets/index-CqbCnqsT.js +0 -810
  260. package/studio/web/assets/index.lazy-BNqr3j0f.js +0 -13
  261. package/studio/web/assets/info-DKCQHKI2--1InqQeT.js +0 -1
  262. package/studio/web/assets/infoDiagram-6WML65LV-DliK38kp.js +0 -2
  263. package/studio/web/assets/isArrayLikeObject-BHDnhloL.js +0 -1
  264. package/studio/web/assets/jsonMode-BVS6P5Xk.js +0 -1
  265. package/studio/web/assets/languageServices-Bl-BvoAb.js +0 -1
  266. package/studio/web/assets/line-Bom3IjHi.js +0 -1
  267. package/studio/web/assets/linear-DwB_ruUT.js +0 -1
  268. package/studio/web/assets/lspLanguageFeatures-C7XtZA_w.js +0 -4
  269. package/studio/web/assets/map-BaFkSB1l.js +0 -1
  270. package/studio/web/assets/math-Bi_r_ZWc.js +0 -1
  271. package/studio/web/assets/mermaid-parser.core-Dd4RYr9Q.js +0 -4
  272. package/studio/web/assets/mermaid.core-Bbo24DGo.js +0 -11
  273. package/studio/web/assets/notifications-Bb2cj14_.js +0 -1
  274. package/studio/web/assets/notifications-DVlKonbI.js +0 -1
  275. package/studio/web/assets/packet-7NZHBO7P-BGMXB0XL.js +0 -1
  276. package/studio/web/assets/path-fybaL0A-.js +0 -1
  277. package/studio/web/assets/pegDiagram-VL7TDLO6-CWEktcBB.js +0 -1
  278. package/studio/web/assets/pie-RZYD4A2V-DgYg3tCt.js +0 -1
  279. package/studio/web/assets/pieDiagram-7S7Q4E2Y-DEAmZOqY.js +0 -39
  280. package/studio/web/assets/pollUnlessForbidden-DIDXGBc5.js +0 -1
  281. package/studio/web/assets/profile-BSQWfYfq.js +0 -1
  282. package/studio/web/assets/purify.es-DrVcFY1Q.js +0 -3
  283. package/studio/web/assets/queryClient-BLhQvmWC.js +0 -1
  284. package/studio/web/assets/radar-I7S5WNFK-Ag-zp_Rq.js +0 -1
  285. package/studio/web/assets/railroad-3IZDKUUU-aibJDIDV.js +0 -1
  286. package/studio/web/assets/railroad-abnf-AHOZXSZD-BBMEuvIl.js +0 -1
  287. package/studio/web/assets/railroad-ebnf-EBAXGLYW-Be1nseej.js +0 -1
  288. package/studio/web/assets/railroad-peg-LSFZ7HO6-DIvD-Eyb.js +0 -1
  289. package/studio/web/assets/railroadDiagram-AXF67PYL-BJWrgR10.js +0 -1
  290. package/studio/web/assets/react-VeAY02s6.js +0 -1
  291. package/studio/web/assets/react-redux-Ds7wf7Ax.js +0 -5
  292. package/studio/web/assets/regions-Dk3g8bBF.js +0 -1
  293. package/studio/web/assets/register-3F1Tje_F.js +0 -2
  294. package/studio/web/assets/register-S_PdXsoi.js +0 -2
  295. package/studio/web/assets/rolldown-runtime-B0Z9INg1.js +0 -1
  296. package/studio/web/assets/select-CKgJgpkn.js +0 -1
  297. package/studio/web/assets/setComponentFile-DoITgNLb.js +0 -1
  298. package/studio/web/assets/setLocalStorage-CD_L8p_D.js +0 -1
  299. package/studio/web/assets/setup-CiEALLa7.js +0 -2
  300. package/studio/web/assets/src-DUvBCJIr.js +0 -1
  301. package/studio/web/assets/stateDiagram-OKZ733FA-D9oPLTjO.js +0 -1
  302. package/studio/web/assets/stateDiagram-v2-UEYNNEHI-CSD1wbuZ.js +0 -1
  303. package/studio/web/assets/status-CK9tD-Nd.js +0 -61
  304. package/studio/web/assets/string-f6JzSHaO.js +0 -1
  305. package/studio/web/assets/swagger-ui-react-CxjCDFB-.js +0 -240
  306. package/studio/web/assets/swimlanes-SLNWSIFB-BpeoNE9k.js +0 -2
  307. package/studio/web/assets/swimlanesDiagram-ULZ7WXOC-DHxwzxpp.js +0 -8
  308. package/studio/web/assets/table-BvFflPWi.js +0 -1
  309. package/studio/web/assets/tabs-Dd_ElZ36.js +0 -1
  310. package/studio/web/assets/textarea-a3RnTY31.js +0 -9
  311. package/studio/web/assets/toggleHighContrast-CyNC3SCQ.js +0 -49
  312. package/studio/web/assets/tokenization-CWuvR2gh.js +0 -7
  313. package/studio/web/assets/treeView-QDETBFTQ-uHZ-3Fz8.js +0 -1
  314. package/studio/web/assets/treemap-6X3UGDF4-BnUKyy3l.js +0 -1
  315. package/studio/web/assets/tsMode-CslzhcVk.js +0 -11
  316. package/studio/web/assets/typescript-Dtnury3t.js +0 -1
  317. package/studio/web/assets/useAuth-DiJUZS1C.js +0 -1
  318. package/studio/web/assets/useEntityRestURL-Cy-0i1UZ.js +0 -1
  319. package/studio/web/assets/useLocalStorage-DetQlhBe.js +0 -1
  320. package/studio/web/assets/vendor-datadog-XDrpA25D.js +0 -6
  321. package/studio/web/assets/vendor-tanstack-FsM5XZNE.js +0 -1
  322. package/studio/web/assets/wardley-OPB4EBWU-Da7TLC2F.js +0 -1
  323. package/studio/web/assets/workers-BtibQGyk.js +0 -3
  324. package/studio/web/assets/x-D39AGKMb.js +0 -1
  325. package/studio/web/assets/xml-D41zWqPl.js +0 -1
  326. package/studio/web/assets/yaml-BFQ3mF1U.js +0 -1
  327. /package/studio/web/assets/{editor-jjEx9u7D.css → register-jjEx9u7D.css} +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@harperfast/harper",
3
3
  "description": "Harper is an open-source Node.js performance platform that unifies database, cache, application, and messaging layers into one in-memory process.",
4
- "version": "5.2.5",
4
+ "version": "5.2.7",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://harper.fast",
7
7
  "bugs": {
@@ -244,7 +244,7 @@
244
244
  "ses": "^1.15.0",
245
245
  "stream-chain": "2.2.5",
246
246
  "stream-json": "1.9.1",
247
- "structon": "1.0.8",
247
+ "structon": "1.1.0",
248
248
  "systeminformation": "^5.31.4",
249
249
  "tar-fs": "^3.1.2",
250
250
  "tar-stream": "^3.1.8",
@@ -78,25 +78,25 @@ One giant `makeTable()` factory that returns a `TableResource extends Resource`
78
78
 
79
79
  ## "Where is X" cheat sheet
80
80
 
81
- | Question | Where |
82
- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
- | How is a CRUD request authorized? | `Table.ts → #section: authz-hooks`; defaults in `Resource.ts` (`allowRead` etc.) |
84
- | Where does versioning / conflict resolution happen? | `Table.ts → _writeUpdate` (`#section: write-path-internals`) |
85
- | How does `search()` choose an index? | `Table.ts → search` (`#section: search-query`) |
86
- | How are subscriptions replayed? | `Table.ts → subscribe` (`#section: pub-sub`) |
87
- | How is the response body shaped (select clause)? | `Table.ts → transformEntryForSelect` (`#section: search-query`) |
88
- | Where is record-level TTL evaluated? | `Table.ts → setTTLExpiration` (`#section: lifecycle-admin`); `Updatable.getExpiresAt` (`#section: setup-and-factory`). Stored expiry metadata is resolved in the `_writeUpdate` commit closure: `options.expiresAt ?? context.expiresAt ?? (record @expiresAt field, if finite & ≥ 0) ?? table default`. This metadata drives read-hiding + the cleanup sweep. The `@expiresAt` attribute is authoritative for **direct** put/patch only; cache/source fills persist via `recordUpdater` and derive expiry from `sourceContext.expiresAt` (source freshness / table default), not the field. |
89
- | Why does `search()` hide a row that's past its TTL but not yet swept? | `Table.ts → transformEntryForSelect` unconditionally treats `entry.expiresAt < Date.now()` as gone (lazy eviction on read) — correct for a SELECT, but a mutation locating rows to overwrite needs the opposite: pass `target.includeExpired = true` (read by the SQL engine's `runUpdate`/`runDelete` via `SqlEngineContext.includeExpiredRows`) to treat such a row as a live match, matching the leniency a direct by-id `put`/`patch` already has (they skip this check entirely, since `Resource.patch`'s static options don't request `ensureLoaded`). |
90
- | How are residencies enforced (replication)? | `Table.ts → #section: lifecycle-admin` (residency block: `getResidencyRecord`, `setResidency`, `setResidencyById`, `getResidency`) |
91
- | How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`) |
92
- | Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage) |
93
- | What is in `context.transaction` when no scope owns one? | Whatever the last table call left there. A completed scope leaves `RELEASED_TRANSACTION` (`DatabaseTransaction.ts`), which reads latest and no-ops on commit. The next table call finds no transaction and `Table.ts → txnForContext` installs an `ImmediateTransaction` in the slot. A **fresh** one reports `open === OPEN`, so `transaction()` and `Resource`'s dispatcher **join** it instead of starting a scope, and the write commits on its own; that commit closes it for good (nothing reopens this class), so later calls stop joining and start their own scope while `txnForContext` keeps returning it for reads. It is also the only transaction that opens its native handle inside its own `commit()` (its `getReadTxn` never opens one), which is why `commit()` re-reads `this.transaction` after its save loop (#2288). |
94
- | How does a query opt out of a read snapshot? | Pass `snapshot: false` on the search request (e.g. `get_analytics`). `Table.ts → search` calls `txn.useReadTxn(snapshot === false)`; on RocksDB `DatabaseTransaction.getReadTxn` then builds the read txn with `{ disableSnapshot: true }` so a long scan reads latest without pinning a snapshot. No-op on LMDB (`LMDBTransaction.useReadTxn`). |
95
- | How does a URL path map to a Resource? | `Resources.ts → getMatch` (exact/prefix fast path) then `matchParamRoute` (parameterised routes); see "Path routing" below |
96
- | How does HNSW keep the graph connected on delete? | `indexes/HierarchicalNavigableSmallWorld.ts → index()` delete path: zero-degree orphans reindexed via `needsReindexing`; severed multi-node islands detected and reconnected by `repairSeveredNeighbors` (#1712) |
97
- | How is a filter applied _during_ a vector search? | Predicate-aware traversal (#1241): `search.ts → executeConditions` composes companion AND conditions with request `vectorFilter` / `rowFilter` predicates into one `(primaryKey) => boolean` (`composeRecordFilter`) and passes it to `HierarchicalNavigableSmallWorld.search(cond, ctx, filter)`. The filter gates result admission at layer 0 only (routing ignores it, ACORN-style); a visit budget (`filterExpansion`) bounds the under-filled/selective case. Very selective _condition_ filters are instead diverted to the exact brute-force path by the query planner's `estimateCountAsSort` ordering. |
98
- | How does post-ordering resolve vector distances safely? | Each comparator owns its `Sort`, passes it directly to the custom-index resolver, and caches distances by that immutable per-query sort object. |
99
- | How is application row filtering applied? | Authorization admission happens in the resource operation before query work. The legacy `allow*` hook, when armed by the protocol, is evaluated once with its historical receiver semantics; overriding it never changes its scope. An operation override may add indexed conditions and/or attach the JavaScript-only synchronous `target.rowFilter(record, context)`. `Table.search` composes it with query filters and rechecks the final materialized cache/source record. `SubscriptionRequest.rowFilter` covers full-row events; `eventFilter(event, context)` explicitly handles tombstones/messages/raw events. Prefer indexed conditions because an opaque predicate may inspect every admitted candidate and `limit` applies after filtering. |
81
+ | Question | Where |
82
+ | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | How is a CRUD request authorized? | `Table.ts → #section: authz-hooks`; defaults in `Resource.ts` (`allowRead` etc.) |
84
+ | Where does versioning / conflict resolution happen? | `Table.ts → _writeUpdate` (`#section: write-path-internals`) |
85
+ | How does `search()` choose an index? | `Table.ts → search` (`#section: search-query`) |
86
+ | How are subscriptions replayed? | `Table.ts → subscribe` (`#section: pub-sub`) |
87
+ | How is the response body shaped (select clause)? | `Table.ts → transformEntryForSelect` (`#section: search-query`) |
88
+ | Where is record-level TTL evaluated? | `Table.ts → setTTLExpiration` (`#section: lifecycle-admin`); `Updatable.getExpiresAt` (`#section: setup-and-factory`). Stored expiry metadata is resolved in the `_writeUpdate` commit closure: `options.expiresAt ?? context.expiresAt ?? (record @expiresAt field, if finite &amp; ≥ 0) ?? table default`. This metadata drives read-hiding + the cleanup sweep. The `@expiresAt` attribute is authoritative for **direct** put/patch only; cache/source fills persist via `recordUpdater` and derive expiry from `sourceContext.expiresAt` (source freshness / table default), not the field. |
89
+ | Why does `search()` hide a row that's past its TTL but not yet swept? | `Table.ts → transformEntryForSelect` unconditionally treats `entry.expiresAt < Date.now()` as gone (lazy eviction on read) — correct for a SELECT, but a mutation locating rows to overwrite needs the opposite: pass `target.includeExpired = true` (read by the SQL engine's `runUpdate`/`runDelete` via `SqlEngineContext.includeExpiredRows`) to treat such a row as a live match, matching the leniency a direct by-id `put`/`patch` already has (they skip this check entirely, since `Resource.patch`'s static options don't request `ensureLoaded`). |
90
+ | How are residencies enforced (replication)? | `Table.ts → #section: lifecycle-admin` (residency block: `getResidencyRecord`, `setResidency`, `setResidencyById`, `getResidency`) |
91
+ | How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`) |
92
+ | Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage) |
93
+ | What is in `context.transaction` when no scope owns one? | Whatever the last table call left there. A completed scope leaves `RELEASED_TRANSACTION` (`DatabaseTransaction.ts`), which reads latest and no-ops on commit. The next table call finds no transaction and `Table.ts → txnForContext` installs an `ImmediateTransaction` in the slot. It reports `open === OPEN`, but `transaction()` and `Resource`'s dispatcher gate on `isJoinableScope` OPEN _and_ staging its writes for a later commit so they never join it and start their own scope instead (#2292); `txnForContext` keeps returning it for reads and for writes that reach no wrapper, and those commit per write. It is also the only transaction that opens its native handle inside its own `commit()` (its `getReadTxn` never opens one), which is why `commit()` re-reads `this.transaction` after its save loop (#2288). |
94
+ | How does a query opt out of a read snapshot? | Pass `snapshot: false` on the search request (e.g. `get_analytics`). `Table.ts → search` calls `txn.useReadTxn(snapshot === false)`; on RocksDB `DatabaseTransaction.getReadTxn` then builds the read txn with `{ disableSnapshot: true }` so a long scan reads latest without pinning a snapshot. No-op on LMDB (`LMDBTransaction.useReadTxn`). |
95
+ | How does a URL path map to a Resource? | `Resources.ts → getMatch` (exact/prefix fast path) then `matchParamRoute` (parameterised routes); see "Path routing" below |
96
+ | How does HNSW keep the graph connected on delete? | `indexes/HierarchicalNavigableSmallWorld.ts → index()` delete path: zero-degree orphans reindexed via `needsReindexing`; severed multi-node islands detected and reconnected by `repairSeveredNeighbors` (#1712) |
97
+ | How is a filter applied _during_ a vector search? | Predicate-aware traversal (#1241): `search.ts → executeConditions` composes companion AND conditions with request `vectorFilter` / `rowFilter` predicates into one `(primaryKey) => boolean` (`composeRecordFilter`) and passes it to `HierarchicalNavigableSmallWorld.search(cond, ctx, filter)`. The filter gates result admission at layer 0 only (routing ignores it, ACORN-style); a visit budget (`filterExpansion`) bounds the under-filled/selective case. Very selective _condition_ filters are instead diverted to the exact brute-force path by the query planner's `estimateCountAsSort` ordering. |
98
+ | How does post-ordering resolve vector distances safely? | Each comparator owns its `Sort`, passes it directly to the custom-index resolver, and caches distances by that immutable per-query sort object. |
99
+ | How is application row filtering applied? | Authorization admission happens in the resource operation before query work. The legacy `allow*` hook, when armed by the protocol, is evaluated once with its historical receiver semantics; overriding it never changes its scope. An operation override may add indexed conditions and/or attach the JavaScript-only synchronous `target.rowFilter(record, context)`. `Table.search` composes it with query filters and rechecks the final materialized cache/source record. `SubscriptionRequest.rowFilter` covers full-row events; `eventFilter(event, context)` explicitly handles tombstones/messages/raw events. Prefer indexed conditions because an opaque predicate may inspect every admitted candidate and `limit` applies after filtering. |
100
100
 
101
101
  **QUERY admission uses the body projection.** `Resource.transactional` resolves an asynchronous HTTP QUERY body before resource resolution, recursively clones away client-supplied `checkPermission`, and copies the body's `select` onto the operation admission target before `allowRead`. After authorization, `Resource.query` transfers only the narrowed projection to the body target. This ensures a body-only relationship select is checked before `Table.search`; permission-control fields from QUERY data must never reach a search target.
102
102
 
@@ -166,6 +166,11 @@ Tests: `../unitTests/resources/defineResource.test.js`, `../unitTests/resources/
166
166
 
167
167
  ## Conventions
168
168
 
169
+ - A record's `toJSON` is a response projection, not durable state. `recordUpdater` (and the cache-fill
170
+ and audit paths feeding it) projects records to stored fields via `storedFieldsOnly`, so no durable
171
+ form carries a resolver-owned (`@computed`/`@relationship`) name; materialization skips such names
172
+ when promoting a decoded record (`assignStoredFields`), which is what keeps rows written by affected
173
+ releases readable. See harper#2359 and `integrationTests/resources/cachedComputedAttribute.test.ts`.
169
174
  - **Never** remove `transactional()` from a static method on `Resource` — it owns transaction context lifetime.
170
175
  - New `Resource` subclasses should override **instance** methods (`get`, `put`, ...) for behavior; static methods are the protocol entry points and stay generic.
171
176
  - **Overriding a static entry point takes over its whole contract.** Assigning `static post`/`put`/`patch` on a subclass shadows the `transactional()`-wrapped static, so none of that wrapper runs — including the `when(data, ...)` that resolves `data` and the `allowCreate`/`allowUpdate` gate. That is by design (it is how `login.ts` implements a deliberately pre-authentication endpoint), and it means an override owns two obligations the wrapper would otherwise have met:
@@ -261,7 +261,9 @@ export type TransactionWrite = {
261
261
  before?: () => void | Promise<void>;
262
262
  beforeIntermediate?: () => void | Promise<void>;
263
263
  commit?: (txnTime: number, existingEntry: Partial<Entry>, retry: boolean, transaction: any) => MaybePromise<void>;
264
- validate?: (txnTime: number) => void;
264
+ // Once a write has been taken over, the transaction committing it is not the one that staged it, and
265
+ // overload accounting, the replay marker and a no-op write's removal all belong to the committer.
266
+ validate?: (txnTime: number, committedBy: DatabaseTransaction) => void;
265
267
  fullUpdate?: boolean;
266
268
  saved?: boolean;
267
269
  deferSave?: boolean;
@@ -278,6 +280,10 @@ export type TransactionWrite = {
278
280
  // sticky: a non-isRetry staging of this write appended its audit entry (set in save(); the retry
279
281
  // dedup guards in the commit handler read it to ignore the write's own orphaned entry)
280
282
  appendedAuditEntry?: boolean;
283
+ // the transaction holding this write in its `writes` (set in addWrite). A deferred write's save() is
284
+ // only its trigger, so it can be triggered after the context has moved on to another transaction;
285
+ // this is who commits it when the transaction current at that point is not a scope (#2292).
286
+ stagedIn?: DatabaseTransaction;
281
287
  // the preceding write to the same store and key in this transaction, if any (linked in addWrite)
282
288
  priorWrite?: TransactionWrite;
283
289
  // set only by a write that BOTH reads priorStagedWrite() and publishes stagedEntry; addWrite orders
@@ -361,6 +367,11 @@ export class DatabaseTransaction implements Transaction {
361
367
  timeoutBudget = 0;
362
368
  // save() only stages here; ImmediateTransaction overrides it to commit, which addWrite must not defer
363
369
  saveCommits = false;
370
+ // True where save() puts the write into this transaction's native handle, which is what lets a scope
371
+ // take over a write staged in another transaction's `writes` (Table.ts's #saveOperation).
372
+ // LMDBTransaction's save() is a no-op — its commit applies `writes` — so there a write can only be
373
+ // committed by the transaction that holds it.
374
+ stagesWriteOnSave = true;
364
375
  validated = 0;
365
376
  timestamp = 0;
366
377
  retries = 0;
@@ -590,11 +601,47 @@ export class DatabaseTransaction implements Transaction {
590
601
  writesForStore.set(keyId, operation);
591
602
  }
592
603
 
604
+ /**
605
+ * Drop a staged write from this transaction, and from its per-key chain, so the transaction taking it
606
+ * over becomes its only owner (Table.ts's #saveOperation).
607
+ */
608
+ detachWrite(operation: TransactionWrite): void {
609
+ const index = this.writes.indexOf(operation);
610
+ if (index > -1) this.writes[index] = null;
611
+ if (operation.key === undefined) return;
612
+ const writesForStore = this.writesByKey?.get(operation.store);
613
+ if (!writesForStore) return;
614
+ const keyId = writeKeyId(operation.key);
615
+ // Membership, not `stagedIn`, which every commit handler clears and so cannot tell a takeover from a
616
+ // write done in place; a prior already taken over must not become this transaction's basis again.
617
+ let prior = operation.priorWrite;
618
+ while (prior && !this.writes.includes(prior)) prior = prior.priorWrite;
619
+ const tail = writesForStore.get(keyId);
620
+ if (tail === operation) {
621
+ if (prior) writesForStore.set(keyId, prior);
622
+ else writesForStore.delete(keyId);
623
+ return;
624
+ }
625
+ // A successor left chained to it would take its merge basis and index diff from a record another
626
+ // transaction owns and may roll back (harper#1968's failure class).
627
+ for (let successor = tail; successor; successor = successor.priorWrite) {
628
+ if (successor.priorWrite === operation) {
629
+ successor.priorWrite = prior;
630
+ return;
631
+ }
632
+ }
633
+ }
634
+
593
635
  /**
594
636
  * Discard the staged write set (committed or aborted); the per-key chain must go with it so a
595
637
  * reused transaction never bases a write on a previous batch's staged state.
596
638
  */
597
639
  clearWrites(): void {
640
+ // A deferred write's `stagedIn` must not outlive this transaction's ability to commit it: save() can
641
+ // fire after a commit or abort, and routing it back here would revive a write this transaction
642
+ // already rolled back — whose blobs abort() has reclaimed. Cleared here, save() resolves the
643
+ // context's current transaction as it did before `stagedIn` existed.
644
+ for (const write of this.writes) if (write?.stagedIn === this) write.stagedIn = undefined;
598
645
  this.writes = [];
599
646
  this.writesByKey = undefined;
600
647
  }
@@ -729,6 +776,7 @@ export class DatabaseTransaction implements Transaction {
729
776
  this.writeTimeout = this.timeout;
730
777
  this.linkWrite(operation);
731
778
  this.writes.push(operation);
779
+ operation.stagedIn = this;
732
780
  // Hold this write back while any earlier same-key write has not run — out of staging order both
733
781
  // diff against the pre-transaction record (harper#2211, DESIGN.md). The whole chain, not just the
734
782
  // immediate link: an eager non-chaining write in between would otherwise launder the deferral.
@@ -807,7 +855,7 @@ export class DatabaseTransaction implements Transaction {
807
855
  if (!operation.saved) {
808
856
  operation.saved = true;
809
857
  // immediately execute in this transaction
810
- if ((operation.validate?.(txnTime) as any) === false) {
858
+ if ((operation.validate?.(txnTime, this) as any) === false) {
811
859
  operation.commit = () => {}; // noop if we try again
812
860
  return;
813
861
  }
@@ -1491,6 +1539,19 @@ export function isReleasedTransaction(value: unknown): boolean {
1491
1539
  return value === RELEASED_TRANSACTION;
1492
1540
  }
1493
1541
 
1542
+ /**
1543
+ * Whether this transaction can be joined as the atomic scope resources/transaction.ts promises. OPEN is
1544
+ * not sufficient: an ImmediateTransaction commits every write as it is made, so a caller that joined one
1545
+ * would get per-write autocommit with no final commit or abort to roll back to. txnForContext installs
1546
+ * one in a context slot that is empty or holds the released placeholder, where it reports OPEN with
1547
+ * nothing owning a commit for it (#2292). Ownership itself is deliberately not the test — a context
1548
+ * pre-seeded with an externally driven DatabaseTransaction (replayLogs.ts) still owns the writes it is
1549
+ * given, and its own commit/abort still governs them.
1550
+ */
1551
+ export function isJoinableScope(transaction: DatabaseTransaction | null | undefined): boolean {
1552
+ return transaction?.open === TRANSACTION_STATE.OPEN && !transaction.saveCommits;
1553
+ }
1554
+
1494
1555
  let timer;
1495
1556
 
1496
1557
  /**
@@ -25,6 +25,7 @@ export function replicationConfirmation(callback) {
25
25
  }
26
26
 
27
27
  export class LMDBTransaction extends DatabaseTransaction {
28
+ stagesWriteOnSave = false;
28
29
  #context: Context;
29
30
  writes: TransactionWrite[] = []; // the set of writes to commit if the conditions are met
30
31
  validated = 0;
@@ -97,6 +98,9 @@ export class LMDBTransaction extends DatabaseTransaction {
97
98
  const immediateTxn = new ImmediateTransaction(this.db);
98
99
  immediateTxn.addWrite(operation);
99
100
  const result = immediateTxn.commit({});
101
+ // Nothing may be sent back to this throwaway: the write is already committed, and its
102
+ // durability is the promise below.
103
+ operation.stagedIn = undefined;
100
104
  if (result?.then) {
101
105
  operation.promise = result;
102
106
  } else {
@@ -107,6 +111,7 @@ export class LMDBTransaction extends DatabaseTransaction {
107
111
 
108
112
  this.linkWrite(operation);
109
113
  this.writes.push(operation); // standard path, add to current transaction
114
+ operation.stagedIn = this;
110
115
  }
111
116
 
112
117
  removeWrite(operation: TransactionWrite) {
@@ -133,7 +138,7 @@ export class LMDBTransaction extends DatabaseTransaction {
133
138
  this.validated = this.writes.length;
134
139
  for (let i = start; i < this.validated; i++) {
135
140
  const write = this.writes[i];
136
- write?.validate?.(this.timestamp);
141
+ write?.validate?.(this.timestamp, this);
137
142
  }
138
143
  let hasBefore;
139
144
  for (let i = start; i < this.validated; i++) {
@@ -342,6 +347,7 @@ export class LMDBTransaction extends DatabaseTransaction {
342
347
  }
343
348
 
344
349
  export class ImmediateTransaction extends LMDBTransaction {
350
+ saveCommits = true;
345
351
  constructor(db: RootDatabaseKind) {
346
352
  super();
347
353
  this.db = db;
@@ -3,7 +3,7 @@ import { RocksDatabase, type RocksDatabaseOptions, constants, type Store } from
3
3
  const FRESH_VERSION_FLAG = constants.FRESH_VERSION_FLAG;
4
4
  import { WeakLRUCache } from 'weak-lru-cache';
5
5
  import { when } from '../utility/when.ts';
6
- import { entryMap, METADATA, type Entry } from './RecordEncoder.ts';
6
+ import { assignStoredFields, entryMap, METADATA, type Entry } from './RecordEncoder.ts';
7
7
 
8
8
  /**
9
9
  * RocksDatabase subclass that owns all primary-store behaviour for Harper tables:
@@ -60,7 +60,7 @@ export class PrimaryRocksDatabase extends RocksDatabase {
60
60
  if (entry.value.constructor === Object && this.#enc.structPrototype) {
61
61
  const originalValue = entry.value;
62
62
  entry.value = new this.#enc.structPrototype.constructor();
63
- Object.assign(entry.value, originalValue);
63
+ assignStoredFields(entry.value, originalValue, this.#enc);
64
64
  }
65
65
  if (typeof entry.value === 'object' && entry.value !== null) {
66
66
  entryMap.set(entry.value, entry);
@@ -164,7 +164,7 @@ export class PrimaryRocksDatabase extends RocksDatabase {
164
164
  if (entry.value?.constructor === Object && enc.structPrototype) {
165
165
  const originalValue = entry.value;
166
166
  entry.value = new enc.structPrototype.constructor();
167
- for (const key in originalValue) entry.value[key] = originalValue[key];
167
+ assignStoredFields(entry.value, originalValue, enc);
168
168
  }
169
169
  return entry;
170
170
  });
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { Encoder } from 'msgpackr';
9
- import { createStructon } from 'structon';
9
+ import { createStructon, SOURCE_SYMBOL } from 'structon';
10
10
  import {
11
11
  HAS_PREVIOUS_RESIDENCY_ID,
12
12
  HAS_CURRENT_RESIDENCY_ID,
@@ -123,12 +123,86 @@ let timestampNextEncoding = 0,
123
123
  additionalAuditRefsNextEncoding: Array<{ version: number; nodeId: number }> | undefined;
124
124
  // tracking metadata with a singleton works better than trying to alter response of getEntry/get and coordinating that across caching layers
125
125
  export let lastMetadata: Entry | null = null;
126
+ /**
127
+ * A resolver owns its attribute's name, so no durable form of a record may carry a value under it.
128
+ * A value gets there either through the response `toJSON` the table installs on the record prototype
129
+ * (msgpackr consults an instance's toJSON when it encodes one) or as an own key from a source or
130
+ * peer payload.
131
+ */
132
+ export function storedFieldsOnly(encoder: any, record: any) {
133
+ if (record === null || typeof record !== 'object') return record;
134
+ // Optional: this also runs for a store whose encode hook was grafted onto a foreign encoder
135
+ // (copyDb's migration target), which carries no table schema.
136
+ const resolved = encoder?.resolvedAttributeNames;
137
+ if (resolved === undefined) return record;
138
+ const resolvedList = encoder.resolvedAttributeNamesList ?? Array.from(resolved);
139
+ // A typed-struct instance keeps its stored fields as accessors on a structon-generated child
140
+ // prototype carrying structon's own toJSON, so it never reaches the response projection — and
141
+ // copying its own keys would encode an empty record.
142
+ let project =
143
+ encoder.surfacedToJSON !== undefined &&
144
+ record.toJSON === encoder.surfacedToJSON &&
145
+ record[SOURCE_SYMBOL] === undefined;
146
+ if (!project && typeof record.toJSON === 'function' && record[SOURCE_SYMBOL] === undefined) {
147
+ // A class instance with its own serialization: the encoder will consult that toJSON, so the
148
+ // projection has to run on its OUTPUT, or the serialized form smuggles resolver-owned keys past
149
+ // a check that only saw the instance. A non-object (or self) result is left for the encoder,
150
+ // which invokes toJSON again — serializers are assumed pure.
151
+ const json = record.toJSON();
152
+ if (json !== record && json !== null && typeof json === 'object') record = json;
153
+ }
154
+ if (!project) {
155
+ for (let i = 0; i < resolvedList.length; i++) {
156
+ if (Object.hasOwn(record, resolvedList[i])) {
157
+ project = true;
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ if (!project) return record;
163
+ const stored = {};
164
+ for (const key of Object.keys(record)) {
165
+ if (resolved.has(key)) continue;
166
+ // '__proto__' as an own key must stay a data property: plain assignment would run the inherited
167
+ // setter, dropping the field from the durable write and swapping the temporary's prototype.
168
+ if (key === '__proto__')
169
+ Object.defineProperty(stored, key, { value: record[key], enumerable: true, writable: true, configurable: true });
170
+ else stored[key] = record[key];
171
+ }
172
+ return stored;
173
+ }
174
+
175
+ /**
176
+ * A resolver-owned name is skipped rather than assigned during record-instance promotion: through a
177
+ * read-only resolver the assignment throws, and through a writable one (a many-to-one
178
+ * `@relationship`) it would derive a foreign key from the stale stored value.
179
+ */
180
+ export function assignStoredFields(target: any, source: any, encoder: any) {
181
+ const resolved = encoder?.resolvedAttributeNames;
182
+ if (resolved === undefined) return Object.assign(target, source);
183
+ // Own keys, matching the Object.assign fallback above and storedFieldsOnly: a decoded plain
184
+ // object has no inherited enumerables of its own, so walking the chain could only ever pick up
185
+ // somebody else's (a polluted Object.prototype included).
186
+ for (const key of Object.keys(source)) {
187
+ if (resolved.has(key)) continue;
188
+ if (key === '__proto__')
189
+ Object.defineProperty(target, key, { value: source[key], enumerable: true, writable: true, configurable: true });
190
+ else target[key] = source[key];
191
+ }
192
+ return target;
193
+ }
194
+
126
195
  export class RecordEncoder extends StructonEncoder {
127
196
  rootStore: any;
128
197
  declare saveStructures: any;
129
198
  declare getStructures: any;
130
199
  declare _writeStruct: any;
131
200
  structureUpdate?: any;
201
+ // Set by TableResource.updatedAttributes. Undefined for a store with no resolved attributes, which
202
+ // is what keeps the projection off that store's writes entirely.
203
+ resolvedAttributeNames?: Set<string>;
204
+ resolvedAttributeNamesList?: string[];
205
+ surfacedToJSON?: () => any;
132
206
  isRocksDB: boolean;
133
207
  name: string;
134
208
  useVersions: boolean;
@@ -539,7 +613,7 @@ export function handleLocalTimeForGets(store, rootStore) {
539
613
  // if an object was deserialized as a plain object, give it the right prototype for computed properties to be accessible
540
614
  const originalValue = entry.value;
541
615
  entry.value = new store.encoder.structPrototype.constructor();
542
- Object.assign(entry.value, originalValue);
616
+ assignStoredFields(entry.value, originalValue, store.encoder);
543
617
  }
544
618
  entryMap.set(entry.value, entry); // allow the record to access the entry
545
619
  }
@@ -591,7 +665,7 @@ export function handleLocalTimeForGets(store, rootStore) {
591
665
  // if an object was deserialized as a plain object, give it the right prototype for computed properties to be accessible
592
666
  const originalValue = entry.value;
593
667
  entry.value = new store.encoder.structPrototype.constructor();
594
- for (const key in originalValue) entry.value[key] = originalValue[key];
668
+ assignStoredFields(entry.value, originalValue, store.encoder);
595
669
  }
596
670
  }
597
671
  return entry;
@@ -708,6 +782,10 @@ export function recordUpdater(store, tableId, auditStore) {
708
782
  // harper#1309: reset so a record===undefined call (delete/no-op) cannot carry stale bytes
709
783
  // into the audit encodedRecord or write-size analytics of this call.
710
784
  lastValueEncoding = undefined;
785
+ // Every durable form of the record — the stored value, the audit entry's encodedRecord and so the
786
+ // transaction log and replication payload — comes from this record, so projecting it here covers
787
+ // them all; nested values keep their own toJSON serialization.
788
+ record = storedFieldsOnly(store.encoder, record);
711
789
  const isRocksDB = store instanceof RocksDatabase;
712
790
  // determine if and how we apply the local timestamp
713
791
  if (isRocksDB) {
@@ -809,6 +887,10 @@ export function recordUpdater(store, tableId, auditStore) {
809
887
  if (audit) {
810
888
  const username = typeof options?.user === 'string' ? options.user : options?.user?.username;
811
889
  if (auditRecord) {
890
+ // The audit encodedRecord is durable record state too (it is the transaction log and the
891
+ // replication payload), so it takes the same projection — except for message/publish
892
+ // entries, whose auditRecord IS the published payload and must reach subscribers verbatim.
893
+ if (type !== 'message' && type !== 'publish') auditRecord = storedFieldsOnly(store.encoder, auditRecord);
812
894
  encodeBlobsWithFilePath(() => store.encoder.encode(auditRecord), id, store.rootStore);
813
895
  if (blobsWereEncoded) {
814
896
  extendedType |= HAS_BLOBS;
@@ -12,8 +12,8 @@ import {
12
12
  import { randomUUID } from 'crypto';
13
13
  import {
14
14
  DatabaseTransaction,
15
+ isJoinableScope,
15
16
  isReleasedTransaction,
16
- TRANSACTION_STATE,
17
17
  type Transaction,
18
18
  } from './DatabaseTransaction.ts';
19
19
  import { IterableEventQueue } from './IterableEventQueue.ts';
@@ -769,9 +769,10 @@ function transactional(
769
769
  if (isCollection) resourceOptions.isCollection = true;
770
770
  } else resourceOptions = options;
771
771
  const loadAsInstance = this.loadAsInstance;
772
- // Only join an existing transaction if it is still genuinely OPEN (mirrors the reuse check
773
- // resources/transaction.ts's transaction() helper already applies to itself). A `context`
774
- // object can carry a *stale* `.transaction` left over from an earlier, unrelated call that
772
+ // Only join an existing transaction that can actually be a scope (isJoinableScope: OPEN, and it
773
+ // stages its writes rather than committing each one — the same gate resources/transaction.ts's
774
+ // transaction() helper applies to itself). Beyond that, a `context` object can carry a *stale*
775
+ // `.transaction` left over from an earlier, unrelated call that
775
776
  // already ran to completion: ambient contexts obtained via contextStorage.getStore() are
776
777
  // no longer guaranteed to be fresh, one-shot objects now that processLocalTransaction (#1591/
777
778
  // #1592) installs one shared, long-lived context for the lifetime of an entire operation
@@ -795,7 +796,7 @@ function transactional(
795
796
  // starting fresh) makes the write throw transactionOpenTooLongError via addWrite()/commit()'s
796
797
  // poison check, correctly propagating the abort to the caller. See
797
798
  // integrationTests/resources/txn-overtime-atomicity.test.ts.
798
- if (context?.transaction?.open === TRANSACTION_STATE.OPEN || context?.transaction?.timedOut) {
799
+ if (isJoinableScope(context?.transaction) || context?.transaction?.timedOut) {
799
800
  // we are already in a transaction (or it was poisoned by a timeout abort and must fail), proceed
800
801
  const resource = this.getResource(query, context, resourceOptions);
801
802
  return resource.then
@@ -76,6 +76,11 @@ export interface Context {
76
76
  * so there the completed transaction itself stays in the slot — also safe to call, but retained.
77
77
  * `null` was the previous released marker and is still accepted defensively.
78
78
  *
79
+ * A read that resolves a transaction without going through the static-API wrappers (an instance load,
80
+ * for one) replaces a released or never-set slot with an ImmediateTransaction, which commits every
81
+ * write as it is made. `transaction()` and the static API start their own scope rather than joining
82
+ * that, so an explicit `transaction()` is atomic on a released slot as it is on a fresh one.
83
+ *
79
84
  * A transaction its own handler commits mid-scope is rotated to a fresh open generation, so the rest
80
85
  * of that scope's writes are committed — or rolled back — with the scope's final commit rather than
81
86
  * each committing itself immediately. Two cases keep the older per-write behavior: a commit made