@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2

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 (347) hide show
  1. package/bin/cliOperations.ts +12 -0
  2. package/bin/restart.ts +66 -6
  3. package/components/Application.ts +1134 -112
  4. package/components/DESIGN.md +421 -0
  5. package/components/OptionsWatcher.ts +368 -102
  6. package/components/Scope.ts +20 -1
  7. package/components/componentLoader.ts +43 -4
  8. package/components/deploymentOperations.ts +4 -1
  9. package/components/deploymentRecorder.ts +9 -2
  10. package/components/mcp/DESIGN.md +109 -0
  11. package/components/mcp/audit.ts +21 -17
  12. package/components/operations.js +284 -52
  13. package/components/operationsValidation.js +49 -2
  14. package/components/packageComponent.ts +25 -1
  15. package/components/requestRestart.ts +11 -0
  16. package/config/DESIGN.md +306 -0
  17. package/config/RootConfigWatcher.ts +191 -37
  18. package/config/configReadRetry.ts +62 -0
  19. package/config/configUtils.ts +78 -26
  20. package/config/parseConfigFile.ts +34 -0
  21. package/config/readConfigFileSync.ts +44 -0
  22. package/config/watcherArming.ts +59 -0
  23. package/config-root.schema.json +4 -0
  24. package/dataLayer/DESIGN.md +179 -0
  25. package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
  26. package/dataLayer/restoreMarker.ts +92 -25
  27. package/dist/bin/cliOperations.js +13 -0
  28. package/dist/bin/cliOperations.js.map +1 -1
  29. package/dist/bin/restart.js +42 -6
  30. package/dist/bin/restart.js.map +1 -1
  31. package/dist/components/Application.d.ts +104 -9
  32. package/dist/components/Application.js +954 -102
  33. package/dist/components/Application.js.map +1 -1
  34. package/dist/components/OptionsWatcher.d.ts +4 -1
  35. package/dist/components/OptionsWatcher.js +378 -104
  36. package/dist/components/OptionsWatcher.js.map +1 -1
  37. package/dist/components/Scope.js +15 -1
  38. package/dist/components/Scope.js.map +1 -1
  39. package/dist/components/componentLoader.js +35 -3
  40. package/dist/components/componentLoader.js.map +1 -1
  41. package/dist/components/deploymentOperations.js +4 -1
  42. package/dist/components/deploymentOperations.js.map +1 -1
  43. package/dist/components/deploymentRecorder.d.ts +4 -2
  44. package/dist/components/deploymentRecorder.js +1 -0
  45. package/dist/components/deploymentRecorder.js.map +1 -1
  46. package/dist/components/mcp/audit.d.ts +2 -1
  47. package/dist/components/mcp/audit.js +21 -17
  48. package/dist/components/mcp/audit.js.map +1 -1
  49. package/dist/components/operations.d.ts +28 -0
  50. package/dist/components/operations.js +263 -54
  51. package/dist/components/operations.js.map +1 -1
  52. package/dist/components/operationsValidation.js +48 -2
  53. package/dist/components/operationsValidation.js.map +1 -1
  54. package/dist/components/packageComponent.js +24 -0
  55. package/dist/components/packageComponent.js.map +1 -1
  56. package/dist/components/requestRestart.d.ts +1 -0
  57. package/dist/components/requestRestart.js +7 -0
  58. package/dist/components/requestRestart.js.map +1 -1
  59. package/dist/config/RootConfigWatcher.d.ts +2 -0
  60. package/dist/config/RootConfigWatcher.js +189 -35
  61. package/dist/config/RootConfigWatcher.js.map +1 -1
  62. package/dist/config/configReadRetry.d.ts +8 -0
  63. package/dist/config/configReadRetry.js +62 -0
  64. package/dist/config/configReadRetry.js.map +1 -0
  65. package/dist/config/configUtils.d.ts +10 -9
  66. package/dist/config/configUtils.js +63 -27
  67. package/dist/config/configUtils.js.map +1 -1
  68. package/dist/config/parseConfigFile.d.ts +4 -0
  69. package/dist/config/parseConfigFile.js +35 -0
  70. package/dist/config/parseConfigFile.js.map +1 -0
  71. package/dist/config/readConfigFileSync.d.ts +1 -0
  72. package/dist/config/readConfigFileSync.js +47 -0
  73. package/dist/config/readConfigFileSync.js.map +1 -0
  74. package/dist/config/watcherArming.d.ts +15 -0
  75. package/dist/config/watcherArming.js +59 -0
  76. package/dist/config/watcherArming.js.map +1 -0
  77. package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
  78. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  79. package/dist/dataLayer/restoreMarker.d.ts +21 -8
  80. package/dist/dataLayer/restoreMarker.js +94 -27
  81. package/dist/dataLayer/restoreMarker.js.map +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +9 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/json/systemSchema.json +3 -0
  86. package/dist/resources/DatabaseTransaction.d.ts +25 -0
  87. package/dist/resources/DatabaseTransaction.js +224 -18
  88. package/dist/resources/DatabaseTransaction.js.map +1 -1
  89. package/dist/resources/LMDBTransaction.d.ts +2 -1
  90. package/dist/resources/LMDBTransaction.js +22 -3
  91. package/dist/resources/LMDBTransaction.js.map +1 -1
  92. package/dist/resources/PrimaryRocksDatabase.js +22 -6
  93. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  94. package/dist/resources/RecordEncoder.d.ts +1 -1
  95. package/dist/resources/RecordEncoder.js +46 -10
  96. package/dist/resources/RecordEncoder.js.map +1 -1
  97. package/dist/resources/Resource.js +97 -13
  98. package/dist/resources/Resource.js.map +1 -1
  99. package/dist/resources/ResourceInterface.d.ts +8 -0
  100. package/dist/resources/RocksIndexStore.js +2 -1
  101. package/dist/resources/RocksIndexStore.js.map +1 -1
  102. package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
  103. package/dist/resources/RocksTransactionLogStore.js +104 -33
  104. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  105. package/dist/resources/Table.d.ts +58 -7
  106. package/dist/resources/Table.js +1143 -348
  107. package/dist/resources/Table.js.map +1 -1
  108. package/dist/resources/analytics/write.d.ts +3 -0
  109. package/dist/resources/analytics/write.js +49 -16
  110. package/dist/resources/analytics/write.js.map +1 -1
  111. package/dist/resources/auditStore.d.ts +170 -0
  112. package/dist/resources/auditStore.js +457 -11
  113. package/dist/resources/auditStore.js.map +1 -1
  114. package/dist/resources/crdt.d.ts +10 -0
  115. package/dist/resources/crdt.js +22 -0
  116. package/dist/resources/crdt.js.map +1 -1
  117. package/dist/resources/dataLoader.js +3 -4
  118. package/dist/resources/dataLoader.js.map +1 -1
  119. package/dist/resources/databases.d.ts +16 -13
  120. package/dist/resources/databases.js +625 -177
  121. package/dist/resources/databases.js.map +1 -1
  122. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  123. package/dist/resources/derivedIndexRegistry.js +68 -0
  124. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  125. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  126. package/dist/resources/derivedIndexRuntime.js +2027 -0
  127. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  128. package/dist/resources/graphql.js +3 -2
  129. package/dist/resources/graphql.js.map +1 -1
  130. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
  131. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
  132. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  133. package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
  134. package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
  135. package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
  136. package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
  137. package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
  138. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
  139. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  140. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  141. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  142. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  143. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  144. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  145. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
  146. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
  147. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
  148. package/dist/resources/nodeIdMapping.d.ts +5 -0
  149. package/dist/resources/nodeIdMapping.js +49 -0
  150. package/dist/resources/nodeIdMapping.js.map +1 -1
  151. package/dist/resources/recordLock.d.ts +47 -4
  152. package/dist/resources/recordLock.js +138 -7
  153. package/dist/resources/recordLock.js.map +1 -1
  154. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  155. package/dist/resources/recordLockCoordinator.js +2597 -0
  156. package/dist/resources/recordLockCoordinator.js.map +1 -0
  157. package/dist/resources/replayLogs.js +5 -0
  158. package/dist/resources/replayLogs.js.map +1 -1
  159. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  160. package/dist/resources/replicatedApplyFailure.js +63 -0
  161. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  162. package/dist/resources/scheduler/scheduler.js +3 -3
  163. package/dist/resources/scheduler/scheduler.js.map +1 -1
  164. package/dist/resources/search.d.ts +10 -4
  165. package/dist/resources/search.js +160 -40
  166. package/dist/resources/search.js.map +1 -1
  167. package/dist/resources/tracked.d.ts +5 -1
  168. package/dist/resources/tracked.js +74 -23
  169. package/dist/resources/tracked.js.map +1 -1
  170. package/dist/resources/transactionBroadcast.js +4 -6
  171. package/dist/resources/transactionBroadcast.js.map +1 -1
  172. package/dist/security/auth.js +59 -23
  173. package/dist/security/auth.js.map +1 -1
  174. package/dist/security/deferredAuthentication.d.ts +11 -0
  175. package/dist/security/deferredAuthentication.js +25 -3
  176. package/dist/security/deferredAuthentication.js.map +1 -1
  177. package/dist/security/jsLoader.js +9 -4
  178. package/dist/security/jsLoader.js.map +1 -1
  179. package/dist/server/REST.js +39 -5
  180. package/dist/server/REST.js.map +1 -1
  181. package/dist/server/http.d.ts +5 -1
  182. package/dist/server/http.js +34 -2
  183. package/dist/server/http.js.map +1 -1
  184. package/dist/server/mqtt.js +5 -1
  185. package/dist/server/mqtt.js.map +1 -1
  186. package/dist/server/serverHelpers/Headers.d.ts +2 -0
  187. package/dist/server/serverHelpers/Headers.js +6 -0
  188. package/dist/server/serverHelpers/Headers.js.map +1 -1
  189. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  190. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  191. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  192. package/dist/server/serverHelpers/Request.d.ts +5 -10
  193. package/dist/server/serverHelpers/Request.js +38 -136
  194. package/dist/server/serverHelpers/Request.js.map +1 -1
  195. package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
  196. package/dist/server/serverHelpers/contentTypes.js +189 -15
  197. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  198. package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
  199. package/dist/server/serverHelpers/serverUtilities.js +110 -20
  200. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  201. package/dist/server/serverHelpers/uwsServer.js +4 -1
  202. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  203. package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
  204. package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
  205. package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
  206. package/dist/server/storageReclamation.js +1 -1
  207. package/dist/server/storageReclamation.js.map +1 -1
  208. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  209. package/dist/server/threads/isolatedApplications.js +171 -0
  210. package/dist/server/threads/isolatedApplications.js.map +1 -0
  211. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  212. package/dist/server/threads/logRotationTransport.js +33 -0
  213. package/dist/server/threads/logRotationTransport.js.map +1 -0
  214. package/dist/server/threads/manageThreads.d.ts +64 -6
  215. package/dist/server/threads/manageThreads.js +261 -12
  216. package/dist/server/threads/manageThreads.js.map +1 -1
  217. package/dist/server/threads/socketRouter.d.ts +1 -0
  218. package/dist/server/threads/socketRouter.js +196 -13
  219. package/dist/server/threads/socketRouter.js.map +1 -1
  220. package/dist/server/threads/threadServer.js +30 -7
  221. package/dist/server/threads/threadServer.js.map +1 -1
  222. package/dist/utility/errors/hdbError.d.ts +24 -0
  223. package/dist/utility/errors/hdbError.js +58 -1
  224. package/dist/utility/errors/hdbError.js.map +1 -1
  225. package/dist/utility/hdbTerms.d.ts +2 -0
  226. package/dist/utility/hdbTerms.js +2 -0
  227. package/dist/utility/hdbTerms.js.map +1 -1
  228. package/dist/utility/logging/harper_logger.js +218 -38
  229. package/dist/utility/logging/harper_logger.js.map +1 -1
  230. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  231. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  232. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  233. package/dist/utility/logging/logRotation.d.ts +46 -0
  234. package/dist/utility/logging/logRotation.js +365 -0
  235. package/dist/utility/logging/logRotation.js.map +1 -0
  236. package/dist/utility/logging/logRotator.d.ts +1 -1
  237. package/dist/utility/logging/logRotator.js +172 -92
  238. package/dist/utility/logging/logRotator.js.map +1 -1
  239. package/dist/utility/npmUtilities.js +6 -4
  240. package/dist/utility/npmUtilities.js.map +1 -1
  241. package/dist/utility/watcherFallback.d.ts +0 -45
  242. package/dist/utility/watcherFallback.js +1 -125
  243. package/dist/utility/watcherFallback.js.map +1 -1
  244. package/dist/validation/configValidator.js +6 -3
  245. package/dist/validation/configValidator.js.map +1 -1
  246. package/index.ts +9 -0
  247. package/json/systemSchema.json +3 -0
  248. package/npm-shrinkwrap.json +177 -87
  249. package/package.json +13 -5
  250. package/resources/DESIGN.md +689 -19
  251. package/resources/DatabaseTransaction.ts +230 -17
  252. package/resources/LMDBTransaction.ts +21 -3
  253. package/resources/PrimaryRocksDatabase.ts +20 -7
  254. package/resources/RecordEncoder.ts +52 -10
  255. package/resources/Resource.ts +97 -13
  256. package/resources/ResourceInterface.ts +8 -0
  257. package/resources/RocksIndexStore.ts +2 -1
  258. package/resources/RocksTransactionLogStore.ts +111 -31
  259. package/resources/Table.ts +1273 -396
  260. package/resources/analytics/DESIGN.md +38 -0
  261. package/resources/analytics/write.ts +50 -17
  262. package/resources/auditStore.ts +460 -11
  263. package/resources/crdt.ts +22 -0
  264. package/resources/dataLoader.ts +3 -4
  265. package/resources/databases.ts +611 -147
  266. package/resources/derivedIndexRegistry.ts +56 -0
  267. package/resources/derivedIndexRuntime.ts +2292 -0
  268. package/resources/graphql.ts +3 -2
  269. package/resources/indexes/DESIGN.md +833 -0
  270. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
  271. package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
  272. package/resources/indexes/fullTextNativeBinding.ts +146 -0
  273. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  274. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  275. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
  276. package/resources/nodeIdMapping.ts +50 -0
  277. package/resources/record-locks.md +1407 -0
  278. package/resources/recordLock.ts +173 -7
  279. package/resources/recordLockCoordinator.ts +3082 -0
  280. package/resources/replayLogs.ts +5 -0
  281. package/resources/replicatedApplyFailure.ts +77 -0
  282. package/resources/scheduler/DESIGN.md +40 -0
  283. package/resources/scheduler/scheduler.ts +4 -4
  284. package/resources/search.ts +169 -49
  285. package/resources/tracked.ts +73 -22
  286. package/resources/transactionBroadcast.ts +4 -4
  287. package/security/DESIGN.md +175 -0
  288. package/security/auth.ts +53 -24
  289. package/security/deferredAuthentication.ts +24 -2
  290. package/security/jsLoader.ts +9 -4
  291. package/server/DESIGN.md +275 -0
  292. package/server/REST.ts +42 -6
  293. package/server/http.ts +34 -2
  294. package/server/mqtt.ts +6 -4
  295. package/server/serverHelpers/Headers.ts +5 -1
  296. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  297. package/server/serverHelpers/Request.ts +33 -131
  298. package/server/serverHelpers/contentTypes.ts +188 -15
  299. package/server/serverHelpers/serverUtilities.ts +157 -27
  300. package/server/serverHelpers/uwsServer.ts +4 -1
  301. package/server/serverHelpers/webSocketCloseReason.ts +25 -0
  302. package/server/storageReclamation.ts +2 -2
  303. package/server/threads/isolatedApplications.ts +157 -0
  304. package/server/threads/logRotationTransport.ts +40 -0
  305. package/server/threads/manageThreads.js +254 -12
  306. package/server/threads/socketRouter.ts +217 -11
  307. package/server/threads/threadServer.js +30 -7
  308. package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
  309. package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
  310. package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
  311. package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
  312. package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
  313. package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
  314. package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
  315. package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
  316. package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
  317. package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
  318. package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
  319. package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
  320. package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
  321. package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
  322. package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
  323. package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
  324. package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
  325. package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
  326. package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
  327. package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
  328. package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
  329. package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
  330. package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
  331. package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
  332. package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
  333. package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
  334. package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
  335. package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
  336. package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
  337. package/studio/web/index.html +1 -1
  338. package/utility/DESIGN.md +55 -0
  339. package/utility/errors/hdbError.ts +54 -0
  340. package/utility/hdbTerms.ts +2 -0
  341. package/utility/logging/harper_logger.ts +210 -30
  342. package/utility/logging/logGenerationCoordinator.ts +196 -0
  343. package/utility/logging/logRotation.ts +367 -0
  344. package/utility/logging/logRotator.ts +196 -91
  345. package/utility/npmUtilities.ts +6 -4
  346. package/utility/watcherFallback.ts +0 -122
  347. package/validation/configValidator.ts +6 -3
