@harperfast/harper 5.2.9 → 5.2.12

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 (177) hide show
  1. package/dist/resources/DatabaseTransaction.d.ts +3 -0
  2. package/dist/resources/DatabaseTransaction.js +90 -2
  3. package/dist/resources/DatabaseTransaction.js.map +1 -1
  4. package/dist/resources/PrimaryRocksDatabase.js +2 -1
  5. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  6. package/dist/resources/RocksIndexStore.js +2 -1
  7. package/dist/resources/RocksIndexStore.js.map +1 -1
  8. package/dist/resources/Table.js +13 -5
  9. package/dist/resources/Table.js.map +1 -1
  10. package/dist/resources/auditStore.js +142 -53
  11. package/dist/resources/auditStore.js.map +1 -1
  12. package/dist/resources/databases.d.ts +10 -0
  13. package/dist/resources/databases.js +369 -97
  14. package/dist/resources/databases.js.map +1 -1
  15. package/dist/resources/search.js +33 -7
  16. package/dist/resources/search.js.map +1 -1
  17. package/dist/server/storageReclamation.d.ts +7 -0
  18. package/dist/server/storageReclamation.js +10 -0
  19. package/dist/server/storageReclamation.js.map +1 -1
  20. package/dist/server/threads/manageThreads.d.ts +1 -0
  21. package/dist/server/threads/manageThreads.js +7 -0
  22. package/dist/server/threads/manageThreads.js.map +1 -1
  23. package/npm-shrinkwrap.json +41 -41
  24. package/package.json +2 -2
  25. package/resources/DatabaseTransaction.ts +88 -2
  26. package/resources/PrimaryRocksDatabase.ts +2 -1
  27. package/resources/RocksIndexStore.ts +2 -1
  28. package/resources/Table.ts +13 -5
  29. package/resources/auditStore.ts +144 -52
  30. package/resources/databases.ts +351 -93
  31. package/resources/search.ts +32 -7
  32. package/server/storageReclamation.ts +10 -0
  33. package/server/threads/manageThreads.js +7 -0
  34. package/studio/web/assets/Chat-CokJOjyX.js +2267 -0
  35. package/studio/web/assets/FloatingChat-h9BJo6YG.js +23 -0
  36. package/studio/web/assets/{abnfDiagram-VCTEODGH-C0_BAZyO.js → abnfDiagram-VCTEODGH-B0BebmD2.js} +1 -1
  37. package/studio/web/assets/{alertDialog-DIHt7Z0r.js → alertDialog-CQyAJJhl.js} +1 -1
  38. package/studio/web/assets/{apiToken-c3Rd-w6g.js → apiToken-B_g12QrM.js} +1 -1
  39. package/studio/web/assets/applications-Dlq5kJ2O.js +296 -0
  40. package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +1 -0
  41. package/studio/web/assets/{architectureDiagram-5GKGNRK7-BWzrASgm.js → architectureDiagram-5GKGNRK7-7SW3GD-K.js} +1 -1
  42. package/studio/web/assets/authStore-C3Nfubqr.js +3 -0
  43. package/studio/web/assets/{blockDiagram-NRAW4CY4-BdJX9Khj.js → blockDiagram-I7D4REHJ-BqguiadH.js} +2 -2
  44. package/studio/web/assets/{button-DhiX-njv.js → button-BIsUKRZq.js} +2 -2
  45. package/studio/web/assets/{c4Diagram-UCG6FXSJ-CI6MzGmQ.js → c4Diagram-7LVT6UL2-LBNf8t_X.js} +1 -1
  46. package/studio/web/assets/channel-yictG-U-.js +1 -0
  47. package/studio/web/assets/{chevron-up-Df2c8uoU.js → chevron-up-DtKGqDn3.js} +1 -1
  48. package/studio/web/assets/{chunk-TEH6E4GO-P87k5mNi.js → chunk-4HAMMTFA-DWtTut21.js} +1 -1
  49. package/studio/web/assets/{chunk-75Z2AOVW-BT8tVmks.js → chunk-75Z2AOVW-QGQD6th2.js} +1 -1
  50. package/studio/web/assets/{chunk-DU6HZSFF-9kAOOmI6.js → chunk-DU6HZSFF-Chq20Ba5.js} +1 -1
  51. package/studio/web/assets/{chunk-F27PBJKO-BW7ao8AY.js → chunk-F27PBJKO-BVA5EPhV.js} +1 -1
  52. package/studio/web/assets/{chunk-GMAD6QVW-BNyXpoQO.js → chunk-GMAD6QVW-BeS7S07A.js} +1 -1
  53. package/studio/web/assets/{chunk-OBVCFTLP-D4wWOqDQ.js → chunk-GVQU2GXP-sbwVIQ8i.js} +1 -1
  54. package/studio/web/assets/{chunk-G27WJ6UU-COyLMcgK.js → chunk-IMKFNOWR-Bnh3tAVd.js} +1 -1
  55. package/studio/web/assets/{chunk-JQ64N6SF-Cyz1IeLf.js → chunk-L3NEJ4N5-COfUyKII.js} +1 -1
  56. package/studio/web/assets/chunk-OSK3NFVY-ByciRftO.js +10 -0
  57. package/studio/web/assets/{chunk-P2QGCYS3-DmIFY4d7.js → chunk-P2QGCYS3-CP1VhG_c.js} +1 -1
  58. package/studio/web/assets/{chunk-POPQ4Y6H-BPrvMyKz.js → chunk-POPQ4Y6H-ClWhhkwW.js} +1 -1
  59. package/studio/web/assets/{chunk-PWAF6VOD-2zB6IW9i.js → chunk-PWAF6VOD-1z1THyS5.js} +1 -1
  60. package/studio/web/assets/{chunk-RHFEMEQ7-2FgyI8YU.js → chunk-SHT3W25Y-LpQkMsah.js} +2 -2
  61. package/studio/web/assets/{chunk-SVP7TREG-FwtbH2QC.js → chunk-SVP7TREG-jtdAHw0S.js} +1 -1
  62. package/studio/web/assets/{chunk-LCL6LL3I-HOzK_ppE.js → chunk-TICWLB2K-VOwzetX-.js} +1 -1
  63. package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +1 -0
  64. package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +1 -0
  65. package/studio/web/assets/{createLucideIcon-BKGPfjm2.js → createLucideIcon-CzW9508A.js} +1 -1
  66. package/studio/web/assets/{cssMode-CEN2mzSA.js → cssMode-DAjzPoBf.js} +1 -1
  67. package/studio/web/assets/{cynefin-OW5HDTMX-BRkpLFQV.js → cynefin-OW5HDTMX-BbdbCvub.js} +1 -1
  68. package/studio/web/assets/{cynefinDiagram-5FMLGOSQ-CHT1DaX6.js → cynefinDiagram-5FMLGOSQ-TP-aIqbt.js} +1 -1
  69. package/studio/web/assets/{dagre-3AP2YEHR-DpUXBh63.js → dagre-GXQ25YYZ-DShnGpGo.js} +1 -1
  70. package/studio/web/assets/{diagram-S7CK7UJ4-BuymVFZT.js → diagram-S7CK7UJ4-aoCVTtcy.js} +1 -1
  71. package/studio/web/assets/{diagram-UQ7AKVKN-CyP148RM.js → diagram-UQ7AKVKN-DglXtQ6x.js} +1 -1
  72. package/studio/web/assets/{diagram-VSXAHHWV-CoCAg3M9.js → diagram-VSXAHHWV-fhEdmkwM.js} +1 -1
  73. package/studio/web/assets/{diagram-VX7I27RA-BpOqCFca.js → diagram-VX7I27RA-DccVJet6.js} +1 -1
  74. package/studio/web/assets/{diagram-Z3DM3KII-Bfpw7Vbj.js → diagram-Z3DM3KII-D-RyJJb7.js} +1 -1
  75. package/studio/web/assets/{dialog-CBf0Mr1d.js → dialog-Cn2uWgD4.js} +1 -1
  76. package/studio/web/assets/{dist-lkA3O3eM.js → dist-DP8UjMB_.js} +1 -1
  77. package/studio/web/assets/{download-BtTOBem-.js → download-B5T5r7ss.js} +1 -1
  78. package/studio/web/assets/{ebnfDiagram-PWID7BFC-DS_6aWqL.js → ebnfDiagram-PWID7BFC-DJGpIpz_.js} +1 -1
  79. package/studio/web/assets/{editor-D8oDeCTL.js → editor-VUNQ-5Ye.js} +1 -1
  80. package/studio/web/assets/{erDiagram-SSCWMZ5O-DJNk6Fgw.js → erDiagram-RLTQ6QDP-CIfNlgkC.js} +1 -1
  81. package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +1 -0
  82. package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +1 -0
  83. package/studio/web/assets/{ganttDiagram-EL5Y4UJY-2pOExxMY.js → ganttDiagram-EL5Y4UJY-BxToTzzD.js} +1 -1
  84. package/studio/web/assets/{getAnalytics-D4LKGeVy.js → getAnalytics-GHK8ORfM.js} +1 -1
  85. package/studio/web/assets/{gitGraph-4MIJSDKK-CH5ZxwzF.js → gitGraph-4MIJSDKK-D2s2w8lE.js} +1 -1
  86. package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-DVIsIhbO.js → gitGraphDiagram-WWUBYQGX-Dwntd4-x.js} +1 -1
  87. package/studio/web/assets/{html-u3vOg7LJ.js → html-CFqhtdfa.js} +1 -1
  88. package/studio/web/assets/{htmlMode-DyO31v-P.js → htmlMode-CpgOTfjz.js} +1 -1
  89. package/studio/web/assets/{index-Cxj2_wsl.css → index-7RMEgVG1.css} +1 -1
  90. package/studio/web/assets/index-ZQ1upnh7.js +824 -0
  91. package/studio/web/assets/index.lazy-HDafSRxx.js +2 -0
  92. package/studio/web/assets/{info-A6RAGUB7-CPQfTnaG.js → info-A6RAGUB7-DYjkvb0C.js} +1 -1
  93. package/studio/web/assets/{infoDiagram-RXCK75RN-DlwLYlwm.js → infoDiagram-27XIBGKW-Bnp1FJE5.js} +1 -1
  94. package/studio/web/assets/{ishikawaDiagram-5VMMS53U-BRXRp29U.js → ishikawaDiagram-5VMMS53U-D9Xh2r6X.js} +1 -1
  95. package/studio/web/assets/{javascript-CUvxOyTC.js → javascript-9l5JXJwc.js} +1 -1
  96. package/studio/web/assets/{journeyDiagram-EYS64GPL-B0ou8k0n.js → journeyDiagram-3NMN7TZE-CokIi6ll.js} +2 -2
  97. package/studio/web/assets/{jsonMode-f_IwbF3D.js → jsonMode-BJBH-iR0.js} +1 -1
  98. package/studio/web/assets/{kanban-definition-3QL26DDD-uYg7iYzp.js → kanban-definition-UXKFOSKX-CukSFJfX.js} +1 -1
  99. package/studio/web/assets/{languageServices-DXtZ6rEF.js → languageServices-0ZvgO63b.js} +1 -1
  100. package/studio/web/assets/{lspLanguageFeatures-B4pCF1zO.js → lspLanguageFeatures-sNVDopx3.js} +1 -1
  101. package/studio/web/assets/{mermaid-parser.core-Ck-fC8b7.js → mermaid-parser.core-BlEsOWNO.js} +3 -3
  102. package/studio/web/assets/{mermaid.core-CP8aNNYm.js → mermaid.core-BlkGaMIH.js} +5 -5
  103. package/studio/web/assets/{mindmap-definition-FBJOCRG2-CgTZ-rit.js → mindmap-definition-YA3MSWOX-IprMc_0j.js} +1 -1
  104. package/studio/web/assets/notifications-BukjVuVe.js +1 -0
  105. package/studio/web/assets/{notifications-D3tIQ4sg.js → notifications-DvBJIIO0.js} +1 -1
  106. package/studio/web/assets/{packet-AYTQ26CC-DEyoPtPb.js → packet-AYTQ26CC-Bi3V04Zi.js} +1 -1
  107. package/studio/web/assets/{pegDiagram-XKGWAZYB-DrD-7sD9.js → pegDiagram-XKGWAZYB-BNuPDLZY.js} +1 -1
  108. package/studio/web/assets/{pie-WAS4IAKB-wjj-EI1d.js → pie-WAS4IAKB-_6DoDbng.js} +1 -1
  109. package/studio/web/assets/{pieDiagram-E7YTZNPT-GntqDCzv.js → pieDiagram-E7YTZNPT-DqNb6Ht2.js} +1 -1
  110. package/studio/web/assets/{profile-DZWU7MgT.js → profile-B-jyYdzV.js} +1 -1
  111. package/studio/web/assets/{quadrantDiagram-AXDQQJYC-0UeqXQGd.js → quadrantDiagram-AXDQQJYC-BGH9E2YR.js} +1 -1
  112. package/studio/web/assets/{radar-RG4KPBEZ-DFSA5h7k.js → radar-RG4KPBEZ-DDdVczcL.js} +1 -1
  113. package/studio/web/assets/{railroad-74A4TZTK-CaOUG9wR.js → railroad-74A4TZTK-BJUP4Jds.js} +1 -1
  114. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +1 -0
  115. package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +1 -0
  116. package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +1 -0
  117. package/studio/web/assets/{railroadDiagram-O6MQD6OU-yHUELZaV.js → railroadDiagram-O6MQD6OU-DGTPh2KZ.js} +1 -1
  118. package/studio/web/assets/{regions-CkyurXzE.js → regions-GN6Mu5aH.js} +1 -1
  119. package/studio/web/assets/{register-BUyhWjBO.js → register-CU-gCfLP.js} +3 -3
  120. package/studio/web/assets/{requirementDiagram-EFPCY7ZU-DNEGFjuW.js → requirementDiagram-BXWQKSXE-BJnO6uLz.js} +1 -1
  121. package/studio/web/assets/{sankeyDiagram-P5KCCOFB-DpyAmSVR.js → sankeyDiagram-P5KCCOFB-0vSOdymH.js} +1 -1
  122. package/studio/web/assets/{sequenceDiagram-WJ2MYXX4-TyaT7xNk.js → sequenceDiagram-WJ2MYXX4-hETizDWE.js} +1 -1
  123. package/studio/web/assets/{setComponentFile-CeyKSZAa.js → setComponentFile-D7Fww0lC.js} +1 -1
  124. package/studio/web/assets/{setup-D2kn7cAA.js → setup-DG3Ws0Gg.js} +2 -2
  125. package/studio/web/assets/{stateDiagram-HBIQ2CUA-CeEdTArZ.js → stateDiagram-D77RDMKH-CdYQ_KtC.js} +1 -1
  126. package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +1 -0
  127. package/studio/web/assets/status-koJd66XF.js +61 -0
  128. package/studio/web/assets/{swimlanes-XN3QIQJK-B54FmF46.js → swimlanes-42K2YHIH-B8cHIpU4.js} +1 -1
  129. package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +8 -0
  130. package/studio/web/assets/{tabs-B_G5zscN.js → tabs-BrHu7gJi.js} +1 -1
  131. package/studio/web/assets/{timeline-definition-24CTP7MA-D-a9ujbo.js → timeline-definition-24CTP7MA-BJWYSXqF.js} +1 -1
  132. package/studio/web/assets/{toggleHighContrast-C0UW6rI2.js → toggleHighContrast-CGksgPgv.js} +1 -1
  133. package/studio/web/assets/{treeView-Q6P3EWNA-CrW_6JnS.js → treeView-Q6P3EWNA-qxe_v6CQ.js} +1 -1
  134. package/studio/web/assets/{treemap-WGGIJYW6-BxyYLdP_.js → treemap-WGGIJYW6-dDo97XXF.js} +1 -1
  135. package/studio/web/assets/{tsMode-DBC0zmDx.js → tsMode-D3T_5QzW.js} +1 -1
  136. package/studio/web/assets/{typescript-DApRQir3.js → typescript-BegjUvtB.js} +1 -1
  137. package/studio/web/assets/{useEntityRestURL-DB6JStU1.js → useEntityRestURL-BicFpx_N.js} +1 -1
  138. package/studio/web/assets/{useLocalStorage-Dtj1QS8_.js → useLocalStorage-BqMR3D8_.js} +1 -1
  139. package/studio/web/assets/vendor-core-c2JRRJpV.js +58 -0
  140. package/studio/web/assets/vendor-datadog-CLUcJXOo.js +6 -0
  141. package/studio/web/assets/{vendor-react-Dyj4O3HE.js → vendor-react-CJV_K1u4.js} +1 -1
  142. package/studio/web/assets/vendor-tanstack-DxzraizX.js +1 -0
  143. package/studio/web/assets/{vendor-ui-vhu-UHhF.js → vendor-ui-BUjK0h8a.js} +2 -2
  144. package/studio/web/assets/{vennDiagram-4TSXK5OY-Cy7s7Mpy.js → vennDiagram-4TSXK5OY-A3i-lCdl.js} +1 -1
  145. package/studio/web/assets/{wardley-WFR3VGLG-BeBL35g2.js → wardley-WFR3VGLG-B0ik-_6g.js} +1 -1
  146. package/studio/web/assets/{wardleyDiagram-VM6X3IG4-BylmIGSg.js → wardleyDiagram-VM6X3IG4-CjrkKWUR.js} +1 -1
  147. package/studio/web/assets/{workers-C0bFIedw.js → workers-Bmx4l_9B.js} +1 -1
  148. package/studio/web/assets/x-DIzaLEdK.js +1 -0
  149. package/studio/web/assets/{xml-HWd01lU-.js → xml-Bg40Rcra.js} +1 -1
  150. package/studio/web/assets/{xychartDiagram-S5SC5T6Z-CoKALMXr.js → xychartDiagram-S5SC5T6Z-Biok4GYV.js} +1 -1
  151. package/studio/web/assets/{yaml-CIH0Nt-h.js → yaml-DolDvfXE.js} +1 -1
  152. package/studio/web/index.html +14 -14
  153. package/studio/web/assets/Chat-JpO8EtUu.js +0 -2067
  154. package/studio/web/assets/FloatingChat-Bcj3xSZu.js +0 -23
  155. package/studio/web/assets/applications-ByqLRKyZ.js +0 -296
  156. package/studio/web/assets/architecture-7GRP2DOG-DNdx5tEU.js +0 -1
  157. package/studio/web/assets/authStore-qKmCZcaf.js +0 -3
  158. package/studio/web/assets/channel-DtCV8PTL.js +0 -1
  159. package/studio/web/assets/chunk-R7TYR2AO-Irip67yr.js +0 -10
  160. package/studio/web/assets/classDiagram-DTDB5LWJ-DbO_dCNE.js +0 -1
  161. package/studio/web/assets/classDiagram-v2-JRS7N3AN-DbO_dCNE.js +0 -1
  162. package/studio/web/assets/eventmodeling-NTZA5JFV-5jbe4A5P.js +0 -1
  163. package/studio/web/assets/flowDiagram-A5DVABFB-Dp9Ezlow.js +0 -1
  164. package/studio/web/assets/index-aSt5tY-L.js +0 -824
  165. package/studio/web/assets/index.lazy-B9jiPwT8.js +0 -2
  166. package/studio/web/assets/notifications-CUoYgU98.js +0 -1
  167. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bc0Qi0WH.js +0 -1
  168. package/studio/web/assets/railroad-ebnf-LZEXJU2U-G8rVVZ2C.js +0 -1
  169. package/studio/web/assets/railroad-peg-WCYAUIDC-CrehKBhC.js +0 -1
  170. package/studio/web/assets/stateDiagram-v2-4QOOHH4V-D4tuw9Su.js +0 -1
  171. package/studio/web/assets/status-D7Xn5ePA.js +0 -61
  172. package/studio/web/assets/swimlanesDiagram-VK2B7HYN-XOhmNEvq.js +0 -8
  173. package/studio/web/assets/vendor-core-RCcadM3e.js +0 -73
  174. package/studio/web/assets/vendor-datadog-BRv-mOv1.js +0 -6
  175. package/studio/web/assets/vendor-tanstack-BiFWSB3W.js +0 -1
  176. package/studio/web/assets/x-B9o9hsep.js +0 -1
  177. /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.CHECKPOINT_ALGORITHM = 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");
