@harperfast/harper 5.2.8 → 5.2.10

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 (189) hide show
  1. package/config-root.schema.json +10 -0
  2. package/dist/resources/DatabaseTransaction.d.ts +3 -0
  3. package/dist/resources/DatabaseTransaction.js +90 -2
  4. package/dist/resources/DatabaseTransaction.js.map +1 -1
  5. package/dist/resources/PrimaryRocksDatabase.js +2 -1
  6. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  7. package/dist/resources/RocksIndexStore.js +2 -1
  8. package/dist/resources/RocksIndexStore.js.map +1 -1
  9. package/dist/resources/Table.js +13 -5
  10. package/dist/resources/Table.js.map +1 -1
  11. package/dist/resources/auditStore.js +142 -53
  12. package/dist/resources/auditStore.js.map +1 -1
  13. package/dist/resources/databases.d.ts +9 -0
  14. package/dist/resources/databases.js +315 -76
  15. package/dist/resources/databases.js.map +1 -1
  16. package/dist/resources/search.d.ts +1 -0
  17. package/dist/resources/search.js +41 -13
  18. package/dist/resources/search.js.map +1 -1
  19. package/dist/server/storageReclamation.d.ts +7 -0
  20. package/dist/server/storageReclamation.js +10 -0
  21. package/dist/server/storageReclamation.js.map +1 -1
  22. package/dist/server/threads/manageThreads.d.ts +1 -0
  23. package/dist/server/threads/manageThreads.js +7 -0
  24. package/dist/server/threads/manageThreads.js.map +1 -1
  25. package/dist/utility/hdbTerms.d.ts +2 -0
  26. package/dist/utility/hdbTerms.js +2 -0
  27. package/dist/utility/hdbTerms.js.map +1 -1
  28. package/dist/utility/rocksMemoryConfig.js +7 -4
  29. package/dist/utility/rocksMemoryConfig.js.map +1 -1
  30. package/dist/validation/configValidator.js +2 -0
  31. package/dist/validation/configValidator.js.map +1 -1
  32. package/npm-shrinkwrap.json +41 -41
  33. package/package.json +2 -2
  34. package/resources/DatabaseTransaction.ts +88 -2
  35. package/resources/PrimaryRocksDatabase.ts +2 -1
  36. package/resources/RocksIndexStore.ts +2 -1
  37. package/resources/Table.ts +13 -5
  38. package/resources/auditStore.ts +144 -52
  39. package/resources/databases.ts +296 -70
  40. package/resources/search.ts +43 -14
  41. package/server/storageReclamation.ts +10 -0
  42. package/server/threads/manageThreads.js +7 -0
  43. package/studio/web/assets/Chat-Br06zdMA.js +2267 -0
  44. package/studio/web/assets/FloatingChat-BWImX5fA.js +23 -0
  45. package/studio/web/assets/{abnfDiagram-VCTEODGH-C0_BAZyO.js → abnfDiagram-VCTEODGH-B0BebmD2.js} +1 -1
  46. package/studio/web/assets/{alertDialog-DIHt7Z0r.js → alertDialog-CQyAJJhl.js} +1 -1
  47. package/studio/web/assets/{apiToken-CrPb_98N.js → apiToken-DN0nmDsq.js} +1 -1
  48. package/studio/web/assets/applications-kSxVoyeU.js +296 -0
  49. package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +1 -0
  50. package/studio/web/assets/{architectureDiagram-5GKGNRK7-BWzrASgm.js → architectureDiagram-5GKGNRK7-7SW3GD-K.js} +1 -1
  51. package/studio/web/assets/authStore-C3Nfubqr.js +3 -0
  52. package/studio/web/assets/{blockDiagram-NRAW4CY4-BdJX9Khj.js → blockDiagram-I7D4REHJ-BqguiadH.js} +2 -2
  53. package/studio/web/assets/{button-DhiX-njv.js → button-BIsUKRZq.js} +2 -2
  54. package/studio/web/assets/{c4Diagram-UCG6FXSJ-CI6MzGmQ.js → c4Diagram-7LVT6UL2-LBNf8t_X.js} +1 -1
  55. package/studio/web/assets/channel-yictG-U-.js +1 -0
  56. package/studio/web/assets/{chevron-up-Df2c8uoU.js → chevron-up-DtKGqDn3.js} +1 -1
  57. package/studio/web/assets/{chunk-TEH6E4GO-P87k5mNi.js → chunk-4HAMMTFA-DWtTut21.js} +1 -1
  58. package/studio/web/assets/{chunk-75Z2AOVW-BT8tVmks.js → chunk-75Z2AOVW-QGQD6th2.js} +1 -1
  59. package/studio/web/assets/{chunk-DU6HZSFF-9kAOOmI6.js → chunk-DU6HZSFF-Chq20Ba5.js} +1 -1
  60. package/studio/web/assets/{chunk-F27PBJKO-BW7ao8AY.js → chunk-F27PBJKO-BVA5EPhV.js} +1 -1
  61. package/studio/web/assets/{chunk-GMAD6QVW-BNyXpoQO.js → chunk-GMAD6QVW-BeS7S07A.js} +1 -1
  62. package/studio/web/assets/{chunk-OBVCFTLP-D4wWOqDQ.js → chunk-GVQU2GXP-sbwVIQ8i.js} +1 -1
  63. package/studio/web/assets/{chunk-G27WJ6UU-COyLMcgK.js → chunk-IMKFNOWR-Bnh3tAVd.js} +1 -1
  64. package/studio/web/assets/{chunk-JQ64N6SF-Cyz1IeLf.js → chunk-L3NEJ4N5-COfUyKII.js} +1 -1
  65. package/studio/web/assets/chunk-OSK3NFVY-ByciRftO.js +10 -0
  66. package/studio/web/assets/{chunk-P2QGCYS3-DmIFY4d7.js → chunk-P2QGCYS3-CP1VhG_c.js} +1 -1
  67. package/studio/web/assets/{chunk-POPQ4Y6H-BPrvMyKz.js → chunk-POPQ4Y6H-ClWhhkwW.js} +1 -1
  68. package/studio/web/assets/{chunk-PWAF6VOD-2zB6IW9i.js → chunk-PWAF6VOD-1z1THyS5.js} +1 -1
  69. package/studio/web/assets/{chunk-RHFEMEQ7-2FgyI8YU.js → chunk-SHT3W25Y-LpQkMsah.js} +2 -2
  70. package/studio/web/assets/{chunk-SVP7TREG-FwtbH2QC.js → chunk-SVP7TREG-jtdAHw0S.js} +1 -1
  71. package/studio/web/assets/{chunk-LCL6LL3I-HOzK_ppE.js → chunk-TICWLB2K-VOwzetX-.js} +1 -1
  72. package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +1 -0
  73. package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +1 -0
  74. package/studio/web/assets/{createLucideIcon-BKGPfjm2.js → createLucideIcon-CzW9508A.js} +1 -1
  75. package/studio/web/assets/{cssMode-D8ceA4av.js → cssMode-C1JeufH5.js} +1 -1
  76. package/studio/web/assets/{cynefin-OW5HDTMX-BRkpLFQV.js → cynefin-OW5HDTMX-BbdbCvub.js} +1 -1
  77. package/studio/web/assets/{cynefinDiagram-5FMLGOSQ-CHT1DaX6.js → cynefinDiagram-5FMLGOSQ-TP-aIqbt.js} +1 -1
  78. package/studio/web/assets/{dagre-3AP2YEHR-DpUXBh63.js → dagre-GXQ25YYZ-DShnGpGo.js} +1 -1
  79. package/studio/web/assets/{diagram-S7CK7UJ4-BuymVFZT.js → diagram-S7CK7UJ4-aoCVTtcy.js} +1 -1
  80. package/studio/web/assets/{diagram-UQ7AKVKN-CyP148RM.js → diagram-UQ7AKVKN-DglXtQ6x.js} +1 -1
  81. package/studio/web/assets/{diagram-VSXAHHWV-CoCAg3M9.js → diagram-VSXAHHWV-fhEdmkwM.js} +1 -1
  82. package/studio/web/assets/{diagram-VX7I27RA-BpOqCFca.js → diagram-VX7I27RA-DccVJet6.js} +1 -1
  83. package/studio/web/assets/{diagram-Z3DM3KII-Bfpw7Vbj.js → diagram-Z3DM3KII-D-RyJJb7.js} +1 -1
  84. package/studio/web/assets/{dialog-CBf0Mr1d.js → dialog-Cn2uWgD4.js} +1 -1
  85. package/studio/web/assets/{dist-lkA3O3eM.js → dist-DP8UjMB_.js} +1 -1
  86. package/studio/web/assets/{download-BtTOBem-.js → download-B5T5r7ss.js} +1 -1
  87. package/studio/web/assets/{ebnfDiagram-PWID7BFC-DS_6aWqL.js → ebnfDiagram-PWID7BFC-DJGpIpz_.js} +1 -1
  88. package/studio/web/assets/{editor-C-jlLd1G.js → editor-qoo9CrGO.js} +1 -1
  89. package/studio/web/assets/{erDiagram-SSCWMZ5O-DJNk6Fgw.js → erDiagram-RLTQ6QDP-CIfNlgkC.js} +1 -1
  90. package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +1 -0
  91. package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +1 -0
  92. package/studio/web/assets/{ganttDiagram-EL5Y4UJY-2pOExxMY.js → ganttDiagram-EL5Y4UJY-BxToTzzD.js} +1 -1
  93. package/studio/web/assets/{getAnalytics-D4LKGeVy.js → getAnalytics-GHK8ORfM.js} +1 -1
  94. package/studio/web/assets/{gitGraph-4MIJSDKK-CH5ZxwzF.js → gitGraph-4MIJSDKK-D2s2w8lE.js} +1 -1
  95. package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-DVIsIhbO.js → gitGraphDiagram-WWUBYQGX-Dwntd4-x.js} +1 -1
  96. package/studio/web/assets/{html-BnuKFPjv.js → html-Dt4IIy04.js} +1 -1
  97. package/studio/web/assets/{htmlMode-DXM6YYUw.js → htmlMode-DXgKKr4C.js} +1 -1
  98. package/studio/web/assets/index-6onkYFOG.js +824 -0
  99. package/studio/web/assets/{index-Cxj2_wsl.css → index-7RMEgVG1.css} +1 -1
  100. package/studio/web/assets/index.lazy-BrCFnpNJ.js +2 -0
  101. package/studio/web/assets/{info-A6RAGUB7-CPQfTnaG.js → info-A6RAGUB7-DYjkvb0C.js} +1 -1
  102. package/studio/web/assets/{infoDiagram-RXCK75RN-DlwLYlwm.js → infoDiagram-27XIBGKW-Bnp1FJE5.js} +1 -1
  103. package/studio/web/assets/{ishikawaDiagram-5VMMS53U-BRXRp29U.js → ishikawaDiagram-5VMMS53U-D9Xh2r6X.js} +1 -1
  104. package/studio/web/assets/{javascript-CAuAkYxc.js → javascript-DNCQGUBc.js} +1 -1
  105. package/studio/web/assets/{journeyDiagram-EYS64GPL-B0ou8k0n.js → journeyDiagram-3NMN7TZE-CokIi6ll.js} +2 -2
  106. package/studio/web/assets/{jsonMode-Dny8hrU7.js → jsonMode-CR6HWruP.js} +1 -1
  107. package/studio/web/assets/{kanban-definition-3QL26DDD-uYg7iYzp.js → kanban-definition-UXKFOSKX-CukSFJfX.js} +1 -1
  108. package/studio/web/assets/{languageServices-D1uNqC6s.js → languageServices-BM4fI4rS.js} +1 -1
  109. package/studio/web/assets/{lspLanguageFeatures-Bh-bvkyK.js → lspLanguageFeatures-DSa1ttcD.js} +1 -1
  110. package/studio/web/assets/{mermaid-parser.core-Ck-fC8b7.js → mermaid-parser.core-BlEsOWNO.js} +3 -3
  111. package/studio/web/assets/{mermaid.core-CP8aNNYm.js → mermaid.core-BlkGaMIH.js} +5 -5
  112. package/studio/web/assets/{mindmap-definition-FBJOCRG2-CgTZ-rit.js → mindmap-definition-YA3MSWOX-IprMc_0j.js} +1 -1
  113. package/studio/web/assets/{notifications-DsE0S3zT.js → notifications-BHXLnh6x.js} +1 -1
  114. package/studio/web/assets/notifications-CUmtIA6z.js +1 -0
  115. package/studio/web/assets/{packet-AYTQ26CC-DEyoPtPb.js → packet-AYTQ26CC-Bi3V04Zi.js} +1 -1
  116. package/studio/web/assets/{pegDiagram-XKGWAZYB-DrD-7sD9.js → pegDiagram-XKGWAZYB-BNuPDLZY.js} +1 -1
  117. package/studio/web/assets/{pie-WAS4IAKB-wjj-EI1d.js → pie-WAS4IAKB-_6DoDbng.js} +1 -1
  118. package/studio/web/assets/{pieDiagram-E7YTZNPT-GntqDCzv.js → pieDiagram-E7YTZNPT-DqNb6Ht2.js} +1 -1
  119. package/studio/web/assets/{profile-BIeCZr-w.js → profile-8BeFSF3j.js} +1 -1
  120. package/studio/web/assets/{quadrantDiagram-AXDQQJYC-0UeqXQGd.js → quadrantDiagram-AXDQQJYC-BGH9E2YR.js} +1 -1
  121. package/studio/web/assets/{radar-RG4KPBEZ-DFSA5h7k.js → radar-RG4KPBEZ-DDdVczcL.js} +1 -1
  122. package/studio/web/assets/{railroad-74A4TZTK-CaOUG9wR.js → railroad-74A4TZTK-BJUP4Jds.js} +1 -1
  123. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +1 -0
  124. package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +1 -0
  125. package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +1 -0
  126. package/studio/web/assets/{railroadDiagram-O6MQD6OU-yHUELZaV.js → railroadDiagram-O6MQD6OU-DGTPh2KZ.js} +1 -1
  127. package/studio/web/assets/{regions-B5lGduv5.js → regions-C8qR0HhD.js} +1 -1
  128. package/studio/web/assets/{register-Cvc1TpmQ.js → register-B4n5i0SD.js} +3 -3
  129. package/studio/web/assets/{requirementDiagram-EFPCY7ZU-DNEGFjuW.js → requirementDiagram-BXWQKSXE-BJnO6uLz.js} +1 -1
  130. package/studio/web/assets/{sankeyDiagram-P5KCCOFB-DpyAmSVR.js → sankeyDiagram-P5KCCOFB-0vSOdymH.js} +1 -1
  131. package/studio/web/assets/{sequenceDiagram-WJ2MYXX4-TyaT7xNk.js → sequenceDiagram-WJ2MYXX4-hETizDWE.js} +1 -1
  132. package/studio/web/assets/{setComponentFile-D8ZLDWhU.js → setComponentFile-g0_B0lgX.js} +1 -1
  133. package/studio/web/assets/{setup-D2az-Ue_.js → setup-B0CTj_Q5.js} +2 -2
  134. package/studio/web/assets/{stateDiagram-HBIQ2CUA-CeEdTArZ.js → stateDiagram-D77RDMKH-CdYQ_KtC.js} +1 -1
  135. package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +1 -0
  136. package/studio/web/assets/status-C6Yib7-K.js +61 -0
  137. package/studio/web/assets/{swimlanes-XN3QIQJK-B54FmF46.js → swimlanes-42K2YHIH-B8cHIpU4.js} +1 -1
  138. package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +8 -0
  139. package/studio/web/assets/{tabs-B_G5zscN.js → tabs-BrHu7gJi.js} +1 -1
  140. package/studio/web/assets/{timeline-definition-24CTP7MA-D-a9ujbo.js → timeline-definition-24CTP7MA-BJWYSXqF.js} +1 -1
  141. package/studio/web/assets/{toggleHighContrast-BdIEw2rK.js → toggleHighContrast-Dgta7bVi.js} +1 -1
  142. package/studio/web/assets/{treeView-Q6P3EWNA-CrW_6JnS.js → treeView-Q6P3EWNA-qxe_v6CQ.js} +1 -1
  143. package/studio/web/assets/{treemap-WGGIJYW6-BxyYLdP_.js → treemap-WGGIJYW6-dDo97XXF.js} +1 -1
  144. package/studio/web/assets/{tsMode-CulNMwI9.js → tsMode-CH_jHvU-.js} +1 -1
  145. package/studio/web/assets/{typescript-BpRqjmvQ.js → typescript-Co9LCXd5.js} +1 -1
  146. package/studio/web/assets/{useEntityRestURL-C7x-Yz9r.js → useEntityRestURL-wKC8NsC_.js} +1 -1
  147. package/studio/web/assets/{useLocalStorage-Dtj1QS8_.js → useLocalStorage-BqMR3D8_.js} +1 -1
  148. package/studio/web/assets/vendor-core-c2JRRJpV.js +58 -0
  149. package/studio/web/assets/vendor-datadog-CLUcJXOo.js +6 -0
  150. package/studio/web/assets/{vendor-react-Dyj4O3HE.js → vendor-react-CJV_K1u4.js} +1 -1
  151. package/studio/web/assets/vendor-tanstack-DxzraizX.js +1 -0
  152. package/studio/web/assets/{vendor-ui-vhu-UHhF.js → vendor-ui-BUjK0h8a.js} +2 -2
  153. package/studio/web/assets/{vennDiagram-4TSXK5OY-Cy7s7Mpy.js → vennDiagram-4TSXK5OY-A3i-lCdl.js} +1 -1
  154. package/studio/web/assets/{wardley-WFR3VGLG-BeBL35g2.js → wardley-WFR3VGLG-B0ik-_6g.js} +1 -1
  155. package/studio/web/assets/{wardleyDiagram-VM6X3IG4-BylmIGSg.js → wardleyDiagram-VM6X3IG4-CjrkKWUR.js} +1 -1
  156. package/studio/web/assets/{workers-B2WpJDK9.js → workers-CWeLxCXA.js} +1 -1
  157. package/studio/web/assets/x-DIzaLEdK.js +1 -0
  158. package/studio/web/assets/{xml-CZnimVSX.js → xml-BadC-0Rk.js} +1 -1
  159. package/studio/web/assets/{xychartDiagram-S5SC5T6Z-CoKALMXr.js → xychartDiagram-S5SC5T6Z-Biok4GYV.js} +1 -1
  160. package/studio/web/assets/{yaml-BwNuyZS-.js → yaml-BiUfxPbC.js} +1 -1
  161. package/studio/web/index.html +14 -14
  162. package/utility/hdbTerms.ts +2 -0
  163. package/utility/rocksMemoryConfig.ts +7 -4
  164. package/validation/configValidator.ts +2 -0
  165. package/studio/web/assets/Chat-DDlysEi_.js +0 -2067
  166. package/studio/web/assets/FloatingChat-DdJ3DZJX.js +0 -23
  167. package/studio/web/assets/applications-C6Y4nL_X.js +0 -296
  168. package/studio/web/assets/architecture-7GRP2DOG-DNdx5tEU.js +0 -1
  169. package/studio/web/assets/authStore-qKmCZcaf.js +0 -3
  170. package/studio/web/assets/channel-DtCV8PTL.js +0 -1
  171. package/studio/web/assets/chunk-R7TYR2AO-Irip67yr.js +0 -10
  172. package/studio/web/assets/classDiagram-DTDB5LWJ-DbO_dCNE.js +0 -1
  173. package/studio/web/assets/classDiagram-v2-JRS7N3AN-DbO_dCNE.js +0 -1
  174. package/studio/web/assets/eventmodeling-NTZA5JFV-5jbe4A5P.js +0 -1
  175. package/studio/web/assets/flowDiagram-A5DVABFB-Dp9Ezlow.js +0 -1
  176. package/studio/web/assets/index-BtTvoLf8.js +0 -824
  177. package/studio/web/assets/index.lazy-PVbC6xQh.js +0 -2
  178. package/studio/web/assets/notifications-DPTOF-SX.js +0 -1
  179. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bc0Qi0WH.js +0 -1
  180. package/studio/web/assets/railroad-ebnf-LZEXJU2U-G8rVVZ2C.js +0 -1
  181. package/studio/web/assets/railroad-peg-WCYAUIDC-CrehKBhC.js +0 -1
  182. package/studio/web/assets/stateDiagram-v2-4QOOHH4V-D4tuw9Su.js +0 -1
  183. package/studio/web/assets/status-BJegPrWN.js +0 -61
  184. package/studio/web/assets/swimlanesDiagram-VK2B7HYN-XOhmNEvq.js +0 -8
  185. package/studio/web/assets/vendor-core-RCcadM3e.js +0 -73
  186. package/studio/web/assets/vendor-datadog-BRv-mOv1.js +0 -6
  187. package/studio/web/assets/vendor-tanstack-BiFWSB3W.js +0 -1
  188. package/studio/web/assets/x-B9o9hsep.js +0 -1
  189. /package/studio/web/assets/{sizeCapture-X5ZJPWSS-B0uUizjq.js → sizeCapture-INFHLROL-B0uUizjq.js} +0 -0
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
39
+ exports.ABANDONED_MARK_LOCK_TIMEOUT = exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
40
40
  exports.isReadOnlyMode = isReadOnlyMode;