@@ -0,0 +1,146 @@
1
+ import type { FullTextDerivedIndexEngine } from './fullTextDerivedIndex.ts';
2
+ import { loggerWithTag } from '../../utility/logging/logger.ts';
3
+
4
+ const FULLTEXT_LIFECYCLE_API_VERSION = 1;
5
+ const FULLTEXT_MUTATION_BATCH_API_VERSION = 3;
6
+ const logger = loggerWithTag('fulltext-derived-index');
7
+
8
+ export interface NativeFullTextIndexConfiguration {
9
+ fields: Array<{ name: string; weight?: number }>;
10
+ analyzer: 'english@1';
11
+ stopWords?: boolean;
12
+ positions?: boolean;
13
+ surfaceTerms?: boolean;
14
+ limits: {
15
+ indexingThreads: number;
16
+ searchThreads: number;
17
+ writerMemoryBytes: number;
18
+ maxQueuedCommands: number;
19
+ maxQueuedBytes: number;
20
+ maxBatchBytes: number;
21
+ };
22
+ }
23
+
24
+ export type NativeFullTextRuntimeInfo = {
25
+ packageVersion: string;
26
+ tantivyVersion: string;
27
+ nativeAbiVersion: number;
28
+ lifecycleApiVersion: number;
29
+ mutationBatchApiVersion: number;
30
+ storageBackends: ReadonlyArray<'native'>;
31
+ limits: { maxCommitPayloadBytes: number };
32
+ };
33
+
34
+ export interface NativeFullTextModule {
35
+ NativeFullTextIndex: {
36
+ prototype: Pick<FullTextDerivedIndexEngine, 'applyMutationBatch' | 'publish' | 'close'>;
37
+ };
38
+ runtimeInfo(): Promise<NativeFullTextRuntimeInfo>;
39
+ validateNativeFullTextIndexOptions(
40
+ options: NativeFullTextIndexConfiguration & {
41
+ path: string;
42
+ indexId: string;
43
+ generation: string;
44
+ }
45
+ ): void;
46
+ openNativeFullTextIndex(
47
+ options: NativeFullTextIndexConfiguration & {
48
+ path: string;
49
+ indexId: string;
50
+ generation: string;
51
+ }
52
+ ): Promise<FullTextDerivedIndexEngine>;
53
+ inspectNativeFullTextIndex(
54
+ options: Omit<NativeFullTextIndexConfiguration, 'limits'> & {
55
+ path: string;
56
+ indexId: string;
57
+ generation: string;
58
+ }
59
+ ):
60
+ | { state: 'missing' | 'cursorless' }
61
+ | { state: 'checkpointed'; committedPayload: string }
62
+ | { state: 'incompatible'; code: string };
63
+ resetNativeFullTextIndex(options: {
64
+ path: string;
65
+ indexId: string;
66
+ }): Promise<{ state: 'missing' } | { state: 'reset'; retiredPath: string }>;
67
+ reclaimRetiredNativeFullTextIndexes(options: {
68
+ path: string;
69
+ retiredPath?: string;
70
+ }): Promise<{ removed: number; failed: number }>;
71
+ }
72
+
73
+ let bindingPromise: Promise<NativeFullTextModule> | undefined;
74
+ let bindingWarningLogged = false;
75
+ const validatedRuntimeInfo = new WeakMap<NativeFullTextModule, NativeFullTextRuntimeInfo>();
76
+
77
+ export async function loadFullTextNativeBinding(): Promise<NativeFullTextModule> {
78
+ if (!bindingPromise) {
79
+ const moduleName = '@harperfast/fulltext/native';
80
+ bindingPromise = import(moduleName).then(validateFullTextNativeBinding);
81
+ bindingPromise.catch((error) => {
82
+ bindingPromise = undefined;
83
+ if (!bindingWarningLogged) {
84
+ bindingWarningLogged = true;
85
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
86
+ try {
87
+ logger.warn?.(
88
+ `The @harperfast/fulltext native module is unavailable${typeof code === 'string' ? ` (${code})` : ''}`
89
+ );
90
+ } catch {}
91
+ }
92
+ });
93
+ }
94
+ return bindingPromise;
95
+ }
96
+
97
+ export async function validateFullTextNativeBinding(module: unknown): Promise<NativeFullTextModule> {
98
+ const prototype = (module as { NativeFullTextIndex?: { prototype?: Partial<FullTextDerivedIndexEngine> } })
99
+ ?.NativeFullTextIndex?.prototype;
100
+ if (
101
+ !module ||
102
+ typeof module !== 'object' ||
103
+ !('runtimeInfo' in module) ||
104
+ typeof module.runtimeInfo !== 'function' ||
105
+ !('NativeFullTextIndex' in module) ||
106
+ typeof module.NativeFullTextIndex !== 'function' ||
107
+ typeof prototype?.applyMutationBatch !== 'function' ||
108
+ typeof prototype.publish !== 'function' ||
109
+ typeof prototype.close !== 'function' ||
110
+ !('openNativeFullTextIndex' in module) ||
111
+ typeof module.openNativeFullTextIndex !== 'function' ||
112
+ !('validateNativeFullTextIndexOptions' in module) ||
113
+ typeof module.validateNativeFullTextIndexOptions !== 'function' ||
114
+ !('inspectNativeFullTextIndex' in module) ||
115
+ typeof module.inspectNativeFullTextIndex !== 'function' ||
116
+ !('resetNativeFullTextIndex' in module) ||
117
+ typeof module.resetNativeFullTextIndex !== 'function' ||
118
+ !('reclaimRetiredNativeFullTextIndexes' in module) ||
119
+ typeof module.reclaimRetiredNativeFullTextIndexes !== 'function'
120
+ )
121
+ throw new TypeError('@harperfast/fulltext/native does not implement the required Harper binding contract');
122
+ const binding = module as NativeFullTextModule;
123
+ const info = await binding.runtimeInfo();
124
+ if (
125
+ !info ||
126
+ typeof info.packageVersion !== 'string' ||
127
+ typeof info.tantivyVersion !== 'string' ||
128
+ !Number.isSafeInteger(info.nativeAbiVersion) ||
129
+ info.lifecycleApiVersion !== FULLTEXT_LIFECYCLE_API_VERSION ||
130
+ info.mutationBatchApiVersion !== FULLTEXT_MUTATION_BATCH_API_VERSION ||
131
+ !Array.isArray(info.storageBackends) ||
132
+ !info.storageBackends.includes('native') ||
133
+ !info.limits ||
134
+ !Number.isSafeInteger(info.limits.maxCommitPayloadBytes) ||
135
+ info.limits.maxCommitPayloadBytes <= 0
136
+ )
137
+ throw new TypeError('@harperfast/fulltext/native reported incompatible runtime capabilities');
138
+ validatedRuntimeInfo.set(binding, info);
139
+ return binding;
140
+ }
141
+
142
+ export function getValidatedFullTextRuntimeInfo(binding: NativeFullTextModule): NativeFullTextRuntimeInfo {
143
+ const info = validatedRuntimeInfo.get(binding);
144
+ if (!info) throw new Error('Full-text native binding has not been validated');
145
+ return info;
146
+ }
@@ -0,0 +1,531 @@
1
+ import { ClientError } from '../../utility/errors/hdbError.ts';
2
+ import { loggerWithTag } from '../../utility/logging/logger.ts';
3
+ import type { Id } from '../ResourceInterface.ts';
4
+ import type { RocksTransactionLogStore } from '../RocksTransactionLogStore.ts';
5
+ import {
6
+ DERIVED_INDEX_ACCEPTED,
7
+ DERIVED_INDEX_DEFERRED,
8
+ DerivedIndexRuntime,
9
+ readDerivedIndexReadiness,
10
+ readDerivedIndexCoverage,
11
+ sameDerivedIndexPositions,
12
+ type DerivedIndexBackend,
13
+ type DerivedIndexBackendHost,
14
+ type DerivedIndexBackendStateChange,
15
+ type DerivedIndexBatch,
16
+ type DerivedIndexCursor,
17
+ type DerivedIndexCoverage,
18
+ type DerivedIndexPositions,
19
+ type DerivedIndexDeliveryResult,
20
+ type DerivedIndexFlushReason,
21
+ type DerivedIndexMutation,
22
+ type DerivedIndexReadiness,
23
+ } from '../derivedIndexRuntime.ts';
24
+
25
+ const logger = loggerWithTag('HNSW');
26
+
27
+ /** The one durable cursor vector of a file-primary HNSW index, stored beside its node mappings. */
28
+ export const DERIVED_INDEX_CURSOR_KEY = Symbol.for('derived-index-cursor');
29
+ export const DEFAULT_MAX_INDEX_LAG_MILLISECONDS = 3000;
30
+ export const MAX_WAIT_FOR_INDEX_MILLISECONDS = 30_000;
31
+
32
+ export type DerivedNativeIndexHost = {
33
+ readiness: () => DerivedIndexReadiness;
34
+ coverage: (maxLagMilliseconds: number) => DerivedIndexCoverage;
35
+ waitForCoverage: (since: bigint, timeout: number, signal?: AbortSignal) => Promise<void>;
36
+ requestRebuild: () => boolean;
37
+ };
38
+
39
+ // Bounds on the queue between the runtime's delivery and the native applier. The runtime already
40
+ // chunks a delivery by records and estimated bytes; this caps how many chunks may wait.
41
+ const QUEUE_CAPACITY_BYTES = 64 * 1024 * 1024;
42
+ const APPLY_SLICE_MILLIS = 5;
43
+ const BARRIER_IDLE_MULTIPLE = 3;
44
+ const BARRIER_IDLE_CEILING_MILLISECONDS = 7_500;
45
+ // Writes to an index this far behind fail with a retryable 503 (see the runtime's lag policy).
46
+ const DEFAULT_MAX_LAG_MILLISECONDS = 30_000;
47
+
48
+ /**
49
+ * The native index methods the backend drives. `applyDerivedValue` inserts, replaces or removes
50
+ * one primary key's vector and stages its mapping as pending; `flushDerived` is the durability
51
+ * barrier that publishes the pending mappings; `resetDerivedStorage` destroys the native file.
52
+ */
53
+ export interface DerivedNativeIndex {
54
+ readonly indexStore: any;
55
+ applyDerivedValue(primaryKey: Id, vector: number[] | undefined, version?: number): void;
56
+ flushDerived(): Promise<void>;
57
+ resetDerivedStorage(): void;
58
+ assertDerivedValue(vector: unknown, label: string): void;
59
+ attachDerivedHost(host: DerivedNativeIndexHost): void;
60
+ }
61
+
62
+ /**
63
+ * `DerivedIndexBackend` over a file-primary HNSW index. `deliver()` only enqueues; an applier drains
64
+ * the queue in bounded time slices so a 4096-record chunk at ~0.35 ms per native insert does not
65
+ * hold the event loop for a second and a half. The barrier is the plane's `msync`, after which the
66
+ * pending mappings and then the cursor vector are published — in that order, so a crash between
67
+ * any two steps leaves a cursor that replays (idempotently) over a mapping that was never published,
68
+ * never a published cursor over native state the barrier did not cover.
69
+ */
70
+ export class HnswDerivedIndexBackend implements DerivedIndexBackend {
71
+ readonly id: string;
72
+ #index: DerivedNativeIndex;
73
+ #host?: DerivedIndexBackendHost;
74
+ #wake?: (change?: DerivedIndexBackendStateChange) => void;
75
+ #queue: DerivedIndexBatch[] = [];
76
+ #queuedBytes = 0;
77
+ #position = 0;
78
+ #scheduled = false;
79
+ #appliedCursor?: DerivedIndexCursor;
80
+ #appliedEpoch?: bigint;
81
+ #flushRequested = false;
82
+ #flushing?: Promise<void>;
83
+ #interruptBarrierAfter = 0;
84
+ #resetting?: Promise<void>;
85
+ #unindexable = 0;
86
+
87
+ constructor(id: string, index: DerivedNativeIndex) {
88
+ this.id = id;
89
+ this.#index = index;
90
+ }
91
+
92
+ attach(host: DerivedIndexBackendHost): void {
93
+ this.#host = host;
94
+ }
95
+
96
+ onStateChange(wake: (change?: DerivedIndexBackendStateChange) => void): () => void {
97
+ this.#wake = wake;
98
+ return () => {
99
+ if (this.#wake === wake) this.#wake = undefined;
100
+ };
101
+ }
102
+
103
+ getDurableCursor(): DerivedIndexCursor | undefined {
104
+ const cursor = this.#index.indexStore.getSync(DERIVED_INDEX_CURSOR_KEY);
105
+ return cursor === undefined ? undefined : (cursor as DerivedIndexCursor);
106
+ }
107
+
108
+ publishCoverage(positions: DerivedIndexPositions, ownerEpoch: bigint): void {
109
+ if (!this.#host?.isOwnerEpoch(ownerEpoch)) return;
110
+ const cursor = this.getDurableCursor();
111
+ if (!cursor) throw new Error('Cannot publish native index coverage without a durable cursor');
112
+ if (cursor.coverage && sameDerivedIndexPositions(cursor.coverage, positions)) return;
113
+ this.#index.indexStore.putSync(DERIVED_INDEX_CURSOR_KEY, { ...cursor, coverage: positions });
114
+ }
115
+
116
+ deliver(batch: DerivedIndexBatch): DerivedIndexDeliveryResult {
117
+ if (this.#queuedBytes >= QUEUE_CAPACITY_BYTES) return DERIVED_INDEX_DEFERRED;
118
+ this.#queue.push(batch);
119
+ this.#queuedBytes += batch.bytes;
120
+ this.#schedule();
121
+ return DERIVED_INDEX_ACCEPTED;
122
+ }
123
+
124
+ flush(_reason: DerivedIndexFlushReason): void {
125
+ this.#flushRequested = true;
126
+ if (this.#queue.length === 0 && !this.#scheduled) this.#runFlush();
127
+ }
128
+
129
+ /**
130
+ * Drop what is queued and resolve once no barrier is in flight. The runtime quiesces an epoch
131
+ * before it mints the next, so nothing of a newer epoch can be queued here yet.
132
+ */
133
+ async shutdown(_epoch: bigint): Promise<void> {
134
+ this.#dropQueue();
135
+ await Promise.allSettled([this.#flushing, this.#resetting]);
136
+ }
137
+
138
+ /**
139
+ * The cursor goes first: a crash anywhere after this line reopens cursorless and rebuilds, never
140
+ * on a format-valid cursor over a destroyed graph.
141
+ */
142
+ reset(_epoch: bigint): Promise<void> {
143
+ this.#dropQueue();
144
+ this.#appliedCursor = undefined;
145
+ this.#resetting = (async () => {
146
+ await this.#flushing;
147
+ this.#index.indexStore.removeSync(DERIVED_INDEX_CURSOR_KEY);
148
+ this.#index.resetDerivedStorage();
149
+ await this.#index.indexStore.clear();
150
+ })();
151
+ return this.#resetting.finally(() => (this.#resetting = undefined));
152
+ }
153
+
154
+ #dropQueue() {
155
+ this.#queue.length = 0;
156
+ this.#queuedBytes = 0;
157
+ this.#position = 0;
158
+ }
159
+
160
+ #schedule() {
161
+ if (this.#scheduled) return;
162
+ this.#scheduled = true;
163
+ setImmediate(() => {
164
+ this.#scheduled = false;
165
+ this.#applySlice();
166
+ });
167
+ }
168
+
169
+ #applySlice() {
170
+ // Application pauses while a barrier is in flight so the barrier publishes exactly the
171
+ // mappings it covers; the flush reschedules application when it settles.
172
+ if (this.#flushing) return;
173
+ const host = this.#host!;
174
+ const until = performance.now() + APPLY_SLICE_MILLIS;
175
+ const wasFull = this.#queuedBytes >= QUEUE_CAPACITY_BYTES;
176
+ let advancedCursor = false;
177
+ while (this.#queue.length > 0 && performance.now() < until) {
178
+ const batch = this.#queue[0];
179
+ if (!host.isOwnerEpoch(batch.ownerEpoch)) {
180
+ this.#queue.shift();
181
+ this.#queuedBytes -= batch.bytes;
182
+ this.#position = 0;
183
+ continue;
184
+ }
185
+ const records = batch.records;
186
+ while (this.#position < records.length && performance.now() < until) {
187
+ try {
188
+ this.#apply(records[this.#position++]);
189
+ } catch (error) {
190
+ this.#queue.length = 0;
191
+ this.#queuedBytes = 0;
192
+ this.#position = 0;
193
+ logger.error(`Derived index '${this.id}' could not apply a record; requesting a rebuild`, error);
194
+ this.#wake?.('failed');
195
+ return;
196
+ }
197
+ }
198
+ if (this.#position < records.length) break;
199
+ this.#queue.shift();
200
+ this.#queuedBytes -= batch.bytes;
201
+ this.#position = 0;
202
+ this.#appliedEpoch = batch.ownerEpoch;
203
+ if (batch.through) {
204
+ this.#appliedCursor = batch.through;
205
+ advancedCursor = true;
206
+ if (this.#mayInterruptForBarrier()) break;
207
+ }
208
+ }
209
+ if (this.#queue.length > 0) {
210
+ if (wasFull && this.#queuedBytes < QUEUE_CAPACITY_BYTES) this.#wake?.('changed');
211
+ if (this.#position === 0 && advancedCursor && this.#mayInterruptForBarrier()) this.#runFlush(true);
212
+ else this.#schedule();
213
+ return;
214
+ }
215
+ if (wasFull) this.#wake?.('changed');
216
+ if (this.#flushRequested) this.#runFlush();
217
+ }
218
+
219
+ #apply(mutation: DerivedIndexMutation) {
220
+ const { recordId, logVersion, state } = mutation;
221
+ if (state.kind !== 'record' || state.projection == null) {
222
+ this.#index.applyDerivedValue(recordId, undefined, logVersion);
223
+ return;
224
+ }
225
+ try {
226
+ this.#index.applyDerivedValue(recordId, state.projection as number[], state.version);
227
+ } catch (error) {
228
+ // The projection validated the vector, but the plane's dimensionality is only known to the
229
+ // worker holding it: a mismatch is this record's fault, not the index's. Same rule as the
230
+ // runtime's own 4xx handling — remove any entry and count it, never rebuild.
231
+ if (!(error instanceof ClientError)) throw error;
232
+ if (this.#unindexable++ === 0)
233
+ logger.warn?.(`Derived index '${this.id}' skipped a record its plane cannot hold: ${error.message}`);
234
+ this.#index.applyDerivedValue(recordId, undefined, state.version);
235
+ }
236
+ }
237
+
238
+ #mayInterruptForBarrier(): boolean {
239
+ return this.#flushRequested && performance.now() >= this.#interruptBarrierAfter;
240
+ }
241
+
242
+ #runFlush(interrupting = false) {
243
+ if (this.#flushing) return;
244
+ this.#flushRequested = false;
245
+ const started = performance.now();
246
+ const cursor = this.#appliedCursor;
247
+ const epoch = this.#appliedEpoch;
248
+ const host = this.#host!;
249
+ this.#flushing = (async () => {
250
+ await this.#index.flushDerived();
251
+ if (epoch !== undefined && !host.isOwnerEpoch(epoch)) return;
252
+ if (cursor) {
253
+ const coverage = this.getDurableCursor()?.coverage;
254
+ this.#index.indexStore.putSync(DERIVED_INDEX_CURSOR_KEY, coverage ? { ...cursor, coverage } : cursor);
255
+ }
256
+ })();
257
+ this.#flushing.then(
258
+ () => {
259
+ this.#flushing = undefined;
260
+ if (interrupting) {
261
+ const idle = Math.min(
262
+ BARRIER_IDLE_MULTIPLE * (performance.now() - started),
263
+ BARRIER_IDLE_CEILING_MILLISECONDS
264
+ );
265
+ this.#interruptBarrierAfter = performance.now() + idle;
266
+ }
267
+ this.#wake?.('changed');
268
+ if (this.#queue.length > 0) this.#schedule();
269
+ else if (this.#flushRequested) this.#runFlush();
270
+ },
271
+ (error) => {
272
+ this.#flushing = undefined;
273
+ logger.error(`Derived index '${this.id}' durability barrier failed`, error);
274
+ this.#wake?.('failed');
275
+ }
276
+ );
277
+ }
278
+ }
279
+
280
+ // One generation owns each physical backend; destructive cleanup also awaits superseded generations.
281
+ type RegisteredTable = { current: { Table: any }; owners: Set<{ Table: any }> };
282
+ type RegisteredBackend = {
283
+ tableId: number;
284
+ settle: () => Promise<void>;
285
+ };
286
+ type Registered = {
287
+ runtime: DerivedIndexRuntime;
288
+ tables: Map<number, RegisteredTable>;
289
+ backends: Map<string, RegisteredBackend>;
290
+ tableBackends: Map<number, Set<RegisteredBackend>>;
291
+ droppingTables: Set<number>;
292
+ retryUnavailable: Set<string>;
293
+ };
294
+ const runtimes = new WeakMap<object, Registered>();
295
+ const retryUnavailableByStore = new Set<string>();
296
+
297
+ function retryUnavailableKey(auditStore: RocksTransactionLogStore, backendId: string): string {
298
+ return `${auditStore.rootStore.path}\0${backendId}`;
299
+ }
300
+
301
+ function markUnavailableRetry(registered: Registered, auditStore: RocksTransactionLogStore, backendId: string): void {
302
+ registered.retryUnavailable.add(backendId);
303
+ retryUnavailableByStore.add(retryUnavailableKey(auditStore, backendId));
304
+ }
305
+
306
+ function consumeUnavailableRetry(
307
+ registered: Registered,
308
+ auditStore: RocksTransactionLogStore,
309
+ backendId: string
310
+ ): boolean {
311
+ const local = registered.retryUnavailable.delete(backendId);
312
+ const storeScoped = retryUnavailableByStore.delete(retryUnavailableKey(auditStore, backendId));
313
+ return local || storeScoped;
314
+ }
315
+
316
+ function runtimeFor(auditStore: RocksTransactionLogStore): Registered {
317
+ let registered = runtimes.get(auditStore);
318
+ if (registered) return registered;
319
+ const tables = new Map<number, RegisteredTable>();
320
+ const runtime = new DerivedIndexRuntime(
321
+ auditStore,
322
+ (tableId, recordId) => {
323
+ const entry = tables.get(tableId)?.current.Table.primaryStore.getEntry(recordId);
324
+ return entry?.value == null ? undefined : { version: entry.version, value: entry.value };
325
+ },
326
+ {
327
+ scanRecords: (tableId) =>
328
+ tables
329
+ .get(tableId)!
330
+ .current.Table.primaryStore.getRange({ versions: true, snapshot: false })
331
+ .map(({ key, value, version }) => ({ recordId: key, version, value })),
332
+ }
333
+ );
334
+ registered = {
335
+ runtime,
336
+ tables,
337
+ backends: new Map(),
338
+ tableBackends: new Map(),
339
+ droppingTables: new Set(),
340
+ retryUnavailable: new Set(),
341
+ };
342
+ runtimes.set(auditStore, registered);
343
+ return registered;
344
+ }
345
+
346
+ const warnedAuditIndexes = new Set<string>();
347
+
348
+ /**
349
+ * Register every post-commit custom index of a table with the shared derived-index runtime of its
350
+ * database. Returns the release for the table's registrations, or undefined when it has none. Runs
351
+ * on every worker; the runtime elects one owner per index.
352
+ */
353
+ export function attachDerivedIndexes(Table: any):
354
+ | {
355
+ close(dropping?: boolean): Promise<void>;
356
+ restoreAfterFailedDrop(): ReturnType<typeof attachDerivedIndexes>;
357
+ completeDrop(dropped?: boolean): void;
358
+ }
359
+ | undefined {
360
+ const hnswAttributes = Table.attributes.filter(
361
+ (attribute: any) => attribute.indexed?.type === 'HNSW' && Table.indices[attribute.name]?.customIndex
362
+ );
363
+ const attributes = hnswAttributes.filter((attribute: any) => Table.indices[attribute.name]?.customIndex?.postCommit);
364
+ if (Table.audit !== true && attributes.length > 0) {
365
+ throw new ClientError(
366
+ `Table '${Table.databaseName}.${Table.tableName}' must enable audit logging before using a post-commit derived index`
367
+ );
368
+ }
369
+ if (!Table.auditStore) return;
370
+ const auditStore = Table.auditStore as RocksTransactionLogStore;
371
+ const registered = runtimeFor(auditStore);
372
+ if (registered.droppingTables.has(Table.tableId)) return;
373
+ for (const attribute of hnswAttributes) {
374
+ const indexStore = Table.indices[attribute.name];
375
+ const id = `hnsw:${indexStore.name}`;
376
+ // A transient JS-backed declaration can precede the first native registration while a
377
+ // schema is being installed. Only an explicit transition away from an already-registered
378
+ // native backend should make a later native registration eligible to rearm an exhausted
379
+ // rebuild budget.
380
+ if (attribute.indexed.nativePlane === false && !indexStore.customIndex.postCommit && registered.backends.has(id))
381
+ markUnavailableRetry(registered, auditStore, id);
382
+ }
383
+ if (attributes.length === 0) return;
384
+ const installed = { Table };
385
+ let registeredTable = registered.tables.get(Table.tableId);
386
+ if (registeredTable) {
387
+ registeredTable.owners.add(installed);
388
+ registeredTable.current = installed;
389
+ } else {
390
+ registeredTable = { current: installed, owners: new Set([installed]) };
391
+ registered.tables.set(Table.tableId, registeredTable);
392
+ }
393
+ const releases: Array<() => Promise<void>> = [];
394
+ const backendIds: string[] = [];
395
+ const registeredBackends = new Map<string, RegisteredBackend>();
396
+ for (const attribute of attributes) {
397
+ const indexStore = Table.indices[attribute.name];
398
+ const index = indexStore.customIndex as DerivedNativeIndex & { postCommit: true };
399
+ const id = `hnsw:${indexStore.name}`;
400
+ const warningKey = `${Table.databaseName}.${Table.tableName}.${indexStore.name}`;
401
+ if (!warnedAuditIndexes.has(warningKey)) {
402
+ warnedAuditIndexes.add(warningKey);
403
+ logger.warn?.(
404
+ `Derived index ${indexStore.name} requires auditing; the audit API retains full record history for the configured retention window`
405
+ );
406
+ }
407
+ const resolver = Table.propertyResolvers?.[attribute.name];
408
+ const label = `Vector for attribute "${attribute.name}"`;
409
+ index.attachDerivedHost({
410
+ readiness: () => registered.runtime.getReadiness(id),
411
+ coverage: (maxLagMilliseconds) =>
412
+ readDerivedIndexCoverage(
413
+ auditStore,
414
+ id,
415
+ () => indexStore.getSync(DERIVED_INDEX_CURSOR_KEY),
416
+ maxLagMilliseconds
417
+ ),
418
+ requestRebuild: () => registered.runtime.requestRebuild(id),
419
+ waitForCoverage: (since, timeout, signal) => registered.runtime.waitForCoverage(id, since, timeout, signal),
420
+ });
421
+ let predecessor = registered.backends.get(id);
422
+ const inherited = predecessor;
423
+ const settlePredecessor = () => {
424
+ const current = predecessor;
425
+ if (!current) return Promise.resolve();
426
+ const settled = current.settle();
427
+ settled.then(
428
+ () => {
429
+ if (predecessor === current) predecessor = undefined;
430
+ },
431
+ () => {}
432
+ );
433
+ return settled;
434
+ };
435
+ const predecessorSettled = settlePredecessor();
436
+ predecessorSettled.catch(() => {});
437
+ if (registered.backends.get(id) === inherited) registered.backends.delete(id);
438
+ const release = registered.runtime.register({
439
+ backend: new HnswDerivedIndexBackend(id, index),
440
+ projections: new Map([
441
+ [
442
+ Table.tableId,
443
+ (record: any) => {
444
+ const vector = resolver ? resolver(record) : record[attribute.name];
445
+ if (vector == null) return undefined;
446
+ index.assertDerivedValue(vector, label);
447
+ return vector;
448
+ },
449
+ ],
450
+ ]),
451
+ options: { maxLagMilliseconds: attribute.indexed?.maxLagMilliseconds ?? DEFAULT_MAX_LAG_MILLISECONDS },
452
+ });
453
+ let settling: Promise<void> | undefined;
454
+ const registeredBackend: RegisteredBackend = {
455
+ tableId: Table.tableId,
456
+ settle: () => {
457
+ if (settling) return settling;
458
+ const attempt = Promise.allSettled([settlePredecessor(), release()]).then((results) => {
459
+ if (results[1].status === 'fulfilled') {
460
+ if (registered.backends.get(id) === registeredBackend) registered.backends.delete(id);
461
+ const tableBackends = registered.tableBackends.get(Table.tableId);
462
+ tableBackends?.delete(registeredBackend);
463
+ if (tableBackends?.size === 0) registered.tableBackends.delete(Table.tableId);
464
+ }
465
+ const failures = results
466
+ .filter((result): result is PromiseRejectedResult => result.status === 'rejected')
467
+ .map((result) => result.reason);
468
+ if (failures.length === 1) throw failures[0];
469
+ if (failures.length) throw new AggregateError(failures, `Could not settle HNSW backend '${id}'`);
470
+ });
471
+ settling = attempt;
472
+ attempt.catch(() => {
473
+ if (settling === attempt) settling = undefined;
474
+ });
475
+ return attempt;
476
+ },
477
+ };
478
+ let tableBackends = registered.tableBackends.get(Table.tableId);
479
+ if (!tableBackends) registered.tableBackends.set(Table.tableId, (tableBackends = new Set()));
480
+ tableBackends.add(registeredBackend);
481
+ registered.backends.set(id, registeredBackend);
482
+ registeredBackends.set(id, registeredBackend);
483
+ const retryUnavailable = consumeUnavailableRetry(registered, auditStore, id);
484
+ if (retryUnavailable && registered.runtime.getReadiness(id).state === 'unavailable')
485
+ registered.runtime.requestRebuild(id);
486
+ backendIds.push(id);
487
+ releases.push(() => registeredBackend.settle());
488
+ }
489
+ return {
490
+ async close(dropping = false) {
491
+ if (dropping) registered.droppingTables.add(Table.tableId);
492
+ const settlements = dropping
493
+ ? [...(registered.tableBackends.get(Table.tableId) ?? [])].map((backend) => backend.settle())
494
+ : releases.map((release) => release());
495
+ const tableRegistration = registered.tables.get(Table.tableId);
496
+ if (tableRegistration) {
497
+ tableRegistration.owners.delete(installed);
498
+ if (tableRegistration.owners.size === 0) {
499
+ registered.tables.delete(Table.tableId);
500
+ } else if (tableRegistration.current === installed) {
501
+ tableRegistration.current = tableRegistration.owners.values().next().value;
502
+ }
503
+ }
504
+ if (dropping) {
505
+ await Promise.all(settlements);
506
+ } else {
507
+ for (const result of await Promise.allSettled(settlements)) {
508
+ if (result.status === 'rejected')
509
+ logger.error(`Could not settle a superseded HNSW backend for table ${Table.tableId}`, result.reason);
510
+ }
511
+ }
512
+ },
513
+ restoreAfterFailedDrop() {
514
+ registered.droppingTables.delete(Table.tableId);
515
+ for (const [id, registeredBackend] of registeredBackends) {
516
+ const current = registered.backends.get(id);
517
+ if (current && current !== registeredBackend) return;
518
+ }
519
+ return attachDerivedIndexes(Table);
520
+ },
521
+ completeDrop(dropped = true) {
522
+ registered.droppingTables.delete(Table.tableId);
523
+ if (dropped) for (const id of backendIds) markUnavailableRetry(registered, auditStore, id);
524
+ },
525
+ };
526
+ }
527
+
528
+ /** Shared readiness of an index on any worker, registered or not. */
529
+ export function derivedIndexReadiness(auditStore: RocksTransactionLogStore, indexStoreName: string) {
530
+ return readDerivedIndexReadiness(auditStore, `hnsw:${indexStoreName}`);
531
+ }