@@ -90,7 +95,6 @@ const PrimaryRocksDatabase_ts_1 = require("./PrimaryRocksDatabase.js");
90
95
  const replayLogs_ts_1 = require("./replayLogs.js");
91
96
  const node_os_1 = require("node:os");
92
97
  const RocksIndexStore_ts_1 = require("./RocksIndexStore.js");
93
- const when_ts_1 = require("../utility/when.js");
94
98
  const rocksMemoryConfig_ts_1 = require("../utility/rocksMemoryConfig.js");
95
99
  const processManagement_js_1 = require("../utility/processManagement/processManagement.js");
96
100
  const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
@@ -321,6 +325,23 @@ const rocksdbDatabaseEnvs = new Map();
321
325
  (0, globals_js_1._assignPackageExport)('databases', exports.databases);
322
326
  (0, globals_js_1._assignPackageExport)('tables', exports.tables);
323
327
  const NEXT_TABLE_ID = Symbol.for('next-table-id');
328
+ /**
329
+ * True when a descriptor claims an index build no live operation in this process can own. The PID and
330
+ * worker generation cannot answer that alone: a container reuses PID 1 and starts the in-memory
331
+ * generation back at 1 while the persisted one is higher. A descriptor with no incarnation was written
332
+ * before the field existed, so it belongs to an earlier process; a thread started without one of its
333
+ * own cannot judge, and falls back rather than declaring a live build dead.
334
+ */
335
+ function isAbandonedIndexBuild(descriptor, currentRestartGeneration) {
336
+ if (!descriptor)
337
+ return false;
338
+ if (descriptor.indexingPID && descriptor.indexingPID !== process.pid)
339
+ return true;
340
+ if (descriptor.restartNumber < currentRestartGeneration)
341
+ return true;
342
+ const incarnation = manageThreads.processIncarnation;
343
+ return !!descriptor.indexingPID && incarnation != null && descriptor.indexingIncarnation !== incarnation;
344
+ }
324
345
  // How many times the schema load will try to finish a tombstoned drop before