41
41
  exports.getRocksCompression = getRocksCompression;
42
42
  exports.resetRocksCompression = resetRocksCompression;
@@ -53,6 +53,9 @@ exports.closeDatabase = closeDatabase;
53
53
  exports.closeLoadedDatabases = closeLoadedDatabases;
54
54
  exports.table = table;
55
55
  exports.canonicalizeIndexOptions = canonicalizeIndexOptions;
56
+ exports.setIndexingCheckpointPeriod = setIndexingCheckpointPeriod;
57
+ exports.resumeStartKey = resumeStartKey;
58
+ exports.tryAcquireUpdateAttributesLock = tryAcquireUpdateAttributesLock;
56
59
  exports.dropTableMeta = dropTableMeta;
57
60
  exports.onUpdatedTable = onUpdatedTable;
58
61
  exports.onRemovedTable = onRemovedTable;
@@ -60,6 +63,7 @@ exports.onRemovedDB = onRemovedDB;
60
63
  exports.getDefaultCompression = getDefaultCompression;
61
64
  exports.flushDatabases = flushDatabases;
62
65
  const node_events_1 = require("node:events");
66
+ const node_crypto_1 = require("node:crypto");
63
67
  const environmentManager_ts_1 = require("../utility/environment/environmentManager.js");
64
68
  const terms_ts_1 = require("../utility/lmdb/terms.js");
