harper 5.3.2 → 5.3.4

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 (261) hide show
  1. package/bin/cliOperations.ts +3 -3
  2. package/bin/copyDb.ts +4 -3
  3. package/components/DESIGN.md +12 -0
  4. package/components/packageComponent.ts +42 -17
  5. package/config/DESIGN.md +4 -0
  6. package/config/configUtils.ts +8 -8
  7. package/config/parseConfigFile.ts +11 -7
  8. package/dist/bin/cliOperations.js +3 -3
  9. package/dist/bin/cliOperations.js.map +1 -1
  10. package/dist/bin/copyDb.js +3 -3
  11. package/dist/bin/copyDb.js.map +1 -1
  12. package/dist/components/packageComponent.d.ts +9 -7
  13. package/dist/components/packageComponent.js +41 -16
  14. package/dist/components/packageComponent.js.map +1 -1
  15. package/dist/config/configUtils.js +4 -3
  16. package/dist/config/configUtils.js.map +1 -1
  17. package/dist/config/parseConfigFile.d.ts +1 -0
  18. package/dist/config/parseConfigFile.js +7 -3
  19. package/dist/config/parseConfigFile.js.map +1 -1
  20. package/dist/resources/DatabaseTransaction.d.ts +80 -11
  21. package/dist/resources/DatabaseTransaction.js +1100 -281
  22. package/dist/resources/DatabaseTransaction.js.map +1 -1
  23. package/dist/resources/LMDBTransaction.d.ts +1 -1
  24. package/dist/resources/LMDBTransaction.js +4 -3
  25. package/dist/resources/LMDBTransaction.js.map +1 -1
  26. package/dist/resources/Resource.js +40 -14
  27. package/dist/resources/Resource.js.map +1 -1
  28. package/dist/resources/RocksTransactionLogStore.js +1 -0
  29. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  30. package/dist/resources/Table.js +97 -34
  31. package/dist/resources/Table.js.map +1 -1
  32. package/dist/resources/analytics/write.js +34 -16
  33. package/dist/resources/analytics/write.js.map +1 -1
  34. package/dist/resources/databases.d.ts +5 -0
  35. package/dist/resources/databases.js +38 -4
  36. package/dist/resources/databases.js.map +1 -1
  37. package/dist/resources/recordLock.d.ts +9 -4
  38. package/dist/resources/recordLock.js +79 -19
  39. package/dist/resources/recordLock.js.map +1 -1
  40. package/dist/server/DurableSubscriptionsSession.d.ts +2 -0
  41. package/dist/server/DurableSubscriptionsSession.js +24 -24
  42. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  43. package/dist/server/mqtt.js +59 -29
  44. package/dist/server/mqtt.js.map +1 -1
  45. package/dist/server/serverHelpers/NodeAdapterResponse.js +18 -0
  46. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -1
  47. package/dist/server/serverHelpers/contentTypes.js +8 -2
  48. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  49. package/dist/server/serverHelpers/sharedMessageEncoding.js +3 -2
  50. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -1
  51. package/dist/utility/npmUtilities.js +18 -7
  52. package/dist/utility/npmUtilities.js.map +1 -1
  53. package/package.json +1 -1
  54. package/resources/DESIGN.md +46 -10
  55. package/resources/DatabaseTransaction.ts +1268 -279
  56. package/resources/LMDBTransaction.ts +4 -3
  57. package/resources/Resource.ts +39 -14
  58. package/resources/RocksTransactionLogStore.ts +1 -0
  59. package/resources/Table.ts +120 -41
  60. package/resources/analytics/DESIGN.md +17 -0
  61. package/resources/analytics/write.ts +28 -15
  62. package/resources/databases.ts +40 -4
  63. package/resources/record-locks.md +34 -17
  64. package/resources/recordLock.ts +89 -22
  65. package/server/DESIGN.md +9 -1
  66. package/server/DurableSubscriptionsSession.ts +22 -23
  67. package/server/mqtt.ts +54 -29
  68. package/server/serverHelpers/NodeAdapterResponse.ts +19 -1
  69. package/server/serverHelpers/contentTypes.ts +8 -1
  70. package/server/serverHelpers/sharedMessageEncoding.ts +3 -2
  71. package/studio/web/assets/Chat-DjZNWwwA.js +2100 -0
  72. package/studio/web/assets/FloatingChat-BWfKK_RX.js +23 -0
  73. package/studio/web/assets/SearchDialog-CdlNxWef.js +1 -0
  74. package/studio/web/assets/{abnfDiagram-O67JEVCF-raXE027J.js → abnfDiagram-YUKMZFIV-DPR2lFRk.js} +1 -1
  75. package/studio/web/assets/{apiToken-DNrfij0E.js → apiToken-DyI8IwU3.js} +1 -1
  76. package/studio/web/assets/applications-DgGopKJq.js +296 -0
  77. package/studio/web/assets/architecture-WOLXFQ4H-DtJHIK0_.js +1 -0
  78. package/studio/web/assets/{architectureDiagram-NJMV4G6O-C8_Eet7w.js → architectureDiagram-47P4ROYG-MDymp2dh.js} +3 -3
  79. package/studio/web/assets/authStore-DE1HFvVP.js +3 -0
  80. package/studio/web/assets/{blockDiagram-BEXU5L5S-DR6S5-1D.js → blockDiagram-E7TT5TSB-O5qRgZmd.js} +3 -3
  81. package/studio/web/assets/button-C37w80Uk.js +21 -0
  82. package/studio/web/assets/{c4Diagram-YGBWAQC7-BAfZ1G01.js → c4Diagram-CRA5TL53-BdPORGHo.js} +2 -2
  83. package/studio/web/assets/channel-yCStoq0_.js +1 -0
  84. package/studio/web/assets/{chevron-up-CFxbMGv2.js → chevron-up-DozwrIz-.js} +1 -1
  85. package/studio/web/assets/chunk-2BW5OAIV-DJ-iXJAw.js +10 -0
  86. package/studio/web/assets/{chunk-ZIGJFQKS-Cs8fUn7y.js → chunk-3YJQHVM4-BOgacqS1.js} +2 -2
  87. package/studio/web/assets/{chunk-7PRAP22T-KyqX6x3f.js → chunk-4EA7E6EY-C-7xqUBl.js} +1 -1
  88. package/studio/web/assets/{chunk-GWA4HPMP-DsejB6gA.js → chunk-7TKQ45FW-C3XA1bIN.js} +1 -1
  89. package/studio/web/assets/{chunk-GTNCS2PH-DGHLXyow.js → chunk-AW2ZBBNX-BdvgD1UW.js} +3 -3
  90. package/studio/web/assets/{chunk-5DYCD2WN-BceBfIHT.js → chunk-DBDB3WZW-CKKxIc6F.js} +1 -1
  91. package/studio/web/assets/{chunk-MBY4JIJT-BwSGweQP.js → chunk-E2ZNV5FY-bVefNHvz.js} +3 -3
  92. package/studio/web/assets/chunk-EU5HNXII-BHv2kWp4.js +213 -0
  93. package/studio/web/assets/chunk-FVRAUYC3-BoL_NO6b.js +2 -0
  94. package/studio/web/assets/{chunk-742MDFTN-CIkt8ijt.js → chunk-HJ2JQQFS-N_98oMRS.js} +1 -1
  95. package/studio/web/assets/{chunk-NETBCI7D-C8UnOw_f.js → chunk-HTAEGDNF-BQ1fSjPs.js} +1 -1
  96. package/studio/web/assets/{chunk-WEXAMYUT-BAIhTwyh.js → chunk-KQW6MTUR-qP0QiQAg.js} +1 -1
  97. package/studio/web/assets/chunk-NGNAAXSQ-DqoPQk1a.js +136 -0
  98. package/studio/web/assets/{chunk-UA2S7LBM-BHpEdH8D.js → chunk-NTY3LDVX-CSBFjlJg.js} +1 -1
  99. package/studio/web/assets/chunk-VPRB5NB3-BVHOe9Q-.js +129 -0
  100. package/studio/web/assets/{chunk-7INBJB4K-BpeAXyOi.js → chunk-XC4XBNZT-46uQwtF4.js} +10 -10
  101. package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DmDPkt2u.js → classDiagram-v2-K4WV4PDN-Da0ps1Ui.js} +3 -3
  102. package/studio/web/assets/cn-DpF1PxBT.js +1 -0
  103. package/studio/web/assets/{createLucideIcon-fO5dL7D5.js → createLucideIcon-DLWRoWN-.js} +1 -1
  104. package/studio/web/assets/{cssMode-DBucrQdT.js → cssMode-4ddO86ey.js} +1 -1
  105. package/studio/web/assets/cynefin-EF2NZ3EQ-jQ2eCUaU.js +1 -0
  106. package/studio/web/assets/cynefinDiagram-3GCD6N5R-TNi5iS0E.js +62 -0
  107. package/studio/web/assets/dagre-W4DXFKR2-BnLHmA44.js +4 -0
  108. package/studio/web/assets/{diagram-22UHCM2B-DJCvcaWP.js → diagram-2UJZ2QOL-r_jehhZR.js} +7 -7
  109. package/studio/web/assets/{diagram-3UASUU5V-Du6VKWln.js → diagram-OVF4WLC6-BSUXGVh8.js} +11 -11
  110. package/studio/web/assets/{diagram-CDSNMT55-l5Ga0u-W.js → diagram-PFPMY2P6-BrVedycw.js} +3 -3
  111. package/studio/web/assets/{diagram-MLGK6HIB-CNQY47LA.js → diagram-UMYRVEAY-CBIaakBB.js} +1 -1
  112. package/studio/web/assets/diagram-YEKJPTXX-DFmmDXCl.js +41 -0
  113. package/studio/web/assets/diagram-ZIFT7M5P-CQfOWxOi.js +3 -0
  114. package/studio/web/assets/dist-9lpzt2Hn.js +11 -0
  115. package/studio/web/assets/{ebnfDiagram-ZINNZB2B-5OnkTpfi.js → ebnfDiagram-VR2GEFS7-FXA3Xe6e.js} +1 -1
  116. package/studio/web/assets/{editor-CHpQCuBq.js → editor-DATuu7_v.js} +1 -1
  117. package/studio/web/assets/elk-IJKZMXRS-CC4gfVRU.js +27 -0
  118. package/studio/web/assets/{erDiagram-OPXOYQCR-CQlCX84D.js → erDiagram-O2IAPWRE-C-BkDTPl.js} +2 -2
  119. package/studio/web/assets/errorText-8xV8WZBt.js +1 -0
  120. package/studio/web/assets/eventmodeling-K75KTNOO-D0dJ_PbR.js +1 -0
  121. package/studio/web/assets/flowDiagram-OXPTDLAJ-C1LEc_Hf.js +1 -0
  122. package/studio/web/assets/{ganttDiagram-FUAMR5RP-bloX3Voy.js → ganttDiagram-R7TSEDQI-85F44BIo.js} +4 -4
  123. package/studio/web/assets/{getAnalytics-C0FHjtdQ.js → getAnalytics-B1xpd4kG.js} +1 -1
  124. package/studio/web/assets/gitGraph-VSP46ZUC-BBmqrmjK.js +1 -0
  125. package/studio/web/assets/gitGraphDiagram-XJZIOB7I-CFY6iMwg.js +106 -0
  126. package/studio/web/assets/graphlib-DS17s2tU.js +1 -0
  127. package/studio/web/assets/{html-B1iA8q38.js → html-c4OKnEF1.js} +1 -1
  128. package/studio/web/assets/{htmlMode-CrL3YO5Y.js → htmlMode-yXjqUsW9.js} +1 -1
  129. package/studio/web/assets/index-CDnn0-PN.css +1 -0
  130. package/studio/web/assets/index-CIK9Nhgo.js +833 -0
  131. package/studio/web/assets/index.lazy-DQpKL_QQ.js +1 -0
  132. package/studio/web/assets/info-OHQRW6UA-Bs2aEco7.js +1 -0
  133. package/studio/web/assets/{infoDiagram-VRGFBTTK-B0J1132p.js → infoDiagram-5W2HQ5XZ-d6yG1yfZ.js} +1 -1
  134. package/studio/web/assets/invalidateEntityQueries-BkWXiqjz.js +1 -0
  135. package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-BNk0tuQx.js → ishikawaDiagram-K3B6WC7H-CSYz-WRA.js} +2 -2
  136. package/studio/web/assets/{javascript-CXgQMkoa.js → javascript-CP1WPkLu.js} +1 -1
  137. package/studio/web/assets/{journeyDiagram-ZHPQQLJL-Be79RMQM.js → journeyDiagram-COXZFDF6-BDj6Wqcj.js} +3 -3
  138. package/studio/web/assets/{jsonMode-DycqRKOv.js → jsonMode-BWsIHqbb.js} +1 -1
  139. package/studio/web/assets/{kanban-definition-PNTS6WVX-4uuoQP9W.js → kanban-definition-P3RFRI5V-C2Bjfbp-.js} +6 -6
  140. package/studio/web/assets/{languageServices-HYHV1wDJ.js → languageServices-Ca72n4Ql.js} +1 -1
  141. package/studio/web/assets/{lspLanguageFeatures-CH_6SYiA.js → lspLanguageFeatures-BQdsSDeh.js} +1 -1
  142. package/studio/web/assets/{mermaid-parser.core-V1UYMeXV.js → mermaid-parser.core-Ch_T61iL.js} +6 -6
  143. package/studio/web/assets/mermaid.core-BOAgKzo5.js +44 -0
  144. package/studio/web/assets/{mindmap-definition-NLK3R4M7-C70Ctm0J.js → mindmap-definition-LZFPQGKD-DzOapzMM.js} +3 -3
  145. package/studio/web/assets/notifications-BYgO5-yk.js +1 -0
  146. package/studio/web/assets/notifications-DsGk-TnW.js +1 -0
  147. package/studio/web/assets/operations-Dy69AlTr.js +4 -0
  148. package/studio/web/assets/packet-JDAUHWVQ-CGHB_EIE.js +1 -0
  149. package/studio/web/assets/{pegDiagram-GJSIUBJH-d6z1cc7W.js → pegDiagram-BGZESJAR-ADzOIKrz.js} +1 -1
  150. package/studio/web/assets/pie-XZMESJXO-Bijz5Y2o.js +1 -0
  151. package/studio/web/assets/pieDiagram-CAPJLFHJ-VM7fvbwO.js +39 -0
  152. package/studio/web/assets/profile-DzQvoYBF.js +1 -0
  153. package/studio/web/assets/{quadrantDiagram-O4NWA36T-CIF5Qkou.js → quadrantDiagram-GDMTTRAM-Bmza84_j.js} +3 -3
  154. package/studio/web/assets/radar-ABXABTNO-IaC8pB63.js +1 -0
  155. package/studio/web/assets/railroad-I3PHUGI6-CCrSUqeq.js +1 -0
  156. package/studio/web/assets/railroad-abnf-LNEFI6M7-BhobVp-t.js +1 -0
  157. package/studio/web/assets/railroad-ebnf-DWYD2UWJ-OHhGUTEh.js +1 -0
  158. package/studio/web/assets/railroad-peg-JR7BVNR7-FupC3Jcn.js +1 -0
  159. package/studio/web/assets/{railroadDiagram-XR7U4H2S-y3Jv2U6N.js → railroadDiagram-SM67HX2B-CgFGOVI5.js} +1 -1
  160. package/studio/web/assets/regions-Sv_IN7XA.js +1 -0
  161. package/studio/web/assets/{register-DCN4QUii.js → register-AM2_79FO.js} +3 -3
  162. package/studio/web/assets/request-BJj8dbdU.js +2 -0
  163. package/studio/web/assets/{requirementDiagram-PLB6GJNP-ChINZEmu.js → requirementDiagram-X7JNWC4A-BHoQgkoT.js} +4 -4
  164. package/studio/web/assets/restartGate-D_e93lFE.js +1 -0
  165. package/studio/web/assets/restartUnconfirmed-BqcDZ__Y.js +1 -0
  166. package/studio/web/assets/sankeyDiagram-UM26HJRW-8tFu_oUe.js +40 -0
  167. package/studio/web/assets/sequenceDiagram-ZO4K6R2Y-jryb1rwT.js +169 -0
  168. package/studio/web/assets/setComponentFile-BCnAnxjR.js +1 -0
  169. package/studio/web/assets/setup-Ca4RpPzm.js +2 -0
  170. package/studio/web/assets/{stateDiagram-v2-GCMORJYK-Bs8RIb9K.js → stateDiagram-v2-TBUQTH76-CnWE5yFn.js} +2 -2
  171. package/studio/web/assets/status-D05eZ5pQ.js +61 -0
  172. package/studio/web/assets/{swimlanes-2SLR337P-B52xrf6t.js → swimlanes-N4OXWK64-CBlFzDJn.js} +1 -1
  173. package/studio/web/assets/swimlanesDiagram-K3J5GTZL-DvFLtUPi.js +8 -0
  174. package/studio/web/assets/{timeline-definition-EJHVYXUP-DJSITpQX.js → timeline-definition-YOQAKGHF-CFWqHrqI.js} +4 -4
  175. package/studio/web/assets/{toggleHighContrast-CfAp6Sxb.js → toggleHighContrast-JRkLfyfb.js} +1 -1
  176. package/studio/web/assets/treeView-D4JQ5SDB-CpV9zuVb.js +1 -0
  177. package/studio/web/assets/treemap-SAJKECNS-Bc94Tqiq.js +1 -0
  178. package/studio/web/assets/{tsMode-HZR0bfq4.js → tsMode-C_-eSRVR.js} +1 -1
  179. package/studio/web/assets/{typescript-UMJSfZyF.js → typescript-BYXigM_n.js} +1 -1
  180. package/studio/web/assets/{useEntityRestURL-B379CS-h.js → useEntityRestURL-HHmjsRm9.js} +1 -1
  181. package/studio/web/assets/usecaseDiagram-VIAY4XPW-Brf2xP3S.js +328 -0
  182. package/studio/web/assets/{vendor-core-6LhfJlRC.js → vendor-core-PejxhUWQ.js} +1 -1
  183. package/studio/web/assets/vendor-datadog-GjET_Cbn.js +6 -0
  184. package/studio/web/assets/{vendor-react-BOliS0BL.js → vendor-react-DSIx92Ld.js} +1 -1
  185. package/studio/web/assets/vendor-tanstack-C5Y-nrd2.js +2 -0
  186. package/studio/web/assets/vendor-ui-BBWHOEoQ.js +41 -0
  187. package/studio/web/assets/{vennDiagram-UO4OBE2U-DZTsDA4k.js → vennDiagram-BLWOH2XV-XxOyJPq_.js} +3 -3
  188. package/studio/web/assets/wardley-7MLQ67FV-DRW0zuE4.js +1 -0
  189. package/studio/web/assets/{wardleyDiagram-VNRHLVJA-qu_f7Tvl.js → wardleyDiagram-YMQ3BMBF-B3WskJ9W.js} +3 -3
  190. package/studio/web/assets/wasAReleasedBeforeB-D5grAaGM.js +1 -0
  191. package/studio/web/assets/{workers-Bn_0_L1R.js → workers-DY0ka4Y7.js} +1 -1
  192. package/studio/web/assets/{xml-CG2N-1r1.js → xml-CdGINpmN.js} +1 -1
  193. package/studio/web/assets/xychartDiagram-TAQBALBS-D5NV4Env.js +7 -0
  194. package/studio/web/assets/{yaml-68pda6Av.js → yaml-DYeF07_w.js} +1 -1
  195. package/studio/web/index.html +17 -15
  196. package/utility/npmUtilities.ts +17 -7
  197. package/studio/web/assets/Chat-CrxPD4ph.js +0 -2294
  198. package/studio/web/assets/FloatingChat-EXzoUQ-9.js +0 -23
  199. package/studio/web/assets/SearchDialog-7qRL8Yqx.js +0 -1
  200. package/studio/web/assets/_basePickBy-QJXUw_pr.js +0 -1
  201. package/studio/web/assets/_baseUniq-DR4vVjyC.js +0 -1
  202. package/studio/web/assets/alertDialog-BbWD9Xxa.js +0 -1
  203. package/studio/web/assets/applications-CdqjiSQe.js +0 -296
  204. package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +0 -1
  205. package/studio/web/assets/arrow-up-D2g_57vT.js +0 -1
  206. package/studio/web/assets/authStore-CJCaADw9.js +0 -3
  207. package/studio/web/assets/button-DWwfgGWI.js +0 -21
  208. package/studio/web/assets/channel-D0AkPjJ8.js +0 -1
  209. package/studio/web/assets/chunk-3FUC2YCW-DuRp-j19.js +0 -2
  210. package/studio/web/assets/chunk-7M6MHVWA-h6WlaTzO.js +0 -213
  211. package/studio/web/assets/chunk-FOHPRMQF-DzxwRta7.js +0 -161
  212. package/studio/web/assets/chunk-O7XYJQB3-CBS3nZYV.js +0 -128
  213. package/studio/web/assets/chunk-Z7XXMR3K-DSHbImdf.js +0 -10
  214. package/studio/web/assets/cn-6T8VranJ.js +0 -1
  215. package/studio/web/assets/cynefin-OW5HDTMX-DIvbXLSj.js +0 -1
  216. package/studio/web/assets/cynefinDiagram-VND7K2PF-BdtaXiCE.js +0 -62
  217. package/studio/web/assets/dagre-6A5THRUB-C7RUWHyz.js +0 -4
  218. package/studio/web/assets/diagram-ATOU4E4O-dxRfeG4B.js +0 -3
  219. package/studio/web/assets/diagram-MPIPVDR6-7prveD1B.js +0 -41
  220. package/studio/web/assets/dist-BrRuMUzt.js +0 -11
  221. package/studio/web/assets/download-Cn6tPG4L.js +0 -1
  222. package/studio/web/assets/elk-276RUBZZ-DxDQPjQT.js +0 -27
  223. package/studio/web/assets/errorText-NpBmvk9I.js +0 -1
  224. package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +0 -1
  225. package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +0 -1
  226. package/studio/web/assets/gitGraph-4MIJSDKK-BrXWmJt2.js +0 -1
  227. package/studio/web/assets/gitGraphDiagram-X574FWY7-C2KnnZeY.js +0 -106
  228. package/studio/web/assets/graphlib-C9Tl7QrK.js +0 -1
  229. package/studio/web/assets/index-QLZw6xrU.css +0 -1
  230. package/studio/web/assets/index-qCLyhyah.js +0 -828
  231. package/studio/web/assets/index.lazy-DCx-DxIV.js +0 -2
  232. package/studio/web/assets/info-A6RAGUB7-BhzBHe4U.js +0 -1
  233. package/studio/web/assets/mermaid.core-1PX6R49U.js +0 -44
  234. package/studio/web/assets/notifications-BSObVIY1.js +0 -1
  235. package/studio/web/assets/notifications-DFLXk68b.js +0 -1
  236. package/studio/web/assets/packet-AYTQ26CC-DWbXHx5g.js +0 -1
  237. package/studio/web/assets/pie-WAS4IAKB-DjoTm3yr.js +0 -1
  238. package/studio/web/assets/pieDiagram-5QR66LMP-Dz3RHpDA.js +0 -39
  239. package/studio/web/assets/profile-TBjzOMIy.js +0 -1
  240. package/studio/web/assets/radar-RG4KPBEZ-CVBgjCOj.js +0 -1
  241. package/studio/web/assets/railroad-74A4TZTK-BdhXw10Q.js +0 -1
  242. package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +0 -1
  243. package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +0 -1
  244. package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +0 -1
  245. package/studio/web/assets/regions-C4an4H7e.js +0 -1
  246. package/studio/web/assets/restartGate-nHAFLDh-.js +0 -1
  247. package/studio/web/assets/sankeyDiagram-IPEJSGJF-D9AyLce2.js +0 -40
  248. package/studio/web/assets/sequenceDiagram-PO4LG4MO-hfk4aIEm.js +0 -169
  249. package/studio/web/assets/setComponentFile-BrqOM1Zm.js +0 -1
  250. package/studio/web/assets/setup-0v2MCm-E.js +0 -2
  251. package/studio/web/assets/status-DdezofUu.js +0 -61
  252. package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +0 -8
  253. package/studio/web/assets/tabs-BCTQzoQK.js +0 -1
  254. package/studio/web/assets/treeView-Q6P3EWNA-C-epdxRo.js +0 -1
  255. package/studio/web/assets/treemap-WGGIJYW6-DOVyp_zw.js +0 -1
  256. package/studio/web/assets/usecaseDiagram-POWQR4AR-B6252cbq.js +0 -328
  257. package/studio/web/assets/vendor-datadog-ozKf-pts.js +0 -6
  258. package/studio/web/assets/vendor-tanstack-Bntfka-M.js +0 -2
  259. package/studio/web/assets/vendor-ui-9EYYDgoN.js +0 -41
  260. package/studio/web/assets/wardley-WFR3VGLG-Cfqoe6z4.js +0 -1
  261. package/studio/web/assets/xychartDiagram-PMCCYNJV-UrOKRsmR.js +0 -7