325
346
  // giving up for the rest of this process's lifetime. A drop that fails once
326
347
  // almost always fails identically forever - the usual cause is a RocksDB
@@ -590,6 +611,7 @@ function readMetaDb(path, defaultTable, databaseName = DEFAULT_DATABASE_NAME, au
590
611
  rootStore = (0, lmdb_1.open)(envInit);
591
612
  lmdbDatabaseEnvs.set(path, rootStore);
592
613
  }
614
+ rootStore.dbisDb?.resetReadTxn();
593
615
  return initStores(path, rootStore, databaseName, defaultTable, auditPath, isLegacy);
594
616
  }
595
617
  catch (error) {
@@ -835,6 +857,8 @@ function initStores(path, rootStore, databaseName, defaultTable, auditPath, isLe
835
857
  indices[attribute.name] = dbi;
836
858
  indices[attribute.name].indexNulls = attribute.indexNulls;
837
859
  }
860
+ // the only way a thread that never declares the schema reaches Table.indices
861
+ indices[attribute.name].isIndexing = !!attribute.indexingPID;
838
862
  const existingAttribute = existingAttributes.find((existingAttribute) => existingAttribute.name === attribute.name);
839
863
  if (existingAttribute)
840
864
  existingAttributes.splice(existingAttributes.indexOf(existingAttribute), 1, attribute);
@@ -1147,6 +1171,10 @@ async function dropDatabase(databaseName) {
1147
1171
  delete exports.databases[databaseName];
1148
1172
  exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
1149
1173
  if (rootStore) {
1174
+ // awaited: retirement stops the loop admitting work, the barrier is what says the pass that was
1175
+ // already running has released the stores this is about to close and unlink
1176
+ await rootStore.auditStore?.stopAuditCleanup?.();
1177
+ (0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
1150
1178
  if (rootStore.status === 'open') {
1151
1179
  if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1152
1180
  rootStore.close();
@@ -1164,6 +1192,8 @@ async function dropDatabase(databaseName) {
1164
1192
  // the drop lock now (still before any destructive step)
1165
1193
  if (rootStore instanceof rocksdb_js_1.RocksDatabase)
1166
1194
  lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
1195
+ await rootStore.auditStore?.stopAuditCleanup?.();
1196
+ (0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
1167
1197
  if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1168
1198
  rootStore.close();
1169
1199
  rootStore.destroy();
@@ -1206,10 +1236,6 @@ function closeDatabase(databaseName) {
1206
1236
  continue;
1207
1237
  if (table.primaryStore.rootStore)
1208
1238
  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
1239
  }
1214
1240
  // a database with no tables (an empty schema, or one whose tables were all dropped) still holds
1215
1241
  // an open root store, tracked only on the defined-database entry rather than any table — include
@@ -1217,7 +1243,23 @@ function closeDatabase(databaseName) {
1217
1243
  const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
1218
1244
  if (definedRoot)
1219
1245
  rootStores.add(definedRoot);
1246
+ // before any table store closes, so no further pass is admitted. This is synchronous, so it cannot
1247
+ // await the drain barrier stopAuditCleanup() returns; what covers it is the in-pass status checks,
1248
+ // plus the fact that its production callers reach it only for RocksDB databases, whose pass is one
1249
+ // synchronous purgeLogs() call with nothing suspended mid-removal.
1250
+ for (const rootStore of rootStores)
1251
+ rootStore.auditStore?.stopAuditCleanup?.();
1252
+ for (const tableName in dbTables) {
1253
+ const table = dbTables[tableName];
1254
+ if (!table?.primaryStore)
1255
+ continue;
1256
+ for (const indexName in table.indices || {}) {
1257
+ closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
1258
+ }
1259
+ closeStore(table.primaryStore, `table ${tableName}`);
1260
+ }
1220
1261
  for (const rootStore of rootStores) {
1262
+ (0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
1221
1263
  closeStore(rootStore.dbisDb, 'attributes store');
1222
1264
  closeStore(rootStore, 'root store');
1223
1265
  lmdbDatabaseEnvs.delete(rootStore.path);
@@ -1778,15 +1820,13 @@ function table(tableDefinition) {
1778
1820
  if (changed ||
1779
1821
  indexFormatNeedsPersist ||
1780
1822
  attributeDescriptor?.indexingFailed ||
1781
- (attributeDescriptor?.indexingPID && attributeDescriptor?.indexingPID !== process.pid) ||
1782
- attributeDescriptor?.restartNumber < currentRestartGeneration) {
1823
+ isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
1783
1824
  hasChanges = true;
1784
1825
  exclusiveLock();
1785
1826
  attributeDescriptor = attributesDbi.getSync(dbiKey);
1786
1827
  if (structurallyChanged ||
1787
1828
  attributeDescriptor?.indexingFailed ||
1788
- (attributeDescriptor?.indexingPID && attributeDescriptor?.indexingPID !== process.pid) ||
1789
- attributeDescriptor?.restartNumber < currentRestartGeneration) {
1829
+ isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
1790
1830
  hasChanges = true;
1791
1831
  if (attribute.indexNulls === undefined)
1792
1832
  attribute.indexNulls = true;
@@ -1805,9 +1845,20 @@ function table(tableDefinition) {
1805
1845
  // representation-only option difference — preserve the checkpoint so the backfill
1806
1846
  // resumes rather than restarts. Canonicalized to match structurallyChanged above.
1807
1847
  const indexOptionsChanged = canonicalIndexKey(attributeDescriptor?.indexed) !== canonicalIndexKey(attribute.indexed);
1808
- attribute.lastIndexedKey = indexOptionsChanged
1809
- ? undefined
1810
- : (attributeDescriptor?.lastIndexedKey ?? undefined);
1848
+ // Only a checkpoint runIndexing stamped with its own key resumes: earlier releases advanced
1849
+ // lastIndexedKey past failed and unflushed index writes, so any other is a full rebuild.
1850
+ const uncertifiedCheckpoint = attributeDescriptor?.lastIndexedKey !== undefined &&
1851
+ (attributeDescriptor.checkpointCertified === undefined ||
1852
+ attributeDescriptor.checkpointAlgorithm !== exports.CHECKPOINT_ALGORITHM ||
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;
1860
+ attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
1861
+ }
1811
1862
  // Explicit reindex is the upgrade path from a legacy (un-versioned) custom-index
1812
1863
  // object store to the versioned, VT-cacheable format. A full rebuild from scratch
1813
1864
  // (lastIndexedKey === undefined) clears the store and rewrites every node, so the
@@ -1829,6 +1880,9 @@ function table(tableDefinition) {
1829
1880
  // the new process reuses the old PID. Cleared on clean completion; left in place
1830
1881
  // on failure/crash so the next, higher-numbered restart re-triggers the backfill.
1831
1882
  attribute.restartNumber = currentRestartGeneration;
1883
+ if (manageThreads.processIncarnation != null)
1884
+ attribute.indexingIncarnation = manageThreads.processIncarnation;
1885
+ attribute.indexingBuildId = (0, node_crypto_1.randomBytes)(8).toString('hex');
1832
1886
  delete attribute.indexingFailed; // clear failure flag for the new run
1833
1887
  dbi.isIndexing = true;
1834
1888
  Object.defineProperty(attribute, 'dbi', { value: dbi, configurable: true, enumerable: false });
@@ -1844,6 +1898,12 @@ function table(tableDefinition) {
1844
1898
  reindexReasons.push(`crash-recovery(pid=${attributeDescriptor.indexingPID})`);
1845
1899
  if (attributeDescriptor?.restartNumber < currentRestartGeneration)
1846
1900
  reindexReasons.push('restart-number');
1901
+ if (uncertifiedCheckpoint)
1902
+ reindexReasons.push('uncertified-checkpoint');
1903
+ if (attributeDescriptor?.indexingPID === process.pid &&
1904
+ manageThreads.processIncarnation != null &&
1905
+ attributeDescriptor.indexingIncarnation !== manageThreads.processIncarnation)
1906
+ reindexReasons.push('abandoned-build(previous process incarnation)');
1847
1907
  logger.info(`reindex ${databaseName}.${tableName}.${attribute.name}: reason=${reindexReasons.join(',') || 'unknown'}`);
1848
1908
  // we only set indexing nulls to true if new or reindexing, we can't have partial indexing of null
1849
1909
  attributesToIndex.push(attribute);
@@ -1856,12 +1916,22 @@ function table(tableDefinition) {
1856
1916
  // workers / a reload would treat the still-partial index as ready and return incomplete results.
1857
1917
  attribute.indexingPID = attributeDescriptor.indexingPID;
1858
1918
  attribute.lastIndexedKey = attributeDescriptor.lastIndexedKey;
1919
+ if (attributeDescriptor.checkpointCertified !== undefined) {
1920
+ attribute.checkpointCertified = attributeDescriptor.checkpointCertified;
1921
+ attribute.checkpointAlgorithm = attributeDescriptor.checkpointAlgorithm;
1922
+ }
1859
1923
  // Carry the in-progress restart generation too, so persisting this metadata-only
1860
1924
  // change doesn't drop it and break the crash-recovery trigger for the running backfill.
1861
1925
  attribute.restartNumber = attributeDescriptor.restartNumber;
1926
+ attribute.indexingIncarnation = attributeDescriptor.indexingIncarnation;
1927
+ attribute.indexingBuildId = attributeDescriptor.indexingBuildId;
1862
1928
  if (attributeDescriptor.indexingFailed)
1863
1929
  attribute.indexingFailed = attributeDescriptor.indexingFailed;
1864
1930
  }
1931
+ // The declared attribute never carries the stamp, so any rewrite of a descriptor that has
1932
+ // one would drop it and make a completed index look like a pre-stamp build.
1933
+ if (attribute.checkpointAlgorithm === undefined && attributeDescriptor?.checkpointAlgorithm !== undefined)
1934
+ attribute.checkpointAlgorithm = attributeDescriptor.checkpointAlgorithm;
1865
1935
  attributesDbi.put(dbiKey, attribute);
1866
1936
  }
1867
1937
  // If a migration is in progress (indexingPID set), any newly opened dbi must also
@@ -1892,7 +1962,10 @@ function table(tableDefinition) {
1892
1962
  }
1893
1963
  logger.trace(`${tableName} table loading, running index`);
1894
1964
  if (attributesToIndex.length > 0 || indicesToRemove.length > 0) {
1895
- Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove);
1965
+ // captured before the backfill can rewrite the attributes
1966
+ const buildIds = new Map(attributesToIndex.map((attribute) => [attribute, attribute.indexingBuildId]));
1967
+ const markSettled = () => markAbandonedIndexBuild(Table, rootStore, buildIds);
1968
+ Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove).then(markSettled, markSettled);
1896
1969
  }
1897
1970
  else if (hasChanges)
1898
1971
  signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
@@ -1964,40 +2037,238 @@ function canonicalizeIndexOptions(value) {
1964
2037
  }
1965
2038
  return value;
1966
2039
  }
2040
+ // Bumped when a change alters which keys a checkpoint may certify. A descriptor stamped by any other
2041
+ // version resumes as uncertified (full rebuild) rather than being trusted, and a completed index keeps
2042
+ // the stamp of the build that wrote it.
2043
+ exports.CHECKPOINT_ALGORITHM = 2;
1967
2044
  const MAX_OUTSTANDING_INDEXING = 1000;
1968
2045
  const MIN_OUTSTANDING_INDEXING = 10;
2046
+ const INDEXING_YIELD_INTERVAL = 100;
2047
+ // A resumable checkpoint is written only after a flush (see flushIndexStores), at most once per period
2048
+ // and never before this many more records: the flush seals every column family in the database, so a
2049
+ // slow backfill must not impose the period's flush rate on unrelated tables.
2050
+ let indexingCheckpointPeriodMs = 5000;
2051
+ let indexingCheckpointMinRecords = 10000;
2052
+ function setIndexingCheckpointPeriod(ms, minRecords = indexingCheckpointMinRecords) {
2053
+ const previous = { ms: indexingCheckpointPeriodMs, minRecords: indexingCheckpointMinRecords };
2054
+ indexingCheckpointPeriodMs = ms;
2055
+ indexingCheckpointMinRecords = minRecords;
2056
+ return previous;
2057
+ }
2058
+ const yieldEventTurn = () => new Promise((resolve) => setImmediate(resolve));
2059
+ // RocksDB index stores have no WAL (openRocksDatabase defaults disableWAL), so a flush is what makes the
2060
+ // entries a checkpoint certifies durable. A flush only covers writes issued before it started, so a caller
2061
+ // never joins one in flight: it joins the next one, which every backfill on that database asking meanwhile
2062
+ // shares — at most one in flight and one queued.
2063
+ const indexingFlushes = new WeakMap();
2064
+ function flushIndexStores(rootStore) {
2065
+ if (!(rootStore instanceof rocksdb_js_1.RocksDatabase))
2066
+ return;
2067
+ let flushes = indexingFlushes.get(rootStore);
2068
+ if (!flushes)
2069
+ indexingFlushes.set(rootStore, (flushes = {}));
2070
+ if (flushes.queued)
2071
+ return flushes.queued;
2072
+ const start = () => {
2073
+ flushes.queued = undefined;
2074
+ const flush = rootStore.flush().finally(() => {
2075
+ if (flushes.inFlight === flush)
2076
+ flushes.inFlight = undefined;
2077
+ });
2078
+ flushes.inFlight = flush;
2079
+ return flush;
2080
+ };
2081
+ if (!flushes.inFlight)
2082
+ return start();
2083
+ return (flushes.queued = flushes.inFlight.then(start, start));
2084
+ }
2085
+ function resumeStartKey(attributes) {
2086
+ let start;
2087
+ for (const attribute of attributes) {
2088
+ if (attribute.lastIndexedKey == undefined)
2089
+ return undefined;
2090
+ if (start === undefined || (0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
2091
+ start = attribute.lastIndexedKey;
2092
+ }
2093
+ return start;
2094
+ }
2095
+ // Bounded, unlike the exclusiveLock() spin above: that one runs on the declaring path, where the
2096
+ // caller is waiting on the result and there is nothing useful to do without the lock. This one runs
2097
+ // after a backfill has already settled, so a holder that never releases would wedge the worker's
2098
+ // event loop for nothing. Giving up costs only the marker, and the next load of the table
2099
+ // re-triggers the build regardless.
2100
+ exports.ABANDONED_MARK_LOCK_TIMEOUT = 10000;
2101
+ const abandonedMarkLockWait = new Int32Array(new SharedArrayBuffer(4));
2102
+ // `timeout` is the test seam; production callers take the default.
2103
+ function tryAcquireUpdateAttributesLock(rootStore, timeout = exports.ABANDONED_MARK_LOCK_TIMEOUT) {
2104
+ if (rootStore.tryLock('update-attributes'))
2105
+ return true;
2106
+ const startTime = performance.now();
2107
+ let waitTime = 1;
2108
+ while (!rootStore.tryLock('update-attributes')) {
2109
+ const elapsed = performance.now() - startTime;
2110
+ if (elapsed >= timeout)
2111
+ return false;
2112
+ // Atomics.wait rather than a busy spin: the section this guards is synchronous, so the wait
2113
+ // blocks this thread either way, but sleeping does not burn a core while it does.
2114
+ if (elapsed >= 2) {
2115
+ Atomics.wait(abandonedMarkLockWait, 0, 0, Math.min(waitTime, timeout - elapsed));
2116
+ if (waitTime < 16)
2117
+ waitTime *= 2;
2118
+ }
2119
+ }
2120
+ return true;
2121
+ }
2122
+ /**
2123
+ * Persists the failure marker for a build that ended without running one of runIndexing's own exit
2124
+ * paths, so something re-triggers it. Fenced on `indexingBuildId` inside the storage engine's catalog
2125
+ * serialization boundary, because a replacement generation (or another thread declaring different index
2126
+ * options) can claim the attribute before an outgoing build's promise settles, and marking that would fail
2127
+ * a live build. The fence read and write stay synchronous, and nothing here may throw because
2128
+ * `Table.indexingOperation` reaches operations-API callers.
2129
+ */
2130
+ async function markAbandonedIndexBuild(Table, rootStore, buildIds) {
2131
+ for (const [attribute, buildId] of buildIds) {
2132
+ try {
2133
+ let marked;
2134
+ if (buildId == null || Table.dbisDB.getSync(attribute.key)?.indexingBuildId !== buildId)
2135
+ continue;
2136
+ const markIfOwned = () => {
2137
+ const descriptor = Table.dbisDB.getSync(attribute.key);
2138
+ if (descriptor?.indexingBuildId === buildId && !descriptor.indexingFailed) {
2139
+ Table.dbisDB.putSync(attribute.key, { ...descriptor, indexingFailed: true });
2140
+ marked = true;
2141
+ }
2142
+ };
2143
+ if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
2144
+ if (!tryAcquireUpdateAttributesLock(rootStore)) {
2145
+ logger.warn(`Could not mark the abandoned index build of ${Table.databaseName}.${Table.tableName}.${attribute.name}: ` +
2146
+ `timed out after ${exports.ABANDONED_MARK_LOCK_TIMEOUT}ms waiting for the exclusive 'update-attributes' lock. ` +
2147
+ `The index stays incomplete and the next load of the table re-triggers the backfill.`);
2148
+ continue;
2149
+ }
2150
+ try {
2151
+ markIfOwned();
2152
+ }
2153
+ finally {
2154
+ rootStore.unlock('update-attributes');
2155
+ }
2156
+ }
2157
+ else {
2158
+ rootStore.transactionSync(markIfOwned);
2159
+ }
2160
+ if (marked)
2161
+ logger.warn(`Indexing of ${Table.databaseName}.${Table.tableName}.${attribute.name} ended without completing. ` +
2162
+ `The index stays incomplete and every query on the attribute reports it as not indexed yet; ` +
2163
+ `the next load of the table retries the backfill from the last checkpoint (indexingFailed=true).`);
2164
+ }
2165
+ catch (error) {
2166
+ // A store closed by shutdown is the common case, and it cannot be written to at all.
2167
+ try {
2168
+ logger.debug(`Could not mark the abandoned index build of ${Table.tableName}.${attribute.name}`, error);
2169
+ }
2170
+ catch { }
2171
+ }
2172
+ }
2173
+ }
1969
2174
  async function runIndexing(Table, attributes, indicesToRemove) {
2175
+ let checkpointing;
2176
+ let hadIndexingErrors = false;
2177
+ const attributeErrorReported = {};
2178
+ const onIndexPutRejected = (property, error) => {
2179
+ hadIndexingErrors = true;
2180
+ if (attributeErrorReported[property])
2181
+ return;
2182
+ attributeErrorReported[property] = true;
2183
+ logger.error(`Error indexing attribute ${property}`, error);
2184
+ };
2185
+ const putRejectionHandlers = attributes.map((attribute) => (error) => onIndexPutRejected(attribute.name, error));
1970
2186
  try {
1971
2187
  logger.info(`Indexing ${Table.tableName} attributes`, attributes);
1972
2188
  await signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName));
1973
2189
  let lastResolution;
2190
+ // The checkpoint and completion barriers have to cover every mutation still in flight: any of them
2191
+ // may reject after those barriers read hadIndexingErrors.
2192
+ const pendingMutations = new Set();
2193
+ let settleWaiter;
2194
+ const track = (result, onRejected) => {
2195
+ if (!result?.then)
2196
+ return result;
2197
+ const tracked = result.then(() => {
2198
+ pendingMutations.delete(tracked);
2199
+ settleWaiter?.();
2200
+ return false;
2201
+ }, (error) => {
2202
+ pendingMutations.delete(tracked);
2203
+ settleWaiter?.();
2204
+ onRejected(error);
2205
+ return true;
2206
+ });
2207
+ pendingMutations.add(tracked);
2208
+ return result;
2209
+ };
2210
+ // The tracked promises absorb their own rejections, so one failure never abandons its siblings.
2211
+ const drainMutations = async () => {
2212
+ if (!pendingMutations.size)
2213
+ return false;
2214
+ return (await Promise.all([...pendingMutations])).some(Boolean);
2215
+ };
2216
+ // Waiting on a chosen entry would stall behind a slow one the others have already overtaken.
2217
+ const nextSettlement = () => new Promise((resolve) => {
2218
+ settleWaiter = () => {
2219
+ settleWaiter = undefined;
2220
+ resolve(undefined);
2221
+ };
2222
+ });
1974
2223
  for (const index of indicesToRemove) {
1975
- lastResolution = index.drop();
2224
+ track(index.drop(), (error) => onIndexPutRejected(index.name, error));
1976
2225
  }
1977
2226
  let interrupted;
1978
- let hadIndexingErrors = false;
1979
- const attributeErrorReported = {};
1980
2227
  let indexed = 0;
1981
2228
  const attributesLength = attributes.length;
1982
2229
  await new Promise((resolve) => setImmediate(resolve)); // yield event turn, indexing should consistently take at least one event turn
1983
2230
  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
2231
+ const start = resumeStartKey(attributes);
2232
+ if (start === undefined) {
2233
+ for (const attribute of attributes) {
1991
2234
  if (attribute.dbi.clearAsync) {
1992
- // LMDB, note that we don't need to wait for this to complete, just gets enqueued in front of the other writes
1993
- attribute.dbi.clearAsync();
2235
+ // LMDB enqueues this ahead of the index writes, so the scan need not wait for it — but the
2236
+ // barriers must, or a rejected clear certifies a checkpoint over stale entries.
2237
+ track(attribute.dbi.clearAsync(), (error) => onIndexPutRejected(attribute.name, error));
1994
2238
  }
1995
2239
  else {
1996
2240
  await attribute.dbi.clear();
1997
2241
  }
1998
2242
  }
1999
2243
  }
2000
- let outstanding = 0;
2244
+ // A resumed scan starts at the checkpoint, so it must only name a key whose every predecessor is
2245
+ // durably indexed: persisted once the writes it covers have settled and flushed, frozen after any
2246
+ // record fails so the retry re-covers it, and stamped with its own key (see the trigger in table()).
2247
+ const persistCheckpoint = async (key) => {
2248
+ if (hadIndexingErrors)
2249
+ return;
2250
+ try {
2251
+ // Everything still in flight was issued for a key at or before this one: the scan has not
2252
+ // moved past it yet. So a failure among them is a failure this checkpoint would cover.
2253
+ const failed = await drainMutations();
2254
+ if (failed)
2255
+ return;
2256
+ await flushIndexStores(Table.primaryStore.rootStore);
2257
+ const puts = [];
2258
+ for (const attribute of attributes) {
2259
+ attribute.lastIndexedKey = key;
2260
+ attribute.checkpointCertified = key;
2261
+ attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
2262
+ puts.push(Table.dbisDB.put(attribute.key, attribute));
2263
+ }
2264
+ await Promise.all(puts);
2265
+ }
2266
+ catch (error) {
2267
+ logger.warn(`Could not persist the indexing checkpoint for ${Table.tableName}`, error);
2268
+ }
2269
+ };
2270
+ let nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
2271
+ let nextCheckpointRecord = indexingCheckpointMinRecords;
2001
2272
  // this means that a new attribute has been introduced that needs to be indexed
2002
2273
  for (const { key, value: record } of Table.primaryStore.getRange({
2003
2274
  start,
@@ -2005,96 +2276,90 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2005
2276
  versions: true,
2006
2277
  snapshot: false, // don't hold a read transaction this whole time
2007
2278
  })) {
2008
- if (!record)
2009
- continue; // deletion entry
2279
+ const atInterval = ++indexed % INDEXING_YIELD_INTERVAL === 0;
2010
2280
  // TODO: Do we ever need to interrupt due to a schema change that was not a restart?
2011
2281
  //if (Table.schemaVersion !== schemaVersion) return; // break out if there are any schema changes and let someone else pick it up
2012
- outstanding++;
2013
- // Custom indexes (e.g. HNSW) index synchronously and never raise `outstanding`, so the
2014
- // outstanding-based yield below never fires for them. Track that this row did synchronous
2282
+ // Custom indexes (e.g. HNSW) index synchronously and leave pendingMutations empty, so the
2283
+ // backpressure yield below never fires for them. Track that this row did synchronous
2015
2284
  // indexing work so we can still yield the event loop after it.
2016
2285
  let didSynchronousIndexing = false;
2017
2286
  // every index operation needs to be guarded by the version still be the same. If it has already changed before
2018
2287
  // we index, that's fine because indexing is idempotent, we can just put the same values again. If it changes
2019
2288
  // during the indexing, the indexing here will fail. This is also fine because it means the other thread will have
2020
2289
  // 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);
2290
+ if (record) {
2291
+ for (let i = 0; i < attributesLength; i++) {
2292
+ const attribute = attributes[i];
2293
+ const property = attribute.name;
2294
+ const index = attribute.dbi;
2295
+ const onPutRejected = putRejectionHandlers[i];
2296
+ try {
2297
+ const resolver = attribute.resolve;
2298
+ const value = record && (resolver ? resolver(record) : record[property]);
2299
+ if (index.customIndex) {
2300
+ index.customIndex.index(key, value);
2301
+ didSynchronousIndexing = true;
2302
+ continue;
2303
+ }
2304
+ const values = (0, commonUtility_ts_1.getIndexedValues)(value, index.indexNulls);
2305
+ if (values) {
2306
+ for (let i = 0, l = values.length; i < l; i++) {
2307
+ track(index.put(values[i], key), onPutRejected);
2308
+ }
2037
2309
  }
2038
2310
  }
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);
2311
+ catch (error) {
2312
+ hadIndexingErrors = true;
2313
+ if (!attributeErrorReported[property]) {
2314
+ // just report an indexing error once per attribute so we don't spam the logs.
2315
+ // A store closed by worker shutdown surfaces here as "Database not open"; that is
2316
+ // a benign interruption (the next generation re-runs the backfill), so don't log
2317
+ // it as an error — the outer catch returns quietly once the iterator also throws.
2318
+ attributeErrorReported[property] = true;
2319
+ if (Table.primaryStore?.rootStore?.status === 'closed')
2320
+ logger.debug(`Indexing attribute ${property} interrupted by store shutdown`, error);
2321
+ else
2322
+ logger.error(`Error indexing attribute ${property}`, error);
2323
+ }
2052
2324
  }
2053
2325
  }
2054
2326
  }
2055
- (0, when_ts_1.when)(lastResolution, () => outstanding--, (error) => {
2056
- outstanding--;
2057
- hadIndexingErrors = true;
2058
- logger.error(error);
2059
- });
2060
2327
  if (worker_threads_1.workerData && worker_threads_1.workerData.restartNumber !== manageThreads.restartNumber) {
2061
2328
  interrupted = true;
2062
2329
  }
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);
2068
- }
2069
- if (interrupted)
2070
- return;
2330
+ if (interrupted) {
2331
+ await drainMutations();
2332
+ await checkpointing;
2333
+ await persistCheckpoint(key);
2334
+ return;
2335
+ }
2336
+ if (atInterval && indexed >= nextCheckpointRecord && performance.now() >= nextCheckpointAt) {
2337
+ nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
2338
+ nextCheckpointRecord = indexed + indexingCheckpointMinRecords;
2339
+ await checkpointing;
2340
+ checkpointing = persistCheckpoint(key);
2071
2341
  }
2072
- if (outstanding > MAX_OUTSTANDING_INDEXING)
2073
- 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
2342
+ // Checked once per record, so a record's own fan-out can overshoot before the bound applies.
2343
+ while (pendingMutations.size > MAX_OUTSTANDING_INDEXING)
2344
+ await nextSettlement();
2345
+ if (atInterval || didSynchronousIndexing || pendingMutations.size > MIN_OUTSTANDING_INDEXING)
2346
+ await yieldEventTurn();
2078
2347
  }
2079
2348
  }
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.
2087
- try {
2088
- await lastResolution;
2089
- }
2090
- catch (error) {
2091
- hadIndexingErrors = true;
2092
- logger.error(error);
2349
+ await checkpointing;
2350
+ // A mutation that rejects after completion is declared has no build left to park.
2351
+ await drainMutations();
2352
+ // the tail since the last checkpoint is not durable until flushed; announcing the index complete
2353
+ // before that would outlive a crash that loses it
2354
+ if (!hadIndexingErrors) {
2355
+ try {
2356
+ await flushIndexStores(Table.primaryStore.rootStore);
2357
+ }
2358
+ catch (error) {
2359
+ hadIndexingErrors = true;
2360
+ logger.error(`Could not flush the indexes of ${Table.tableName} before marking them complete`, error);
2361
+ }
2093
2362
  }
2094
- // Yield one more event turn so any queued when() error callbacks (which fire as
2095
- // microtasks when their tracked promise settles) have a chance to set hadIndexingErrors
2096
- // before we decide whether to mark indexing as complete.
2097
- await new Promise((resolve) => setImmediate(resolve));
2098
2363
  if (hadIndexingErrors) {
2099
2364
  // Some records failed to index. Persist the failure marker in the descriptor so
2100
2365
  // the next call to table() (including after a restart with a fresh PID) re-triggers
@@ -2123,9 +2388,15 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2123
2388
  // update the attributes to indicate that we are finished
2124
2389
  for (const attribute of attributes) {
2125
2390
  delete attribute.lastIndexedKey;
2391
+ delete attribute.checkpointCertified;
2392
+ // Survives completion, unlike the checkpoint fields: without it an index built here is
2393
+ // indistinguishable from one a release that could skip a failed record declared complete.
2394
+ attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
2126
2395
  delete attribute.indexingPID;
2127
2396
  delete attribute.indexingFailed;
2128
2397
  delete attribute.restartNumber;
2398
+ delete attribute.indexingIncarnation;
2399
+ delete attribute.indexingBuildId;
2129
2400
  attribute.dbi.isIndexing = false;
2130
2401
  // Also clear isIndexing on the currently-active dbi in Table.indices, which may
2131
2402
  // differ from attribute.dbi if a resetDatabases() call during this migration
@@ -2142,6 +2413,7 @@ async function runIndexing(Table, attributes, indicesToRemove) {
2142
2413
  }
2143
2414
  }
2144
2415
  catch (error) {
2416
+ await checkpointing;
2145
2417
  // A worker shutting down closes its stores mid-backfill, so the range iterator or a
2146
2418
  // put throws (e.g. "Database not open" / "Iterator not initialized"). This is an
2147
2419
  // interruption, not a data error: the next worker generation re-runs the backfill via