65
69
  const lmdb_1 = require("lmdb");
@@ -81,6 +85,7 @@ const harper_logger_ts_1 = __importDefault(require("../utility/logging/harper_lo
81
85
  const { forComponent } = harper_logger_ts_1.default;
82
86
  const manageThreads = __importStar(require("../server/threads/manageThreads.js"));
83
87
  const auditStore_ts_1 = require("./auditStore.js");
88
+ const storageReclamation_ts_1 = require("../server/storageReclamation.js");
84
89
  const RecordEncoder_ts_1 = require("./RecordEncoder.js");
85
90
  const blob_ts_1 = require("./blob.js");
86
91
  const customIndexes_ts_1 = require("./indexes/customIndexes.js");
@@ -321,6 +326,23 @@ const rocksdbDatabaseEnvs = new Map();
321
326
  (0, globals_js_1._assignPackageExport)('databases', exports.databases);
322
327
  (0, globals_js_1._assignPackageExport)('tables', exports.tables);
323
328
  const NEXT_TABLE_ID = Symbol.for('next-table-id');
329
+ /**
330
+ * True when a descriptor claims an index build no live operation in this process can own. The PID and
331
+ * worker generation cannot answer that alone: a container reuses PID 1 and starts the in-memory
332
+ * generation back at 1 while the persisted one is higher. A descriptor with no incarnation was written
333
+ * before the field existed, so it belongs to an earlier process; a thread started without one of its
334
+ * own cannot judge, and falls back rather than declaring a live build dead.
335
+ */
336
+ function isAbandonedIndexBuild(descriptor, currentRestartGeneration) {
337
+ if (!descriptor)
338
+ return false;
339
+ if (descriptor.indexingPID && descriptor.indexingPID !== process.pid)
340
+ return true;
341
+ if (descriptor.restartNumber < currentRestartGeneration)
342
+ return true;
343
+ const incarnation = manageThreads.processIncarnation;
344
+ return !!descriptor.indexingPID && incarnation != null && descriptor.indexingIncarnation !== incarnation;
345
+ }
324
346
  // How many times the schema load will try to finish a tombstoned drop before
325
347
  // giving up for the rest of this process's lifetime. A drop that fails once
326
348
  // almost always fails identically forever - the usual cause is a RocksDB
@@ -590,6 +612,7 @@ function readMetaDb(path, defaultTable, databaseName = DEFAULT_DATABASE_NAME, au
590
612
  rootStore = (0, lmdb_1.open)(envInit);
591
613
  lmdbDatabaseEnvs.set(path, rootStore);
592
614
  }
615
+ rootStore.dbisDb?.resetReadTxn();
593
616
  return initStores(path, rootStore, databaseName, defaultTable, auditPath, isLegacy);
594
617
  }
595
618
  catch (error) {
@@ -835,6 +858,8 @@ function initStores(path, rootStore, databaseName, defaultTable, auditPath, isLe
835
858
  indices[attribute.name] = dbi;
836
859
  indices[attribute.name].indexNulls = attribute.indexNulls;
837
860
  }
861
+ // the only way a thread that never declares the schema reaches Table.indices
862
+ indices[attribute.name].isIndexing = !!attribute.indexingPID;
838
863
  const existingAttribute = existingAttributes.find((existingAttribute) => existingAttribute.name === attribute.name);
839
864
  if (existingAttribute)
840
865
  existingAttributes.splice(existingAttributes.indexOf(existingAttribute), 1, attribute);
@@ -1147,6 +1172,10 @@ async function dropDatabase(databaseName) {
1147
1172
  delete exports.databases[databaseName];
1148
1173
  exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
1149
1174
  if (rootStore) {
1175
+ // awaited: retirement stops the loop admitting work, the barrier is what says the pass that was
1176
+ // already running has released the stores this is about to close and unlink
1177
+ await rootStore.auditStore?.stopAuditCleanup?.();
1178
+ (0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
1150
1179
  if (rootStore.status === 'open') {
1151
1180
  if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1152
1181
  rootStore.close();
@@ -1164,6 +1193,8 @@ async function dropDatabase(databaseName) {
1164
1193
  // the drop lock now (still before any destructive step)
1165
1194
  if (rootStore instanceof rocksdb_js_1.RocksDatabase)
1166
1195
  lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
1196
+ await rootStore.auditStore?.stopAuditCleanup?.();
1197
+ (0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
1167
1198
  if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1168
1199
  rootStore.close();
1169
1200
  rootStore.destroy();
@@ -1206,10 +1237,6 @@ function closeDatabase(databaseName) {
1206
1237
  continue;
1207
1238
  if (table.primaryStore.rootStore)
1208
1239
  rootStores.add(table.primaryStore.rootStore);
1209
- for (const indexName in table.indices || {}) {
1210
- closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
1211
- }
1212
- closeStore(table.primaryStore, `table ${tableName}`);
1213
1240
  }
1214
1241
  // a database with no tables (an empty schema, or one whose tables were all dropped) still holds
1215
1242
  // an open root store, tracked only on the defined-database entry rather than any table — include
@@ -1217,7 +1244,23 @@ function closeDatabase(databaseName) {
1217
1244
  const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
1218
1245
  if (definedRoot)
1219
1246
  rootStores.add(definedRoot);
1247
+ // before any table store closes, so no further pass is admitted. This is synchronous, so it cannot
1248
+ // await the drain barrier stopAuditCleanup() returns; what covers it is the in-pass status checks,
1249
+ // plus the fact that its production callers reach it only for RocksDB databases, whose pass is one
1250
+ // synchronous purgeLogs() call with nothing suspended mid-removal.
1251
+ for (const rootStore of rootStores)
1252
+ rootStore.auditStore?.stopAuditCleanup?.();
1253
+ for (const tableName in dbTables) {
1254
+ const table = dbTables[tableName];
1255
+ if (!table?.primaryStore)
1256
+ continue;
1257
+ for (const indexName in table.indices || {}) {
1258
+ closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
1259
+ }
1260
+ closeStore(table.primaryStore, `table ${tableName}`);
1261
+ }
1220
1262
  for (const rootStore of rootStores) {
1263
+ (0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
1221
1264
  closeStore(rootStore.dbisDb, 'attributes store');
1222
1265
  closeStore(rootStore, 'root store');
1223
1266
  lmdbDatabaseEnvs.delete(rootStore.path);
@@ -1778,15 +1821,13 @@ function table(tableDefinition) {
1778
1821
  if (changed ||
1779
1822
  indexFormatNeedsPersist ||
1780
1823
  attributeDescriptor?.indexingFailed ||
1781
- (attributeDescriptor?.indexingPID && attributeDescriptor?.indexingPID !== process.pid) ||
1782
- attributeDescriptor?.restartNumber < currentRestartGeneration) {
1824
+ isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
1783
1825
  hasChanges = true;
1784
1826
  exclusiveLock();
1785
1827
  attributeDescriptor = attributesDbi.getSync(dbiKey);
1786
1828
  if (structurallyChanged ||
1787
1829
  attributeDescriptor?.indexingFailed ||
1788
- (attributeDescriptor?.indexingPID && attributeDescriptor?.indexingPID !== process.pid) ||
1789
- attributeDescriptor?.restartNumber < currentRestartGeneration) {
1830
+ isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
1790
1831
  hasChanges = true;
1791
1832
  if (attribute.indexNulls === undefined)
1792
1833
  attribute.indexNulls = true;
@@ -1805,9 +1846,17 @@ function table(tableDefinition) {
1805
1846
  // representation-only option difference — preserve the checkpoint so the backfill
1806
1847
  // resumes rather than restarts. Canonicalized to match structurallyChanged above.
1807
1848
  const indexOptionsChanged = canonicalIndexKey(attributeDescriptor?.indexed) !== canonicalIndexKey(attribute.indexed);
1808
- attribute.lastIndexedKey = indexOptionsChanged
1809
- ? undefined
1810
- : (attributeDescriptor?.lastIndexedKey ?? undefined);
1849
+ // Only a checkpoint runIndexing stamped with its own key resumes: earlier releases advanced
1850
+ // lastIndexedKey past failed and unflushed index writes, so any other is a full rebuild.
1851
+ const uncertifiedCheckpoint = attributeDescriptor?.lastIndexedKey !== undefined &&
1852
+ (attributeDescriptor.checkpointCertified === undefined ||
1853
+ (0, lmdb_1.compareKeys)(attributeDescriptor.checkpointCertified, attributeDescriptor.lastIndexedKey) !== 0);
1854
+ attribute.lastIndexedKey =
1855
+ indexOptionsChanged || uncertifiedCheckpoint
1856
+ ? undefined
1857
+ : (attributeDescriptor?.lastIndexedKey ?? undefined);
1858
+ if (attribute.lastIndexedKey !== undefined)
1859
+ attribute.checkpointCertified = attribute.lastIndexedKey;
1811
1860
  // Explicit reindex is the upgrade path from a legacy (un-versioned) custom-index
1812
1861
  // object store to the versioned, VT-cacheable format. A full rebuild from scratch
1813
1862
  // (lastIndexedKey === undefined) clears the store and rewrites every node, so the
@@ -1829,6 +1878,9 @@ function table(tableDefinition) {
1829
1878
  // the new process reuses the old PID. Cleared on clean completion; left in place
1830
1879
  // on failure/crash so the next, higher-numbered restart re-triggers the backfill.
1831
1880
  attribute.restartNumber = currentRestartGeneration;
1881
+ if (manageThreads.processIncarnation != null)
1882
+ attribute.indexingIncarnation = manageThreads.processIncarnation;
1883
+ attribute.indexingBuildId = (0, node_crypto_1.randomBytes)(8).toString('hex');
1832
1884
  delete attribute.indexingFailed; // clear failure flag for the new run
1833
1885
  dbi.isIndexing = true;
1834
1886
  Object.defineProperty(attribute, 'dbi', { value: dbi, configurable: true, enumerable: false });
@@ -1844,6 +1896,12 @@ function table(tableDefinition) {
1844
1896
  reindexReasons.push(`crash-recovery(pid=${attributeDescriptor.indexingPID})`);
1845
1897
  if (attributeDescriptor?.restartNumber < currentRestartGeneration)
1846
1898
  reindexReasons.push('restart-number');
1899
+ if (uncertifiedCheckpoint)
1900
+ reindexReasons.push('uncertified-checkpoint');
1901
+ if (attributeDescriptor?.indexingPID === process.pid &&
1902
+ manageThreads.processIncarnation != null &&
1903
+ attributeDescriptor.indexingIncarnation !== manageThreads.processIncarnation)
1904
+ reindexReasons.push('abandoned-build(previous process incarnation)');
1847
1905
  logger.info(`reindex ${databaseName}.${tableName}.${attribute.name}: reason=${reindexReasons.join(',') || 'unknown'}`);
1848
1906
  // we only set indexing nulls to true if new or reindexing, we can't have partial indexing of null
1849
1907
  attributesToIndex.push(attribute);
@@ -1856,9 +1914,13 @@ function table(tableDefinition) {
1856
1914
  // workers / a reload would treat the still-partial index as ready and return incomplete results.
1857
1915
  attribute.indexingPID = attributeDescriptor.indexingPID;
1858
1916
  attribute.lastIndexedKey = attributeDescriptor.lastIndexedKey;
1917
+ if (attributeDescriptor.checkpointCertified !== undefined)
1918
+ attribute.checkpointCertified = attributeDescriptor.checkpointCertified;
1859
1919
  // Carry the in-progress restart generation too, so persisting this metadata-only
1860
1920
  // change doesn't drop it and break the crash-recovery trigger for the running backfill.
1861
1921
  attribute.restartNumber = attributeDescriptor.restartNumber;
1922
+ attribute.indexingIncarnation = attributeDescriptor.indexingIncarnation;
1923
+ attribute.indexingBuildId = attributeDescriptor.indexingBuildId;
1862
1924
  if (attributeDescriptor.indexingFailed)
1863
1925
  attribute.indexingFailed = attributeDescriptor.indexingFailed;
1864
1926
  }
@@ -1892,7 +1954,10 @@ function table(tableDefinition) {
1892
1954
  }
1893
1955
  logger.trace(`${tableName} table loading, running index`);
1894
1956
  if (attributesToIndex.length > 0 || indicesToRemove.length > 0) {
1895
- Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove);
1957
+ // captured before the backfill can rewrite the attributes
1958
+ const buildIds = new Map(attributesToIndex.map((attribute) => [attribute, attribute.indexingBuildId]));
1959
+ const markSettled = () => markAbandonedIndexBuild(Table, rootStore, buildIds);
1960
+ Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove).then(markSettled, markSettled);
1896
1961
  }
1897
1962
  else if (hasChanges)
1898
1963
  signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
@@ -1966,28 +2031,163 @@ function canonicalizeIndexOptions(value) {
1966
2031
  }
1967
2032
  const MAX_OUTSTANDING_INDEXING = 1000;
1968
2033
  const MIN_OUTSTANDING_INDEXING = 10;
2034
+ const INDEXING_YIELD_INTERVAL = 100;
2035
+ // A resumable checkpoint is written only after a flush (see flushIndexStores), at most once per period
2036
+ // and never before this many more records: the flush seals every column family in the database, so a
2037
+ // slow backfill must not impose the period's flush rate on unrelated tables.
2038
+ let indexingCheckpointPeriodMs = 5000;
2039
+ let indexingCheckpointMinRecords = 10000;
2040
+ function setIndexingCheckpointPeriod(ms, minRecords = indexingCheckpointMinRecords) {
2041
+ const previous = { ms: indexingCheckpointPeriodMs, minRecords: indexingCheckpointMinRecords };
2042
+ indexingCheckpointPeriodMs = ms;
2043
+ indexingCheckpointMinRecords = minRecords;
2044
+ return previous;
2045
+ }
2046
+ const yieldEventTurn = () => new Promise((resolve) => setImmediate(resolve));
2047
+ // RocksDB index stores have no WAL (openRocksDatabase defaults disableWAL), so a flush is what makes the
2048
+ // entries a checkpoint certifies durable. A flush only covers writes issued before it started, so a caller
2049
+ // never joins one in flight: it joins the next one, which every backfill on that database asking meanwhile
2050
+ // shares — at most one in flight and one queued.
2051
+ const indexingFlushes = new WeakMap();
2052
+ function flushIndexStores(rootStore) {
2053
+ if (!(rootStore instanceof rocksdb_js_1.RocksDatabase))
2054
+ return;
2055
+ let flushes = indexingFlushes.get(rootStore);
2056
+ if (!flushes)
2057
+ indexingFlushes.set(rootStore, (flushes = {}));
2058
+ if (flushes.queued)
2059
+ return flushes.queued;
2060
+ const start = () => {
2061
+ flushes.queued = undefined;
2062
+ const flush = rootStore.flush().finally(() => {
2063
+ if (flushes.inFlight === flush)
2064
+ flushes.inFlight = undefined;
2065
+ });
2066
+ flushes.inFlight = flush;
2067
+ return flush;
2068
+ };
2069
+ if (!flushes.inFlight)
2070
+ return start();
2071
+ return (flushes.queued = flushes.inFlight.then(start, start));
2072
+ }
2073
+ function resumeStartKey(attributes) {
2074
+ let start;
2075
+ for (const attribute of attributes) {
2076
+ if (attribute.lastIndexedKey == undefined)
2077
+ return undefined;
2078
+ if (start === undefined || (0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
2079
+ start = attribute.lastIndexedKey;
2080
+ }
2081
+ return start;
2082
+ }
2083
+ // Bounded, unlike the exclusiveLock() spin above: that one runs on the declaring path, where the
2084
+ // caller is waiting on the result and there is nothing useful to do without the lock. This one runs
2085
+ // after a backfill has already settled, so a holder that never releases would wedge the worker's
2086
+ // event loop for nothing. Giving up costs only the marker, and the next load of the table
2087
+ // re-triggers the build regardless.
2088
+ exports.ABANDONED_MARK_LOCK_TIMEOUT = 10000;
2089
+ const abandonedMarkLockWait = new Int32Array(new SharedArrayBuffer(4));
2090
+ // `timeout` is the test seam; production callers take the default.
2091
+ function tryAcquireUpdateAttributesLock(rootStore, timeout = exports.ABANDONED_MARK_LOCK_TIMEOUT) {
2092
+ if (rootStore.tryLock('update-attributes'))
2093
+ return true;
2094
+ const startTime = performance.now();
2095
+ let waitTime = 1;
2096
+ while (!rootStore.tryLock('update-attributes')) {
2097
+ const elapsed = performance.now() - startTime;
2098
+ if (elapsed >= timeout)
2099
+ return false;
2100
+ // Atomics.wait rather than a busy spin: the section this guards is synchronous, so the wait
2101
+ // blocks this thread either way, but sleeping does not burn a core while it does.
2102
+ if (elapsed >= 2) {
2103
+ Atomics.wait(abandonedMarkLockWait, 0, 0, Math.min(waitTime, timeout - elapsed));
2104
+ if (waitTime < 16)
2105
+ waitTime *= 2;
2106
+ }
2107
+ }
2108
+ return true;
2109
+ }
2110
+ /**
2111
+ * Persists the failure marker for a build that ended without running one of runIndexing's own exit
2112
+ * paths, so something re-triggers it. Fenced on `indexingBuildId` inside the storage engine's catalog
2113
+ * serialization boundary, because a replacement generation (or another thread declaring different index
2114
+ * options) can claim the attribute before an outgoing build's promise settles, and marking that would fail
2115
+ * a live build. The fence read and write stay synchronous, and nothing here may throw because
2116
+ * `Table.indexingOperation` reaches operations-API callers.
2117
+ */
2118
+ async function markAbandonedIndexBuild(Table, rootStore, buildIds) {
2119
+ for (const [attribute, buildId] of buildIds) {
2120
+ try {
2121
+ let marked;
2122
+ if (buildId == null || Table.dbisDB.getSync(attribute.key)?.indexingBuildId !== buildId)
2123
+ continue;
2124
+ const markIfOwned = () => {
2125
+ const descriptor = Table.dbisDB.getSync(attribute.key);
2126
+ if (descriptor?.indexingBuildId === buildId && !descriptor.indexingFailed) {
2127
+ Table.dbisDB.putSync(attribute.key, { ...descriptor, indexingFailed: true });
2128
+ marked = true;
2129
+ }
2130
+ };
2131
+ if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
2132
+ if (!tryAcquireUpdateAttributesLock(rootStore)) {
2133
+ logger.warn(`Could not mark the abandoned index build of ${Table.databaseName}.${Table.tableName}.${attribute.name}: ` +
2134
+ `timed out after ${exports.ABANDONED_MARK_LOCK_TIMEOUT}ms waiting for the exclusive 'update-attributes' lock. ` +
2135
+ `The index stays incomplete and the next load of the table re-triggers the backfill.`);
2136
+ continue;
2137
+ }
2138
+ try {
2139
+ markIfOwned();
2140
+ }
2141
+ finally {
2142
+ rootStore.unlock('update-attributes');
2143
+ }
2144
+ }
2145
+ else {
2146
+ rootStore.transactionSync(markIfOwned);
2147
+ }
2148
+ if (marked)
2149
+ logger.warn(`Indexing of ${Table.databaseName}.${Table.tableName}.${attribute.name} ended without completing. ` +
2150
+ `The index stays incomplete and every query on the attribute reports it as not indexed yet; ` +
2151
+ `the next load of the table retries the backfill from the last checkpoint (indexingFailed=true).`);
2152
+ }
2153
+ catch (error) {
2154
+ // A store closed by shutdown is the common case, and it cannot be written to at all.
2155
+ try {
2156
+ logger.debug(`Could not mark the abandoned index build of ${Table.tableName}.${attribute.name}`, error);
2157
+ }
2158
+ catch { }
2159
+ }
2160
+ }
2161
+ }
1969
2162
  async function runIndexing(Table, attributes, indicesToRemove) {
2163
+ let checkpointing;
2164
+ let hadIndexingErrors = false;
2165
+ const attributeErrorReported = {};
2166
+ const onIndexPutRejected = (property, error) => {
2167
+ hadIndexingErrors = true;
2168
+ if (attributeErrorReported[property])
2169
+ return;
2170
+ attributeErrorReported[property] = true;
2171
+ logger.error(`Error indexing attribute ${property}`, error);
2172
+ };
2173
+ const putRejectionHandlers = attributes.map((attribute) => (error) => onIndexPutRejected(attribute.name, error));
1970
2174
  try {
1971
2175
  logger.info(`Indexing ${Table.tableName} attributes`, attributes);
1972
2176
  await signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
1973
2177
  let lastResolution;
1974
2178
  for (const index of indicesToRemove) {
1975
2179
  lastResolution = index.drop();
2180
+ if (lastResolution?.then)
2181
+ lastResolution.then(undefined, (error) => onIndexPutRejected(index.name, error));
1976
2182
  }
1977
2183
  let interrupted;
1978
- let hadIndexingErrors = false;
1979
- const attributeErrorReported = {};
1980
2184
  let indexed = 0;
1981
2185
  const attributesLength = attributes.length;
1982
2186
  await new Promise((resolve) => setImmediate(resolve)); // yield event turn, indexing should consistently take at least one event turn
1983
2187
  if (attributesLength > 0) {
1984
- let start;
1985
- for (const attribute of attributes) {
1986
- // if we are resuming, we need to start from the last key we indexed by all attributes
1987
- if ((0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
1988
- start = attribute.lastIndexedKey;
1989
- if (attribute.lastIndexedKey == undefined) {
1990
- // if we are starting from the beginning, clear out any previous index entries since we are rewriting
2188
+ const start = resumeStartKey(attributes);
2189
+ if (start === undefined) {
2190
+ for (const attribute of attributes) {
1991
2191
  if (attribute.dbi.clearAsync) {
1992
2192
  // LMDB, note that we don't need to wait for this to complete, just gets enqueued in front of the other writes
1993
2193
  attribute.dbi.clearAsync();
@@ -1998,6 +2198,28 @@ async function runIndexing(Table, attributes, indicesToRemove) {
1998
2198
  }
1999
2199
  }
2000
2200
  let outstanding = 0;
2201
+ // A resumed scan starts at the checkpoint, so it must only name a key whose every predecessor is
2202
+ // durably indexed: persisted once the writes it covers have settled and flushed, frozen after any
2203
+ // record fails so the retry re-covers it, and stamped with its own key (see the trigger in table()).
2204
+ const persistCheckpoint = async (key) => {
2205
+ if (hadIndexingErrors)
2206
+ return;
2207
+ try {
2208
+ await flushIndexStores(Table.primaryStore.rootStore);
2209
+ const puts = [];
2210
+ for (const attribute of attributes) {
2211
+ attribute.lastIndexedKey = key;
2212
+ attribute.checkpointCertified = key;
2213
+ puts.push(Table.dbisDB.put(attribute.key, attribute));
2214
+ }
2215
+ await Promise.all(puts);
2216
+ }
2217
+ catch (error) {
2218
+ logger.warn(`Could not persist the indexing checkpoint for ${Table.tableName}`, error);
2219
+ }
2220
+ };
2221
+ let nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
2222
+ let nextCheckpointRecord = indexingCheckpointMinRecords;
2001
2223
  // this means that a new attribute has been introduced that needs to be indexed
2002
2224
  for (const { key, value: record } of Table.primaryStore.getRange({
2003
2225
  start,
@@ -2005,8 +2227,7 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2005
2227
  versions: true,
2006
2228
  snapshot: false, // don't hold a read transaction this whole time
2007
2229
  })) {
2008
- if (!record)
2009
- continue; // deletion entry
2230
+ const atInterval = ++indexed % INDEXING_YIELD_INTERVAL === 0;
2010
2231
  // TODO: Do we ever need to interrupt due to a schema change that was not a restart?
2011
2232
  //if (Table.schemaVersion !== schemaVersion) return; // break out if there are any schema changes and let someone else pick it up
2012
2233
  outstanding++;
@@ -2018,72 +2239,75 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2018
2239
  // we index, that's fine because indexing is idempotent, we can just put the same values again. If it changes
2019
2240
  // during the indexing, the indexing here will fail. This is also fine because it means the other thread will have
2020
2241
  // performed indexing and we don't need to do anything further
2021
- for (let i = 0; i < attributesLength; i++) {
2022
- const attribute = attributes[i];
2023
- const property = attribute.name;
2024
- const index = attribute.dbi;
2025
- try {
2026
- const resolver = attribute.resolve;
2027
- const value = record && (resolver ? resolver(record) : record[property]);
2028
- if (index.customIndex) {
2029
- index.customIndex.index(key, value);
2030
- didSynchronousIndexing = true;
2031
- continue;
2032
- }
2033
- const values = (0, commonUtility_ts_1.getIndexedValues)(value, index.indexNulls);
2034
- if (values) {
2035
- for (let i = 0, l = values.length; i < l; i++) {
2036
- lastResolution = index.put(values[i], key);
2242
+ if (record) {
2243
+ for (let i = 0; i < attributesLength; i++) {
2244
+ const attribute = attributes[i];
2245
+ const property = attribute.name;
2246
+ const index = attribute.dbi;
2247
+ const onPutRejected = putRejectionHandlers[i];
2248
+ try {
2249
+ const resolver = attribute.resolve;
2250
+ const value = record && (resolver ? resolver(record) : record[property]);
2251
+ if (index.customIndex) {
2252
+ index.customIndex.index(key, value);
2253
+ didSynchronousIndexing = true;
2254
+ continue;
2255
+ }
2256
+ const values = (0, commonUtility_ts_1.getIndexedValues)(value, index.indexNulls);
2257
+ if (values) {
2258
+ for (let i = 0, l = values.length; i < l; i++) {
2259
+ lastResolution = index.put(values[i], key);
2260
+ if (lastResolution?.then)
2261
+ lastResolution.then(undefined, onPutRejected);
2262
+ }
2037
2263
  }
2038
2264
  }
2039
- }
2040
- catch (error) {
2041
- hadIndexingErrors = true;
2042
- if (!attributeErrorReported[property]) {
2043
- // just report an indexing error once per attribute so we don't spam the logs.
2044
- // A store closed by worker shutdown surfaces here as "Database not open"; that is
2045
- // a benign interruption (the next generation re-runs the backfill), so don't log
2046
- // it as an error — the outer catch returns quietly once the iterator also throws.
2047
- attributeErrorReported[property] = true;
2048
- if (Table.primaryStore?.rootStore?.status === 'closed')
2049
- logger.debug(`Indexing attribute ${property} interrupted by store shutdown`, error);
2050
- else
2051
- logger.error(`Error indexing attribute ${property}`, error);
2265
+ catch (error) {
2266
+ hadIndexingErrors = true;
2267
+ if (!attributeErrorReported[property]) {
2268
+ // just report an indexing error once per attribute so we don't spam the logs.
2269
+ // A store closed by worker shutdown surfaces here as "Database not open"; that is
2270
+ // a benign interruption (the next generation re-runs the backfill), so don't log
2271
+ // it as an error — the outer catch returns quietly once the iterator also throws.
2272
+ attributeErrorReported[property] = true;
2273
+ if (Table.primaryStore?.rootStore?.status === 'closed')
2274
+ logger.debug(`Indexing attribute ${property} interrupted by store shutdown`, error);
2275
+ else
2276
+ logger.error(`Error indexing attribute ${property}`, error);
2277
+ }
2052
2278
  }
2053
2279
  }
2054
2280
  }
2055
- (0, when_ts_1.when)(lastResolution, () => outstanding--, (error) => {
2056
- outstanding--;
2057
- hadIndexingErrors = true;
2058
- logger.error(error);
2059
- });
2281
+ (0, when_ts_1.when)(lastResolution, () => outstanding--, () => outstanding--);
2060
2282
  if (worker_threads_1.workerData && worker_threads_1.workerData.restartNumber !== manageThreads.restartNumber) {
2061
2283
  interrupted = true;
2062
2284
  }
2063
- if (++indexed % 100 === 0 || interrupted) {
2064
- // occasionally update our progress so if we crash, we can resume
2065
- for (const attribute of attributes) {
2066
- attribute.lastIndexedKey = key;
2067
- Table.dbisDB.put(attribute.key, attribute);
2285
+ if (interrupted) {
2286
+ try {
2287
+ await lastResolution;
2068
2288
  }
2069
- if (interrupted)
2070
- return;
2289
+ catch {
2290
+ // already counted and logged by the rejection handler above
2291
+ }
2292
+ await checkpointing;
2293
+ await persistCheckpoint(key);
2294
+ return;
2295
+ }
2296
+ if (atInterval && indexed >= nextCheckpointRecord && performance.now() >= nextCheckpointAt) {
2297
+ nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
2298
+ nextCheckpointRecord = indexed + indexingCheckpointMinRecords;
2299
+ await checkpointing;
2300
+ checkpointing = (0, when_ts_1.when)(lastResolution, () => persistCheckpoint(key), () => { });
2071
2301
  }
2072
2302
  if (outstanding > MAX_OUTSTANDING_INDEXING)
2073
2303
  await lastResolution;
2074
- else if (outstanding > MIN_OUTSTANDING_INDEXING)
2075
- await new Promise((resolve) => setImmediate(resolve)); // yield event turn, don't want to use all computation
2076
- else if (didSynchronousIndexing)
2077
- await new Promise((resolve) => setImmediate(resolve)); // custom indexes (e.g. HNSW) index synchronously and never raise `outstanding`; without this yield a large backfill runs in a single event-loop turn, starving keepalive/replication and queries and never letting the isIndexing flag be observed
2304
+ if (atInterval || didSynchronousIndexing || outstanding > MIN_OUTSTANDING_INDEXING)
2305
+ await yieldEventTurn();
2078
2306
  }
2079
2307
  }
2080
- // Await the last pending put. If it rejects, that is also an indexing error.
2081
- // Note: the when() calls above already attach rejection handlers to each record's
2082
- // last-put promise; this try-catch specifically handles the case where lastResolution
2083
- // itself rejects (i.e. the very last put in the loop failed) which would otherwise
2084
- // throw past the hadIndexingErrors check to the outer catch. The broader issue of
2085
- // unhandled rejections from non-last puts in multi-value attributes is pre-existing
2086
- // and out of scope for this fix.
2308
+ await checkpointing;
2309
+ // Await the last pending put. If it rejects, that is also an indexing error (already counted by
2310
+ // onIndexPutRejected); catching it here keeps it from escaping to the outer catch.
2087
2311
  try {
2088
2312
  await lastResolution;
2089
2313
  }
@@ -2095,6 +2319,17 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2095
2319
  // microtasks when their tracked promise settles) have a chance to set hadIndexingErrors
2096
2320
  // before we decide whether to mark indexing as complete.
2097
2321
  await new Promise((resolve) => setImmediate(resolve));
2322
+ // the tail since the last checkpoint is not durable until flushed; announcing the index complete
2323
+ // before that would outlive a crash that loses it
2324
+ if (!hadIndexingErrors) {
2325
+ try {
2326
+ await flushIndexStores(Table.primaryStore.rootStore);
2327
+ }
2328
+ catch (error) {
2329
+ hadIndexingErrors = true;
2330
+ logger.error(`Could not flush the indexes of ${Table.tableName} before marking them complete`, error);
2331
+ }
2332
+ }
2098
2333
  if (hadIndexingErrors) {
2099
2334
  // Some records failed to index. Persist the failure marker in the descriptor so
2100
2335
  // the next call to table() (including after a restart with a fresh PID) re-triggers
@@ -2123,9 +2358,12 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2123
2358
  // update the attributes to indicate that we are finished
2124
2359
  for (const attribute of attributes) {
2125
2360
  delete attribute.lastIndexedKey;
2361
+ delete attribute.checkpointCertified;
2126
2362
  delete attribute.indexingPID;
2127
2363
  delete attribute.indexingFailed;
2128
2364
  delete attribute.restartNumber;
2365
+ delete attribute.indexingIncarnation;
2366
+ delete attribute.indexingBuildId;
2129
2367
  attribute.dbi.isIndexing = false;
2130
2368
  // Also clear isIndexing on the currently-active dbi in Table.indices, which may
2131
2369
  // differ from attribute.dbi if a resetDatabases() call during this migration
@@ -2142,6 +2380,7 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2142
2380
  }
2143
2381
  }
2144
2382
  catch (error) {
2383
+ await checkpointing;
2145
2384
  // A worker shutting down closes its stores mid-backfill, so the range iterator or a
2146
2385
  // put throws (e.g. "Database not open" / "Iterator not initialized"). This is an
2147
2386
  // interruption, not a data error: the next worker generation re-runs the backfill via