@@ -70,6 +70,7 @@ export class LMDBTransaction extends DatabaseTransaction {
70
70
  this.readTxn = (this.db as any).useReadTransaction();
71
71
 
72
72
  this.readTxnsUsed = 1;
73
+ this.readTxnGeneration++;
73
74
  this.baseReadRefConsumed = false;
74
75
  if ((this.readTxn as any).openTimer) (this.readTxn as any).openTimer = 0;
75
76
  trackedTxns.add(this as any);
@@ -89,8 +90,8 @@ export class LMDBTransaction extends DatabaseTransaction {
89
90
  return this.readTxn;
90
91
  }
91
92
 
92
- doneReadTxn() {
93
- if (!this.readTxn) return;
93
+ doneReadTxn(generation = this.readTxnGeneration) {
94
+ if (!this.readTxn || generation !== this.readTxnGeneration) return;
94
95
  if (this.readTxn instanceof RocksTransaction) {
95
96
  // TODO: Implement this for RocksDB
96
97
  } else {
@@ -434,7 +435,7 @@ export class LMDBTransaction extends DatabaseTransaction {
434
435
  this.baseReadRefConsumed = true;
435
436
  }
436
437
  } else {
437
- while (this.readTxnsUsed > 0) this.doneReadTxn(); // release the read snapshot when we abort, we assume we don't need it
438
+ while (this.readTxn && this.readTxnsUsed > 0) this.doneReadTxn(); // release the read snapshot when we abort, we assume we don't need it
438
439
  }
439
440
  this.open = TRANSACTION_STATE.CLOSED;
440
441
  this.drainCompletions();
@@ -1099,7 +1099,7 @@ export function allowedMethods(resource: any) {
1099
1099
  */
1100
1100
  function selectFromObject(object, propertyResolvers, context) {
1101
1101
  // TODO: eventually we will do aggregate functions here
1102
- const record = object.getRecord?.();
1102
+ const record = typeof object.getRecord === 'function' ? object.getRecord() : undefined;
1103
1103
  if (record) {
1104
1104
  const ownData = object.getChanges?.();
1105
1105
  return (property) => {
@@ -1109,25 +1109,36 @@ function selectFromObject(object, propertyResolvers, context) {
1109
1109
  }
1110
1110
  if (ownData && property in ownData) {
1111
1111
  return ownData[property];
1112
- } else if ((resolver = propertyResolvers?.[property])) {
1112
+ } else if (
1113
+ propertyResolvers &&
1114
+ Object.hasOwn(propertyResolvers, property) &&
1115
+ (resolver = propertyResolvers[property])
1116
+ ) {
1113
1117
  return resolver(object, context);
1114
1118
  } else return record[property];
1115
1119
  };
1116
1120
  } else if (propertyResolvers) {
1117
1121
  return (property) => {
1118
- const resolver = propertyResolvers[property];
1119
- return resolver ? resolver(object, context) : object[property];
1122
+ const resolver = Object.hasOwn(propertyResolvers, property) ? propertyResolvers[property] : undefined;
1123
+ if (resolver) return resolver(object, context);
1124
+ const value = object[property];
1125
+ return typeof value === 'function' ? undefined : value;
1126
+ };
1127
+ } else
1128
+ return (property) => {
1129
+ const value = object[property];
1130
+ return typeof value === 'function' ? undefined : value;
1120
1131
  };
1121
- } else return (property) => object[property];
1122
1132
  }
1123
1133
  export function transformForSelect(select, resource) {
1124
- const propertyResolvers = resource.propertyResolvers;
1125
- const context = resource.getContext?.();
1134
+ const propertyResolvers = resource?.propertyResolvers;
1135
+ const context = resource?.getContext?.();
1126
1136
  let subTransforms;
1127
1137
  if (typeof select === 'string')
1128
1138
  // if select is a single string then return property value
1129
1139
  return function transform(object) {
1130
- if (object.then) return object.then(transform);
1140
+ if (object == null || (!resource && typeof object !== 'object')) return object;
1141
+ if (typeof object.then === 'function') return object.then(transform);
1131
1142
  if (Array.isArray(object)) return object.map(transform);
1132
1143
  return selectFromObject(object, propertyResolvers, context)(select);
1133
1144
  };
@@ -1135,7 +1146,8 @@ export function transformForSelect(select, resource) {
1135
1146
  // if it is an array, return an array
1136
1147
  if (select.asArray)
1137
1148
  return function transform(object) {
1138
- if (object.then) return object.then(transform);
1149
+ if (object == null || (!resource && typeof object !== 'object')) return object;
1150
+ if (typeof object.then === 'function') return object.then(transform);
1139
1151
  if (Array.isArray(object)) return object.map(transform);
1140
1152
  const results = [];
1141
1153
  const getProperty = handleProperty(selectFromObject(object, propertyResolvers, context));
@@ -1145,18 +1157,27 @@ export function transformForSelect(select, resource) {
1145
1157
  return results;
1146
1158
  };
1147
1159
  const forceNulls = select.forceNulls;
1160
+ const selectsPrototype = select.some((property) => (property.name || property) === '__proto__');
1148
1161
  return function transform(object) {
1149
- if (object.then) return object.then(transform);
1162
+ if (object == null || (!resource && typeof object !== 'object')) return object;
1163
+ if (typeof object.then === 'function') return object.then(transform);
1150
1164
  if (Array.isArray(object))
1151
1165
  return object.map((value) => (value && typeof value === 'object' ? transform(value) : value));
1152
1166
  // finally the case of returning objects
1153
1167
  const selectedData = {};
1154
1168
  const getProperty = handleProperty(selectFromObject(object, propertyResolvers, context));
1169
+ if (selectsPrototype)
1170
+ Object.defineProperty(selectedData, '__proto__', {
1171
+ value: undefined,
1172
+ writable: true,
1173
+ enumerable: true,
1174
+ configurable: true,
1175
+ });
1155
1176
  let promises;
1156
1177
  for (const property of select) {
1157
1178
  let value = getProperty(property);
1158
1179
  if (value === undefined && forceNulls) value = null;
1159
- if (value?.then) {
1180
+ if (typeof value?.then === 'function') {
1160
1181
  if (!promises) promises = [];
1161
1182
  promises.push(value.then((value) => (selectedData[property.name || property] = value)));
1162
1183
  } else selectedData[property.name || property] = value;
@@ -1172,14 +1193,18 @@ export function transformForSelect(select, resource) {
1172
1193
  } else if (typeof property === 'object') {
1173
1194
  // TODO: Handle aggregate functions
1174
1195
  if (property.name) {
1175
- if (!subTransforms) subTransforms = {};
1196
+ const value = getProperty(property.name);
1197
+ if (!value || typeof value !== 'object' || value instanceof Date) return value;
1198
+ if (!subTransforms) subTransforms = Object.create(null);
1176
1199
  // TODO: Get the resource, cache this transform, and apply above
1177
1200
  let transform = subTransforms[property.name];
1178
1201
  if (!transform) {
1179
- const resource = propertyResolvers[property.name]?.definition?.tableClass;
1202
+ const resource =
1203
+ propertyResolvers && Object.hasOwn(propertyResolvers, property.name)
1204
+ ? propertyResolvers[property.name]?.definition?.tableClass
1205
+ : undefined;
1180
1206
  transform = subTransforms[property.name] = transformForSelect(property.select || property, resource);
1181
1207
  }
1182
- const value = getProperty(property.name);
1183
1208
  return transform(value);
1184
1209
  } else return getProperty(property);
1185
1210
  } else return property;
@@ -167,6 +167,7 @@ export class RocksTransactionLogStore extends EventEmitter {
167
167
  };
168
168
  }
169
169
  log.addEntry(entryBinary, options.transaction.id);
170
+ options.transaction.hasStagedLog = true;
170
171
  }
171
172
 
172
173
  /** A log holds one origin, which keeps `txnLogKey` unique within it; see resources/DESIGN.md. */
@@ -261,6 +261,8 @@ const MAX_CONCURRENT_LMDB_HISTORY_REMOVALS = 1000;
261
261
  // Each evict otherwise pays a full transaction commit, so batching amortizes that cost. LMDB already
262
262
  // coalesces async writes per event turn (eventTurnBatching), so it keeps the per-record path.
263
263
  const EVICTION_BATCH_SIZE = 100;
264
+ const COMMITTED_READ = { uncachedRead: true };
265
+ const UNRESOLVED = Symbol('unresolved');
264
266
  // Cap on eviction-batch commits in flight at once, so commit I/O overlaps scan/staging without
265
267
  // letting an unbounded number of open transactions (and their snapshots) accumulate.
266
268
  const MAX_INFLIGHT_EVICTION_BATCHES = 4;
@@ -3796,10 +3798,23 @@ export function makeTable(options): TableResourceClass {
3796
3798
  (this.constructor as any).source?.relocate && !(context as any)?.source
3797
3799
  ? (this.constructor as any).source.relocate.bind((this.constructor as any).source, id, undefined, context)
3798
3800
  : undefined,
3799
- commit: (txnTime, existingEntry, _retry, transaction: any) => {
3801
+ commit: (txnTime, existingEntry, _retry, transaction: any, committedBy?: DatabaseTransaction) => {
3800
3802
  const txnLogKey =
3801
3803
  isRocksDB && options?.version != null ? (transaction?.getTimestamp?.() ?? txnTime) : txnTime;
3802
- if (precedesExistingVersion(txnTime, existingEntry, options?.nodeId) < 0) return;
3804
+ write.skipped = false;
3805
+ const precedesExisting = precedesExistingVersion(txnTime, existingEntry, options?.nodeId);
3806
+ if (precedesExisting < 0) return;
3807
+ if (
3808
+ // a re-delivered relocate would restamp the committed head, which can be another write of its transaction
3809
+ precedesExisting === 0 &&
3810
+ committedBy?.sourceApply &&
3811
+ isRocksDB &&
3812
+ txnLogKey === txnTime &&
3813
+ isHeadIdentity(identityBase(write, existingEntry, committedBy), txnTime, options?.nodeId)
3814
+ ) {
3815
+ write.skipped = true;
3816
+ return;
3817
+ }
3803
3818
  const residency = TableResource.getResidencyRecord(options.residencyId);
3804
3819
  let metadata = 0;
3805
3820
  let newRecord = null;
@@ -4331,7 +4346,9 @@ export function makeTable(options): TableResourceClass {
4331
4346
  return this;
4332
4347
  }
4333
4348
  /**
4334
- * Release the lock this instance holds. Resolves true when this call cleared the native key lock.
4349
+ * Release the lock this instance holds. Resolves true when this call cleared the native key lock;
4350
+ * false when it was already released, or when a commit of this lock's writes is still in flight
4351
+ * and the native key is returned once that commit settles.
4335
4352
  * Works for both held (`{ hold: true }`) and transaction-scoped locks. After unlock() the
4336
4353
  * instance is no longer lock-writable; writes through it require a fresh lock.
4337
4354
  */
@@ -4639,16 +4656,20 @@ export function makeTable(options): TableResourceClass {
4639
4656
  }
4640
4657
  },
4641
4658
  before: writeToSource(),
4642
- commit: (txnTime: number, existingEntry: Entry, retry: boolean, transaction: any) => {
4643
- // Whether a prior attempt of THIS write appended its own audit entry (sticky, set in
4644
- // save(); log entries are not part of the aborted rocks transaction, so they survive).
4645
- // Only such a write can find its own orphaned entry in the dedup lookups below and must
4646
- // not treat it as "already applied". Per-write on purpose: the transaction-wide retry
4647
- // flag would also suppress dedup for a genuine re-delivered duplicate co-batched with
4648
- // the conflicting write (double-applying it) and for fresh writes staged through a
4649
- // reused transaction whose retries counter is stale. Sticky on purpose: a proxy read
4650
- // from the last attempt's skipped state launders when a recommit round self-skips
4651
- // (walk identity tie against its own staged record) before a fresh-transaction replay.
4659
+ commit: (
4660
+ txnTime: number,
4661
+ existingEntry: Entry,
4662
+ retry: boolean,
4663
+ transaction: any,
4664
+ committedBy?: DatabaseTransaction
4665
+ ) => {
4666
+ // Whether a prior attempt of THIS write appended its audit entry (sticky, set in
4667
+ // save()). A retry cannot infer that its record mutation committed from an exact log
4668
+ // match alone, so keyed probes stay shielded and walk skips require committed provenance.
4669
+ // Per-write: a transaction-wide retry flag would also suppress dedup for unrelated
4670
+ // deliveries co-batched with the conflict or fresh writes in a reused transaction.
4671
+ // Sticky: a proxy read from the last attempt's skipped state launders when a recommit
4672
+ // round self-skips (walk identity tie against its own staged record) before a replay.
4652
4673
  const stagedOwnAuditEntry = retry && write.appendedAuditEntry === true;
4653
4674
  write.skipped = false; // reset on each retry; cleanup happens after commit if still true
4654
4675
  write.stagedEntry = undefined; // likewise: only set once this round actually stores a record
@@ -4730,6 +4751,22 @@ export function makeTable(options): TableResourceClass {
4730
4751
  let belowAuditFloor = false;
4731
4752
  let dedupVersionCouldBeRetained: (version: number) => boolean;
4732
4753
  if (audit) {
4754
+ let identityRecord: Partial<Entry> | undefined | typeof UNRESOLVED = UNRESOLVED;
4755
+ if (
4756
+ committedBy?.sourceApply &&
4757
+ isRocksDB &&
4758
+ !replaying &&
4759
+ precedesExisting === 0 &&
4760
+ txnLogKey === txnTime &&
4761
+ isHeadIdentity(
4762
+ (identityRecord = identityBase(write, existingEntry, committedBy)),
4763
+ txnTime,
4764
+ options?.nodeId
4765
+ )
4766
+ ) {
4767
+ write.skipped = true;
4768
+ return;
4769
+ }
4733
4770
  // A re-delivered out-of-order write (full-copy audit-replay re-delivers writes) must not have
4734
4771
  // its commutative ops re-folded. additionalAuditRefs is the record's own list of folded
4735
4772
  // out-of-order versions, read with read-your-writes consistency, so this skips the duplicate up
@@ -4743,7 +4780,10 @@ export function makeTable(options): TableResourceClass {
4743
4780
  // === 0 is the identity tie: same version AND same node (the local node is id 0, so an undefined
4744
4781
  // options?.nodeId resolves to the same 0 the ref stored).
4745
4782
  if (
4746
- existingEntry.additionalAuditRefs?.some(
4783
+ (identityRecord === UNRESOLVED
4784
+ ? identityBase(write, existingEntry, committedBy)
4785
+ : identityRecord
4786
+ )?.additionalAuditRefs?.some(
4747
4787
  (ref) =>
4748
4788
  ref.version === txnLogKey &&
4749
4789
  precedesExistingVersion(
@@ -4801,11 +4841,7 @@ export function makeTable(options): TableResourceClass {
4801
4841
  // RocksDB-only: LMDB audit entries are keyed by local audit time, so this lookup doesn't apply
4802
4842
  // there (LMDB keeps the exact unbounded walk). A miss (the keyed lookup can lag a back-to-back re-delivery — #1137)
4803
4843
  // simply falls through to the walk, so this never changes correctness; the additionalAuditRefs
4804
- // check above remains the read-your-writes guard. Never when this write staged in a prior
4805
- // failed attempt: that attempt already appended this write's own audit entry, so the lookup
4806
- // would find it and skip the write as "already applied" when the record was never committed.
4807
- // A recommit of the same transaction survived that skip only because the old write batch
4808
- // still carried the put; a fresh-transaction replay (ERR_TRY_AGAIN) would drop the write.
4844
+ // check above remains the read-your-writes guard. Not on a same-write retry (see stagedOwnAuditEntry).
4809
4845
  if (isRocksDB && !replaying && !stagedOwnAuditEntry && dedupVersionCouldBeRetained(txnLogKey)) {
4810
4846
  const priorAudit = auditStore.get(txnLogKey, tableId, id, options?.nodeId);
4811
4847
  if (
@@ -4857,10 +4893,21 @@ export function makeTable(options): TableResourceClass {
4857
4893
  // `previousNodeId` is never encoded, so absent there means the log is unknown and only the
4858
4894
  // aggregate lookup can resolve a cross-origin predecessor.
4859
4895
  let nodeId = initialAuditHead.nodeId ?? 0;
4896
+ // A retry can load a base restamped by an earlier saved publish/relocate from this
4897
+ // transaction. Those writes do not set stagedEntry, so priorStaged alone cannot
4898
+ // establish that the base is committed.
4899
+ const retryBaseIsCommitted =
4900
+ !priorStaged && !(stagedOwnAuditEntry && hasSavedSameKeySibling(write, committedBy));
4901
+ let walkOriginKnown = retryBaseIsCommitted;
4860
4902
  const succeedingUpdates = []; // record the "future" updates, as we need to apply the updates in reverse order
4861
- const auditRefsToVisit: Array<{ localTime: number; nodeId: number }> = existingEntry.additionalAuditRefs
4862
- ? existingEntry.additionalAuditRefs.map((ref) => ({ localTime: ref.version, nodeId: ref.nodeId }))
4863
- : [];
4903
+ const auditRefsToVisit: Array<{ localTime: number; nodeId: number; originKnown: boolean }> =
4904
+ existingEntry.additionalAuditRefs
4905
+ ? existingEntry.additionalAuditRefs.map((ref) => ({
4906
+ localTime: ref.version,
4907
+ nodeId: ref.nodeId,
4908
+ originKnown: retryBaseIsCommitted,
4909
+ }))
4910
+ : [];
4864
4911
 
4865
4912
  // Out-of-order merges retain every existing branch head; per-origin log keys are not globally ordered.
4866
4913
  if (existingEntry.additionalAuditRefs) {
@@ -4869,13 +4916,13 @@ export function makeTable(options): TableResourceClass {
4869
4916
  }
4870
4917
  }
4871
4918
  let addedAuditRef = false;
4872
- let nextRef: { localTime: number; nodeId: number };
4919
+ let nextRef: { localTime: number; nodeId: number; originKnown: boolean };
4873
4920
  const visitedAuditRefs = new Set<string>();
4874
- const queuePreviousAuditRefs = (auditRecord) => {
4921
+ const queuePreviousAuditRefs = (auditRecord, originKnown: boolean) => {
4875
4922
  const previousRefs = auditRecord.previousAdditionalAuditRefs;
4876
4923
  if (previousRefs) {
4877
4924
  for (const ref of previousRefs) {
4878
- auditRefsToVisit.push({ localTime: ref.version, nodeId: ref.nodeId });
4925
+ auditRefsToVisit.push({ localTime: ref.version, nodeId: ref.nodeId, originKnown });
4879
4926
  logger.debug?.('Adding audit ref from audit record to visit queue', {
4880
4927
  version: ref.version,
4881
4928
  nodeId: ref.nodeId,
@@ -4891,6 +4938,7 @@ export function makeTable(options): TableResourceClass {
4891
4938
  : { txnLogKey: auditRecord.previousVersion, nodeId: auditRecord.previousNodeId };
4892
4939
  localTime = previousHead.txnLogKey;
4893
4940
  nodeId = previousHead.nodeId;
4941
+ walkOriginKnown = walkOriginKnown && previousHead.nodeId != null;
4894
4942
  };
4895
4943
  let walkSteps = 0;
4896
4944
  let auditWalkCapped = false;
@@ -4906,9 +4954,7 @@ export function makeTable(options): TableResourceClass {
4906
4954
  // A re-delivered write whose exact (version, nodeId) is already in the audit log was already
4907
4955
  // applied; drop it rather than re-applying it (double-applying commutative ops) or writing a
4908
4956
  // duplicate audit-only record. Used by the early-out and the depth-cap block below.
4909
- // Never a duplicate for a write that staged in a prior failed attempt: that attempt already
4910
- // appended this write's own audit entry, so the lookup would match it while the record was
4911
- // never committed (see the up-front keyed dedup above).
4957
+ // Not for a same-write retry (see stagedOwnAuditEntry).
4912
4958
  const isReDeliveredDuplicate = () => {
4913
4959
  if (replaying || stagedOwnAuditEntry) return false;
4914
4960
  if (!dedupVersionCouldBeRetained(txnLogKey)) return false; // pre-retention log key — skip the end-of-log scan (best-effort; see above)
@@ -4938,17 +4984,15 @@ export function makeTable(options): TableResourceClass {
4938
4984
  }
4939
4985
  const auditRecord = auditStore.get(localTime, tableId, id, nodeId);
4940
4986
  if (!auditRecord) break;
4941
- queuePreviousAuditRefs(auditRecord);
4987
+ queuePreviousAuditRefs(auditRecord, walkOriginKnown);
4942
4988
  if (
4943
4989
  isRocksDB &&
4944
4990
  !replaying &&
4945
- !stagedOwnAuditEntry &&
4991
+ // on a retry only a link from committed state proves the write committed: an
4992
+ // orphaned log entry can match the identity but is never linked from a committed record
4993
+ (!stagedOwnAuditEntry || walkOriginKnown) &&
4946
4994
  localTime === txnLogKey &&
4947
- precedesExistingVersion(
4948
- txnTime,
4949
- { version: txnTime, localTime: txnLogKey, key: id, nodeId: auditRecord.nodeId },
4950
- options?.nodeId
4951
- ) === 0
4995
+ (auditRecord.nodeId ?? 0) === (options?.nodeId ?? getThisNodeId(auditStore))
4952
4996
  ) {
4953
4997
  write.skipped = true;
4954
4998
  return;
@@ -4966,9 +5010,8 @@ export function makeTable(options): TableResourceClass {
4966
5010
  // Same origin and record version, but a distinct write. Its per-origin log key
4967
5011
  // orders the otherwise non-unique record clock without comparing keys across origins.
4968
5012
  precedesExisting = txnLogKey > localTime ? 1 : -1;
4969
- } else if (replaying || stagedOwnAuditEntry) {
4970
- // The log entry being replayed (or staged by this write's failed attempt) is
4971
- // the write itself, not proof that its primary-store mutation committed.
5013
+ } else if (replaying || (stagedOwnAuditEntry && !walkOriginKnown)) {
5014
+ // replayed, or reached on a retry without committed provenance: not proof it committed
4972
5015
  precedesExisting = 1;
4973
5016
  } else {
4974
5017
  logger.debug?.(
@@ -5054,6 +5097,7 @@ export function makeTable(options): TableResourceClass {
5054
5097
  if (nextRef) {
5055
5098
  localTime = auditedVersion = nextRef.localTime;
5056
5099
  nodeId = nextRef.nodeId;
5100
+ walkOriginKnown = nextRef.originKnown;
5057
5101
  logger.debug?.('Following additional audit ref to continue scanning', { localTime, nodeId });
5058
5102
  }
5059
5103
  } while (nextRef);
@@ -5947,6 +5991,7 @@ export function makeTable(options): TableResourceClass {
5947
5991
  // scans), the read transaction reads against the latest committed data without pinning a
5948
5992
  // consistent snapshot, so the scan doesn't hold a snapshot that blocks compaction.
5949
5993
  const readTxn = txn.useReadTxn(target.snapshot === false);
5994
+ const readTxnGeneration = txn.readTxnGeneration;
5950
5995
  // The explicit row filter participates in query execution: it is pushed into HNSW
5951
5996
  // traversal for vector sorts, applied after conditions otherwise, and checked again after
5952
5997
  // cache/source materialization below. A policy error aborts the query instead of silently
@@ -6089,7 +6134,7 @@ export function makeTable(options): TableResourceClass {
6089
6134
  } finally {
6090
6135
  // We own the iteration here (no results.onDone consumer), so release the read
6091
6136
  // transaction unconditionally — including when the drain throws — or the snapshot leaks.
6092
- txn.doneReadTxn();
6137
+ if (readTxn) txn.doneReadTxn(readTxnGeneration);
6093
6138
  }
6094
6139
  let total: number | null;
6095
6140
  if (wantExact) {
@@ -6129,7 +6174,7 @@ export function makeTable(options): TableResourceClass {
6129
6174
  results.onDone = () => {
6130
6175
  results.onDone = null; // ensure that it isn't called twice
6131
6176
  txn.unregisterReadIterator(results);
6132
- txn.doneReadTxn();
6177
+ if (readTxn) txn.doneReadTxn(readTxnGeneration);
6133
6178
  };
6134
6179
  // Recorded ownership: if the request dies before anything consumes these results, the
6135
6180
  // transaction closes them itself rather than leaving its read snapshot pinned.
@@ -6138,7 +6183,7 @@ export function makeTable(options): TableResourceClass {
6138
6183
  results.getColumns = getColumns;
6139
6184
  return results;
6140
6185
  } catch (error) {
6141
- txn.doneReadTxn();
6186
+ if (readTxn) txn.doneReadTxn(readTxnGeneration);
6142
6187
  throw error;
6143
6188
  }
6144
6189
  }
@@ -7932,6 +7977,7 @@ export function makeTable(options): TableResourceClass {
7932
7977
  },
7933
7978
  };
7934
7979
  propertyResolvers.$highlights.directReturn = true;
7980
+ primaryKeyAttribute = undefined;
7935
7981
  for (const attribute of this.attributes) {
7936
7982
  if (attribute.isPrimaryKey) primaryKeyAttribute = attribute;
7937
7983
  attribute.resolve = null; // reset this
@@ -9102,6 +9148,39 @@ export function makeTable(options): TableResourceClass {
9102
9148
  if (!(existingEntry?.metadataFlags & INVALIDATED)) return existingEntry;
9103
9149
  }
9104
9150
 
9151
+ // The commit base is the committed record unless another write to the key already saved without being
9152
+ // skipped this round: a publish or relocate saves when it is added and restamps the base without staging it.
9153
+ function identityBase(write: any, existingEntry: Entry, transaction: DatabaseTransaction | undefined) {
9154
+ if (hasSavedSameKeySibling(write, transaction)) return committedEntry(write.key);
9155
+ return existingEntry;
9156
+ }
9157
+
9158
+ function hasSavedSameKeySibling(write: any, transaction: DatabaseTransaction | undefined): boolean {
9159
+ for (let other = transaction?.writesByKey?.get(write.store)?.get(write.keyId); other; other = other.priorWrite) {
9160
+ if (
9161
+ other !== write &&
9162
+ other.saved &&
9163
+ !other.skipped &&
9164
+ other.committedOn === write.committedOn &&
9165
+ other.committedRound === write.committedRound
9166
+ )
9167
+ return true;
9168
+ }
9169
+ return false;
9170
+ }
9171
+
9172
+ function committedEntry(id: Id): Entry | undefined {
9173
+ return primaryStore.getEntry(id, COMMITTED_READ);
9174
+ }
9175
+
9176
+ function isHeadIdentity(entry: Partial<Entry> | undefined, version: number, nodeId?: number): boolean {
9177
+ return (
9178
+ entry?.version === version &&
9179
+ (entry.nodeId ?? 0) === (nodeId ?? getThisNodeId(auditStore)) &&
9180
+ !entry.additionalAuditRefs?.length
9181
+ );
9182
+ }
9183
+
9105
9184
  function precedesExistingVersion(txnTime: number, existingEntry: Partial<Entry>, nodeId?: number): number {
9106
9185
  if (nodeId === undefined) {
9107
9186
  nodeId = getThisNodeId(auditStore);
@@ -36,3 +36,20 @@ Still open: `findLastAggregationTime` seeds both markers after a restart from th
36
36
  analytics record, whose `time` is an end-of-cycle stamp, so a restart mid-drain can skip the rest of
37
37
  the backlog; and `storeMetric` discards `table.put()`'s result, so the cursor advances past raw
38
38
  records whose aggregate rows failed to commit. Both need a cursor persisted with the rows.
39
+
40
+ ## Aggregation folds each measure over only the samples that carry it (`resources/analytics/write.ts`)
41
+
42
+ A key's raw samples need not carry the same measures: callback metrics (`recordAction(fn)`) and
43
+ analytics listeners report whatever fields they have, and a raw backlog can come from another
44
+ version. `aggregation()` keeps, per fold target (an action, or one thread's record for `byThread`
45
+ metrics), the count of samples that carried each measure in the cycle-local `measureCounts` map —
46
+ not on the action, which `storeMetric` persists and aggregate listeners receive. A measure's first
47
+ sample seeds it, `max[A-Z]`-named measures fold with `Math.max`, and the rest are a running mean
48
+ weighted by that count. A measure in every sample has a count equal to the target's `count`, so it
49
+ folds exactly as before; keep the running mean, since sum-then-divide rounds differently.
50
+
51
+ The `byThread` combination sums every thread record's numeric fields into the entry, so a measure
52
+ the key's first sample lacked, or that only some threads carry, is kept. That holds only while a
53
+ thread record carries nothing but measures and its `count`, which the thread count then replaces:
54
+ the fold keeps `total`/`ratio` on non-thread actions only, so a `ratio` on a thread record is
55
+ always a caller's measure.
@@ -967,6 +967,8 @@ async function aggregation(fromPeriod, toPeriod = 60000) {
967
967
  const aggregateActions = new Map();
968
968
  const distributions = new Map();
969
969
  const threadsToAverage = [];
970
+ // A measure's weight is the count of only the samples that carried it, per action or thread record.
971
+ const measureCounts = new Map<object, Record<string, number>>();
970
972
  let lastTime: number | undefined;
971
973
  let stoppedAtWindowEdge = false;
972
974
  for (const { key, value } of rawAnalyticsTable.primaryStore.getRange({
@@ -992,7 +994,8 @@ async function aggregation(fromPeriod, toPeriod = 60000) {
992
994
  if (type !== undefined) key += '-' + type;
993
995
  let action = aggregateActions.get(key);
994
996
  if (action) {
995
- if (action.threads) {
997
+ const byThread = Boolean(action.byThread);
998
+ if (byThread) {
996
999
  const actionForThread = action.threads[threadId];
997
1000
  if (actionForThread) action = actionForThread;
998
1001
  else {
@@ -1001,17 +1004,27 @@ async function aggregation(fromPeriod, toPeriod = 60000) {
1001
1004
  }
1002
1005
  }
1003
1006
  if (!action.count) action.count = 1;
1004
- const previousCount = action.count;
1007
+ let counts = measureCounts.get(action);
1008
+ if (!counts) {
1009
+ // Until its first fold, a target holds only its first sample.
1010
+ counts = Object.create(null);
1011
+ for (const measureName in action) {
1012
+ if (typeof action[measureName] === 'number') counts[measureName] = action.count;
1013
+ }
1014
+ measureCounts.set(action, counts);
1015
+ }
1005
1016
  for (const measureName in measures) {
1006
1017
  const value = measures[measureName];
1007
- if (typeof value === 'number') {
1008
- action[measureName] = MAX_MEASURE_NAME.test(measureName)
1009
- ? Math.max(action[measureName], value)
1010
- : (action[measureName] * previousCount + value * count) / (previousCount + count);
1011
- }
1018
+ if (typeof value !== 'number') continue;
1019
+ const measureCount = counts[measureName];
1020
+ if (measureCount === undefined) action[measureName] = value;
1021
+ else if (MAX_MEASURE_NAME.test(measureName)) action[measureName] = Math.max(action[measureName], value);
1022
+ else action[measureName] = (action[measureName] * measureCount + value * count) / (measureCount + count);
1023
+ counts[measureName] = (measureCount ?? 0) + count;
1012
1024
  }
1013
1025
  action.count += count;
1014
- if (total >= 0) {
1026
+ // A thread's record is built from measures alone, so it has no total to add to.
1027
+ if (total >= 0 && !byThread) {
1015
1028
  action.total += total;
1016
1029
  action.ratio = action.total / action.count;
1017
1030
  }
@@ -1043,15 +1056,15 @@ async function aggregation(fromPeriod, toPeriod = 60000) {
1043
1056
  let { path, method, type, metric, count, total, distribution, threads, ...measures } = entry;
1044
1057
  threads = threads.filter((thread) => thread);
1045
1058
  for (const measureName in measures) {
1046
- if (typeof entry[measureName] !== 'number') continue;
1047
- let total = 0;
1048
- for (const thread of threads) {
1059
+ if (typeof entry[measureName] === 'number') entry[measureName] = 0;
1060
+ }
1061
+ // A thread can carry measures the entry's first sample lacked.
1062
+ for (const thread of threads) {
1063
+ for (const measureName in thread) {
1049
1064
  const value = thread[measureName];
1050
- if (typeof value === 'number') {
1051
- total += value;
1052
- }
1065
+ if (typeof value === 'number')
1066
+ entry[measureName] = (typeof entry[measureName] === 'number' ? entry[measureName] : 0) + value;
1053
1067
  }
1054
- entry[measureName] = total;
1055
1068
  }
1056
1069
  entry.count = threads.length;
1057
1070
  delete entry.threads;
@@ -654,6 +654,21 @@ const DROPPED_ROW_PREFIX = '/dropped/';
654
654
  function droppedRowKey(tableName: string): string {
655
655
  return DROPPED_ROW_PREFIX + tableName;
656
656
  }
657
+ /**
658
+ * Whether a catalog row names its table's primary store: one flagged `isPrimaryKey`, or the nameless bare
659
+ * `<table>/` row of a keyless table, which can lose the flag while its store still holds primary records.
660
+ */
661
+ export function isPrimaryCatalogRow(key: unknown, value: any): boolean {
662
+ if (value?.isPrimaryKey) return true;
663
+ // exactly one slash, at the end: initStores reads `<table>/<attribute>` by splitting on the first slash
664
+ return (
665
+ value != null &&
666
+ value.name == null &&
667
+ typeof key === 'string' &&
668
+ key.length > 1 &&
669
+ key.indexOf('/') === key.length - 1
670
+ );
671
+ }
657
672
  /**
658
673
  * Once a name has been dropped, later creates of it take a generation even after the retirement journal is
659
674
  * reclaimed. Keep the row untimed and shaped `{ table, tableId }`: later versions read it as an untimed drop
@@ -1325,7 +1340,7 @@ function initStores(
1325
1340
  definedTables?.add(tableName);
1326
1341
  let tableDef = tablesToLoad.get(tableName);
1327
1342
  if (!tableDef) tablesToLoad.set(tableName, (tableDef = { attributes: [] }));
1328
- if (attribute_name == null || value.isPrimaryKey) tableDef.primary = value;
1343
+ if (isPrimaryCatalogRow(key, value)) tableDef.primary = value;
1329
1344
  if (value.dropping) tableDef.tombstone = value;
1330
1345
  if (attribute_name != null) tableDef.attributes.push(value);
1331
1346
  Object.defineProperty(value, 'key', { value: key, configurable: true });
@@ -1505,7 +1520,8 @@ function initStores(
1505
1520
  (attributesDbi as any).putSync(NEXT_TABLE_ID, tableId + 1);
1506
1521
  (attributesDbi as any).putSync(primaryAttribute.key, primaryAttribute);
1507
1522
  }
1508
- const dbiInit = createOpenDBIObject(!primaryAttribute.isPrimaryKey, primaryAttribute.isPrimaryKey);
1523
+ // primary by selection, not by its flag (isPrimaryCatalogRow)
1524
+ const dbiInit = createOpenDBIObject(false, true);
1509
1525
  dbiInit.compression = primaryAttribute.compression;
1510
1526
  if (dbiInit.compression) {
1511
1527
  const compressionThreshold =
@@ -3671,6 +3687,25 @@ function declareTable<TableResourceType>(target: TableTarget, tableDefinition: T
3671
3687
  if (Table.primaryStore.rootStore.status === 'closed') {
3672
3688
  throw new Error(`Can not use a closed data store from ${tableName} class`);
3673
3689
  }
3690
+ if (Table.primaryKey == null) {
3691
+ // a keyless table cannot adopt a key without re-keying its records
3692
+ for (const attribute of attributes) {
3693
+ if (!attribute.isPrimaryKey) continue;
3694
+ if (origin === 'cluster')
3695
+ logger.warn(
3696
+ `Ignoring peer redefinition of ${databaseName}.${tableName}.${attribute.name} ` +
3697
+ `(isPrimaryKey: local table has no primary key, peer true)`
3698
+ );
3699
+ else if (schemaDefinedExplicit)
3700
+ logger.error(
3701
+ `Table '${databaseName}.${tableName}' has no primary key, so its declared primary key ` +
3702
+ `'${attribute.name}' is ignored and the table stays keyless. Recreate the table with that ` +
3703
+ `primary key, or migrate its records, to use it.`
3704
+ );
3705
+ delete attribute.isPrimaryKey;
3706
+ if (properties?.[attribute.name]) delete properties[attribute.name].primaryKey;
3707
+ }
3708
+ }
3674
3709
  // Reject moving the primary key to a different attribute on a table that already has records.
3675
3710
  // The storage key (Table.primaryKey) is never re-pointed here, so honoring the change would
3676
3711
  // leave describe reporting the new attribute while every record — old and newly inserted — stays
@@ -5736,7 +5771,8 @@ function completeInterruptedDrop(
5736
5771
  }>;
5737
5772
  const bareEntry = catalogRows.find(({ key }) => key === tableName + '/');
5738
5773
  const primaryEntry =
5739
- (bareEntry?.value?.isPrimaryKey ? bareEntry : undefined) ?? catalogRows.find(({ value }) => value?.isPrimaryKey);
5774
+ catalogRows.find(({ value }) => value?.isPrimaryKey) ??
5775
+ (bareEntry && isPrimaryCatalogRow(bareEntry.key, bareEntry.value) ? bareEntry : undefined);
5740
5776
  const tombstoneEntry = bareEntry ?? primaryEntry;
5741
5777
  if (rootStore instanceof RocksDatabase && !fullTextRetired) {
5742
5778
  const names = [
@@ -5795,7 +5831,7 @@ function completeInterruptedDrop(
5795
5831
  // recreate silently inherit the previous table's records.
5796
5832
  for (const { key, value } of catalogRows) {
5797
5833
  const objectStorage =
5798
- value?.isPrimaryKey || (value?.indexed?.type && CUSTOM_INDEXES[value.indexed.type]?.useObjectStore);
5834
+ isPrimaryCatalogRow(key, value) || (value?.indexed?.type && CUSTOM_INDEXES[value.indexed.type]?.useObjectStore);
5799
5835
  const store = (rootStore as any).openDB(key, createOpenDBIObject(!objectStorage, objectStorage) as any);
5800
5836
  try {
5801
5837
  // dropSync (not drop): this function is synchronous, and its callers rely on