@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
@@ -1,12 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HierarchicalNavigableSmallWorld = void 0;
4
+ exports.derivedValuesEqual = derivedValuesEqual;
5
+ const node_fs_1 = require("node:fs");
4
6
  const vector_ts_1 = require("./vector.js");
5
7
  const msgpackr_1 = require("msgpackr");
6
8
  const logger_ts_1 = require("../../utility/logging/logger.js");
7
9
  const hdbError_ts_1 = require("../../utility/errors/hdbError.js");
10
+ const hnswDerivedIndex_ts_1 = require("./hnswDerivedIndex.js");
11
+ const derivedIndexRuntime_ts_1 = require("../derivedIndexRuntime.js");
8
12
  const extended_iterable_1 = require("@harperfast/extended-iterable");
13
+ const rocksdb_js_1 = require("@harperfast/rocksdb-js");
14
+ const node_crypto_1 = require("node:crypto");
15
+ const hnswPlaneBinding_ts_1 = require("./hnswPlaneBinding.js");
9
16
  const logger = (0, logger_ts_1.loggerWithTag)('HNSW');
17
+ /** Element-wise equality of the array-like projections a derived index stores; anything else that failed `===` counts as changed. */
18
+ function derivedValuesEqual(a, b) {
19
+ if (a === b)
20
+ return true;
21
+ if (a == null || b == null || typeof a.length !== 'number' || a.length !== b.length)
22
+ return false;
23
+ for (let i = 0; i < a.length; i++)
24
+ if (a[i] !== b[i])
25
+ return false;
26
+ return true;
27
+ }
10
28
  // int8 scalar quantization of stored graph nodes is ON by default. Each node holds the
11
29
  // vector as a compact int8 `bin` plus a per-vector `scale`, roughly a 5x size reduction
12
30
  // over float32 and ~10x cheaper to decode (a single typed-array view instead of decoding
@@ -85,6 +103,10 @@ const ROUTING_EF = 1;
85
103
  // index's own auto-scaled ceiling so the worst case stays the same order. Schema and per-query ef
86
104
  // pins are authoritative cost ceilings; only an automatically scaled index widens from `limit`.
87
105
  const LIMIT_EF_MAX = 2 * AUTO_EF_CEILING;
106
+ // Rerank candidates per row a bounded nearest-neighbor query consumes, with a floor: each
107
+ // candidate costs a record load and an exact distance, so the rerank must not scale with ef.
108
+ const RERANK_FACTOR = 4;
109
+ const RERANK_MIN = 32;
88
110
  // Auto-scaled construction ef, used only when an index does not explicitly configure efConstruction.
89
111
  // At a constant efConstruction, edge quality erodes as the graph grows until true neighbours become
90
112
  // unreachable at ANY search ef; the cap bounds per-insert cost. Measurements and policy in
@@ -103,6 +125,70 @@ function autoScaleEfConstruction(nodeCount) {
103
125
  // ef moves with the square root of the count and is capped, so a slightly stale size is immaterial;
104
126
  // this only has to be short enough that a table growing from empty picks up a larger ef promptly.
105
127
  const NODE_COUNT_TTL = 10_000;
128
+ // Native traversal-plane geometry (DESIGN.md § Native HNSW plane). A file-primary index builds no
129
+ // JS graph, so the plane header carries its only layer-0 maximum. maxNodes is a fixed sparse
130
+ // reservation — pages materialize on write — and ids at or past it are rejected by the crate,
131
+ // which disables the plane.
132
+ const PLANE_LAYER0_CAP = 64;
133
+ const PLANE_LAYER0_CAP_MAX = 1024;
134
+ // The crate's fixed connection count, which nativePlane pins the index to: a layer-0 cap below it
135
+ // could not hold a node's own forward edges.
136
+ const PLANE_M = 16;
137
+ const PLANE_MAX_NODES = 1 << 24;
138
+ // Default inline primary-key bytes per plane slot (msgpack-encoded); 40 fits UUIDs inside the slot
139
+ // padding. `nativePlaneKeyCap` raises it for tables whose keys are longer.
140
+ const PLANE_KEY_CAP = 40;
141
+ const PLANE_KEY_CAP_MAX = 65535;
142
+ // An existing plane file that cannot be opened is normally another worker mid-create (retry);
143
+ // past this age it is a crashed create and is deleted so the audit-backed runtime can rebuild it.
144
+ const PLANE_STALE_CREATE_MS = 60_000;
145
+ // Retry cadence while another worker holds the create; its header lands shortly after exclusive open.
146
+ const PLANE_ATTACH_RETRY_MS = 250;
147
+ // Marks an error thrown by an app-supplied filter during a plane search: the caller re-raises
148
+ // it as an ordinary query failure instead of disabling the (healthy) plane.
149
+ const NOT_A_PLANE_FAILURE = Symbol('notAPlaneFailure');
150
+ function numericOption(name, value) {
151
+ if (value === undefined || value === null)
152
+ return undefined;
153
+ if ((typeof value !== 'number' && typeof value !== 'string') || (typeof value === 'string' && value.trim() === ''))
154
+ throw new hdbError_ts_1.ClientError(`${name} must be a finite number`);
155
+ const numericValue = Number(value);
156
+ if (!Number.isFinite(numericValue))
157
+ throw new hdbError_ts_1.ClientError(`${name} must be a finite number`);
158
+ return numericValue;
159
+ }
160
+ function normalizeOptimizeRoutingDeclaration(value) {
161
+ if (value === true || value === 'true')
162
+ return 1;
163
+ if (value === false || value === 'false')
164
+ return 0;
165
+ return value;
166
+ }
167
+ function nativePlaneMaxNodes(options) {
168
+ const maxNodes = numericOption('nativePlaneMaxNodes', options?.nativePlaneMaxNodes) ?? PLANE_MAX_NODES;
169
+ if (!Number.isSafeInteger(maxNodes) || maxNodes < 1 || maxNodes >= hnswPlaneBinding_ts_1.PLANE_NO_ID) {
170
+ throw new hdbError_ts_1.ClientError('nativePlaneMaxNodes must be a positive integer below 2^32-1');
171
+ }
172
+ return maxNodes;
173
+ }
174
+ function nativePlaneLayer0Cap(options) {
175
+ const layer0Cap = numericOption('nativePlaneLayer0Cap', options?.nativePlaneLayer0Cap) ?? PLANE_LAYER0_CAP;
176
+ if (!Number.isSafeInteger(layer0Cap) || layer0Cap < PLANE_M || layer0Cap > PLANE_LAYER0_CAP_MAX) {
177
+ throw new hdbError_ts_1.ClientError(`nativePlaneLayer0Cap must be an integer between ${PLANE_M} and ${PLANE_LAYER0_CAP_MAX}`);
178
+ }
179
+ return layer0Cap;
180
+ }
181
+ function nativePlaneKeyCap(options) {
182
+ const keyCap = numericOption('nativePlaneKeyCap', options?.nativePlaneKeyCap) ?? PLANE_KEY_CAP;
183
+ if (!Number.isSafeInteger(keyCap) || keyCap < 8 || keyCap > PLANE_KEY_CAP_MAX) {
184
+ throw new hdbError_ts_1.ClientError('nativePlaneKeyCap must be an integer between 8 and 65535');
185
+ }
186
+ return keyCap;
187
+ }
188
+ function nativePlaneDefaultDisabled() {
189
+ const value = process.env.HNSW_NO_NATIVE_DEFAULT;
190
+ return value != null && value !== '' && value !== '0' && value.toLowerCase() !== 'false';
191
+ }
106
192
  class MinHeap {
107
193
  data = [];
108
194
  get size() {
@@ -163,6 +249,10 @@ function bisectInsert(arr, distance) {
163
249
  */
164
250
  const ENTRY_POINT = Symbol.for('entryPoint');
165
251
  const KEY_PREFIX = Symbol.for('key');
252
+ /** A Map key for a primary key: composite keys by their encoding, since arrays compare by reference. */
253
+ function pendingMappingKey(primaryKey) {
254
+ return Array.isArray(primaryKey) ? `\0${(0, msgpackr_1.pack)(primaryKey).toString('latin1')}` : primaryKey;
255
+ }
166
256
  const MAX_LEVEL = 10; // should give good high-level skip list performance up to trillions of nodes
167
257
  // Visit budget for the post-delete connectivity probe (repairSeveredNeighbors, #1712). Severed
168
258
  // islands are small (bounded by the deleted node's neighborhood), so a probe that visits this many
@@ -180,11 +270,136 @@ const PROBE_VISIT_LIMIT = 256;
180
270
  */
181
271
  class HierarchicalNavigableSmallWorld {
182
272
  static useObjectStore = true;
273
+ static numericOptions = new Set([
274
+ 'M',
275
+ 'efConstruction',
276
+ 'efConstructionSearch',
277
+ 'mL',
278
+ 'optimizeRouting',
279
+ 'filterExpansion',
280
+ 'nativePlaneMaxNodes',
281
+ 'nativePlaneKeyCap',
282
+ 'nativePlaneLayer0Cap',
283
+ 'maxLagMilliseconds',
284
+ ]);
285
+ static truthyStructuralOptions = new Set(['nativePlane']);
286
+ static normalizeOptionValue(name, value) {
287
+ if (name !== 'optimizeRouting')
288
+ return value;
289
+ if (value === true)
290
+ return 1;
291
+ if (value === false)
292
+ return 0;
293
+ if (typeof value === 'string' &&
294
+ (value === 'true' || value === 'false' || (value.trim() !== '' && Number(value) === 0)))
295
+ // Legacy strings are truthy at runtime; keep them structurally distinct from false/0 so #1357 rebuilds.
296
+ return `legacy:${value}`;
297
+ return value;
298
+ }
299
+ static normalizeDeclarationOptions(options, persistedOptions) {
300
+ for (const name of HierarchicalNavigableSmallWorld.numericOptions) {
301
+ if (options[name] === undefined)
302
+ continue;
303
+ if (persistedOptions && Object.hasOwn(persistedOptions, name) && Object.is(options[name], persistedOptions[name]))
304
+ continue;
305
+ if (name === 'optimizeRouting' &&
306
+ typeof persistedOptions?.[name] === 'string' &&
307
+ options[name] !== false &&
308
+ options[name] !== 'false') {
309
+ try {
310
+ const declared = numericOption(name, normalizeOptimizeRoutingDeclaration(options[name]));
311
+ const persisted = numericOption(name, normalizeOptimizeRoutingDeclaration(persistedOptions[name]));
312
+ if (Object.is(declared, persisted)) {
313
+ options[name] = persistedOptions[name];
314
+ continue;
315
+ }
316
+ }
317
+ catch { }
318
+ }
319
+ if (options[name] === null) {
320
+ delete options[name];
321
+ continue;
322
+ }
323
+ const value = name === 'optimizeRouting' ? normalizeOptimizeRoutingDeclaration(options[name]) : options[name];
324
+ options[name] = numericOption(name, value);
325
+ }
326
+ if (Object.hasOwn(options, 'nativePlaneMaxNodes'))
327
+ nativePlaneMaxNodes(options);
328
+ if (Object.hasOwn(options, 'nativePlaneKeyCap'))
329
+ nativePlaneKeyCap(options);
330
+ if (Object.hasOwn(options, 'nativePlaneLayer0Cap'))
331
+ nativePlaneLayer0Cap(options);
332
+ }
333
+ static normalizeNativePlaneDeclaration(value) {
334
+ if (value === undefined || value === null)
335
+ return undefined;
336
+ if (value === true || value === 'true')
337
+ return true;
338
+ if (value === false || value === 'false')
339
+ return false;
340
+ throw new hdbError_ts_1.ClientError('nativePlane must be true or false');
341
+ }
342
+ static validateNativePlaneOptions(rootStore, options) {
343
+ if (!(rootStore instanceof rocksdb_js_1.RocksDatabase)) {
344
+ throw new hdbError_ts_1.ClientError('nativePlane requires the RocksDB storage engine; set nativePlane: false to use the JS index');
345
+ }
346
+ const nativeM = numericOption('M', options?.M);
347
+ const nativeEfConstruction = numericOption('efConstruction', options?.efConstruction);
348
+ const nativeML = numericOption('mL', options?.mL);
349
+ const nativeOptimizeRouting = numericOption('optimizeRouting', normalizeOptimizeRoutingDeclaration(options?.optimizeRouting));
350
+ if (options?.M === null ||
351
+ options?.efConstruction === null ||
352
+ options?.mL === null ||
353
+ options?.optimizeRouting === null ||
354
+ (nativeM !== undefined && nativeM !== PLANE_M) ||
355
+ (nativeEfConstruction !== undefined && nativeEfConstruction !== 200) ||
356
+ (nativeML !== undefined && nativeML !== 1 / Math.log(PLANE_M)) ||
357
+ (nativeOptimizeRouting !== undefined && nativeOptimizeRouting !== 0.5)) {
358
+ throw new hdbError_ts_1.ClientError('nativePlane requires M=16, efConstruction=200, mL=1/ln(16), and optimizeRouting=0.5; set nativePlane: false to use the JS index');
359
+ }
360
+ const maxNodes = nativePlaneMaxNodes(options);
361
+ const keyCap = nativePlaneKeyCap(options);
362
+ const layer0Cap = nativePlaneLayer0Cap(options);
363
+ if (options?.quantization === 'none' || options?.distance === 'euclidean' || options?.distance === 'dotProduct') {
364
+ throw new hdbError_ts_1.ClientError('nativePlane requires an int8-quantized cosine HNSW index; set nativePlane: false to use the JS index');
365
+ }
366
+ return { nativeM, nativeEfConstruction, nativeML, nativeOptimizeRouting, maxNodes, keyCap, layer0Cap };
367
+ }
368
+ static canRunNativePlane(rootStore, options, warnForMissingBinding = true) {
369
+ nativePlaneMaxNodes(options);
370
+ nativePlaneKeyCap(options);
371
+ nativePlaneLayer0Cap(options);
372
+ if (!(rootStore instanceof rocksdb_js_1.RocksDatabase) || (0, hnswPlaneBinding_ts_1.getPlaneBinding)(warnForMissingBinding) == null)
373
+ return false;
374
+ if (options?.M === null ||
375
+ options?.efConstruction === null ||
376
+ options?.mL === null ||
377
+ options?.optimizeRouting === null)
378
+ return false;
379
+ const configuredM = numericOption('M', options?.M);
380
+ const configuredEfConstruction = numericOption('efConstruction', options?.efConstruction);
381
+ const configuredML = numericOption('mL', options?.mL);
382
+ const configuredOptimizeRouting = numericOption('optimizeRouting', normalizeOptimizeRoutingDeclaration(options?.optimizeRouting));
383
+ const baseEligible = options?.quantization !== 'none' &&
384
+ options?.distance !== 'euclidean' &&
385
+ options?.distance !== 'dotProduct' &&
386
+ (configuredM === undefined || configuredM === PLANE_M) &&
387
+ (configuredEfConstruction === undefined || configuredEfConstruction === 200) &&
388
+ (configuredML === undefined || configuredML === 1 / Math.log(PLANE_M)) &&
389
+ (configuredOptimizeRouting === undefined || configuredOptimizeRouting === 0.5);
390
+ if (!baseEligible)
391
+ return false;
392
+ return true;
393
+ }
394
+ static canDefaultToNativePlane(rootStore, options) {
395
+ return (!nativePlaneDefaultDisabled() && HierarchicalNavigableSmallWorld.canRunNativePlane(rootStore, options, false));
396
+ }
183
397
  // Index options that only affect search, not the stored graph — changing them must not trigger a
184
398
  // reindex (databases.ts persists the new value but skips rebuilding). efConstructionSearch is the
185
399
  // search-time candidate-list size; the build uses efConstruction/M/distance, which are structural.
186
- // filterExpansion is the visit-budget multiplier for predicate-aware (filtered) traversal.
187
- static searchOnlyOptions = ['efConstructionSearch', 'filterExpansion'];
400
+ // filterExpansion is the visit-budget multiplier for predicate-aware (filtered) traversal;
401
+ // maxLagMilliseconds controls delivery admission rather than the graph itself.
402
+ static searchOnlyOptions = ['efConstructionSearch', 'filterExpansion', 'maxLagMilliseconds'];
188
403
  // Signals to search.ts that this index accepts a per-record predicate in search() and applies it
189
404
  // during traversal (predicate-aware / ACORN-style filtering), so companion conditions and RBAC can
190
405
  // be pushed down instead of post-filtering an under-filled candidate set (#1241).
@@ -223,6 +438,23 @@ class HierarchicalNavigableSmallWorld {
223
438
  convertedNodes = new WeakMap();
224
439
  nodeCount = 0;
225
440
  nodeCountAt = 0;
441
+ // Native file-primary index. The RocksDB index store holds identity mappings and replay cursors;
442
+ // graph nodes and adjacency exist only in this file.
443
+ // undefined = not yet attached (may retry), null = unavailable or disabled for this process.
444
+ plane;
445
+ planeEligible = false;
446
+ planeRetryAt = 0;
447
+ planeDisabledLogged = false;
448
+ filePrimary = false;
449
+ nativePlaneMaxNodes = PLANE_MAX_NODES;
450
+ nativePlaneKeyCap = PLANE_KEY_CAP;
451
+ nativePlaneLayer0Cap = PLANE_LAYER0_CAP;
452
+ // Installed by attachDerivedIndexes on every worker: shared readiness of the index and the way
453
+ // to ask its owner for a rebuild. Only the owning worker's runtime ever destroys native state.
454
+ derivedHost;
455
+ // keyed by pendingMappingKey(pk): a composite key's arrays never compare equal by reference
456
+ pendingDerivedMappings = new Map();
457
+ postCommit;
226
458
  constructor(indexStore, options) {
227
459
  this.indexStore = indexStore;
228
460
  if (indexStore) {
@@ -230,10 +462,16 @@ class HierarchicalNavigableSmallWorld {
230
462
  // (we would actually like to use float16 if it were available)
231
463
  this.indexStore.encoder.useFloat32 = msgpackr_1.FLOAT32_OPTIONS.ALWAYS;
232
464
  }
465
+ const configuredM = options?.M;
466
+ const configuredEfConstruction = options?.efConstruction;
467
+ const configuredEfConstructionSearch = options?.efConstructionSearch;
468
+ const configuredML = options?.mL;
469
+ const configuredOptimizeRouting = options?.optimizeRouting;
470
+ const configuredFilterExpansion = options?.filterExpansion;
233
471
  this.int8 = options?.quantization !== 'none';
234
472
  // Respect an explicitly-configured ef (efConstruction seeds the search ef too); otherwise auto-scale both.
235
- this.efSearchConfigured = options?.efConstructionSearch !== undefined || options?.efConstruction !== undefined;
236
- this.efConstructionConfigured = options?.efConstruction !== undefined;
473
+ this.efSearchConfigured = configuredEfConstructionSearch !== undefined || configuredEfConstruction !== undefined;
474
+ this.efConstructionConfigured = configuredEfConstruction !== undefined;
237
475
  this.distance =
238
476
  options?.distance === 'euclidean'
239
477
  ? vector_ts_1.euclideanDistance
@@ -242,23 +480,502 @@ class HierarchicalNavigableSmallWorld {
242
480
  : vector_ts_1.cosineDistance;
243
481
  if (options) {
244
482
  // allow all the HNSW parameters to be configured/tuned
245
- if (options.M !== undefined) {
246
- this.M = options.M;
483
+ if (configuredM !== undefined) {
484
+ this.M = configuredM;
247
485
  this.mL = 1 / Math.log(this.M); // recalculate
248
486
  }
249
- if (options.efConstruction !== undefined)
250
- this.efConstruction = this.efConstructionSearch = options.efConstruction;
251
- if (options.efConstructionSearch !== undefined)
252
- this.efConstructionSearch = options.efConstructionSearch;
253
- if (options.mL !== undefined)
254
- this.mL = options.mL;
255
- if (options.optimizeRouting !== undefined)
256
- this.optimizeRouting = options.optimizeRouting;
257
- if (options.filterExpansion !== undefined)
258
- this.filterExpansion = options.filterExpansion;
487
+ if (configuredEfConstruction !== undefined)
488
+ this.efConstruction = this.efConstructionSearch = configuredEfConstruction;
489
+ if (configuredEfConstructionSearch !== undefined)
490
+ this.efConstructionSearch = configuredEfConstructionSearch;
491
+ if (configuredML !== undefined)
492
+ this.mL = configuredML;
493
+ if (configuredOptimizeRouting !== undefined)
494
+ this.optimizeRouting = configuredOptimizeRouting;
495
+ if (configuredFilterExpansion !== undefined)
496
+ this.filterExpansion = configuredFilterExpansion;
497
+ }
498
+ if (options?.nativePlane) {
499
+ const { nativeM, nativeEfConstruction, nativeML, nativeOptimizeRouting, maxNodes, keyCap, layer0Cap } = HierarchicalNavigableSmallWorld.validateNativePlaneOptions(indexStore?.rootStore, options);
500
+ if (nativeM !== undefined)
501
+ this.M = nativeM;
502
+ if (nativeML !== undefined)
503
+ this.mL = nativeML;
504
+ if (nativeOptimizeRouting !== undefined)
505
+ this.optimizeRouting = nativeOptimizeRouting;
506
+ this.efConstruction = nativeEfConstruction ?? 200;
507
+ this.nativePlaneMaxNodes = maxNodes;
508
+ this.nativePlaneKeyCap = keyCap;
509
+ this.nativePlaneLayer0Cap = layer0Cap;
510
+ // The plane stores int8 bins and computes asymmetric cosine only, so the flag is a
511
+ // no-op for float (quantization: "none") and non-cosine indexes.
512
+ this.planeEligible = this.int8 && this.distance === vector_ts_1.cosineDistance;
513
+ if (!this.planeEligible) {
514
+ throw new hdbError_ts_1.ClientError('nativePlane requires an int8-quantized cosine HNSW index; set nativePlane: false to use the JS index');
515
+ }
516
+ this.filePrimary = true;
517
+ this.postCommit = true;
518
+ }
519
+ }
520
+ /** Remove derived native state after the option is disabled. */
521
+ cleanupDisabledPlane() {
522
+ if (this.planeEligible)
523
+ return;
524
+ const filePath = this.planeFilePath();
525
+ if (!filePath)
526
+ return;
527
+ try {
528
+ if ((0, node_fs_1.existsSync)(filePath))
529
+ this.invalidatePlaneFile(filePath, this.plane);
530
+ (0, node_fs_1.unlinkSync)(filePath);
531
+ logger.info?.('deleted the HNSW plane file of an index no longer using nativePlane');
532
+ }
533
+ catch (error) {
534
+ if (error?.code !== 'ENOENT') {
535
+ // A later re-enable must not adopt a file that missed mutations while disabled.
536
+ logger.warn?.('could not delete the HNSW plane file; marking it stale', error);
537
+ this.invalidatePlaneFile(filePath);
538
+ }
539
+ }
540
+ }
541
+ /** Absolute path of this index's plane file, or undefined when the store exposes no path. */
542
+ planeFilePath() {
543
+ const storePath = this.indexStore?.path;
544
+ const storeName = this.indexStore?.name;
545
+ if (typeof storePath !== 'string' || typeof storeName !== 'string')
546
+ return undefined;
547
+ return (0, hnswPlaneBinding_ts_1.planeFilePathFor)(storePath, storeName);
548
+ }
549
+ /**
550
+ * Open or lazily create the native file. An exclusive create resolves multi-worker races; the
551
+ * derived-index runtime owns population, replay, and publication.
552
+ */
553
+ getPlane(dims, dimsFromVector = false) {
554
+ if (this.plane !== undefined)
555
+ return this.plane;
556
+ if (!this.planeEligible)
557
+ return (this.plane = null);
558
+ const now = Date.now();
559
+ if (now < this.planeRetryAt)
560
+ return null;
561
+ const Plane = (0, hnswPlaneBinding_ts_1.getPlaneBinding)();
562
+ if (!Plane)
563
+ return (this.plane = null); // the loader warned once already
564
+ if (!('keyCap' in Plane.prototype)) {
565
+ if (!this.planeDisabledLogged) {
566
+ this.planeDisabledLogged = true;
567
+ logger.error?.('the installed @harperfast/hnsw predates 0.3.0; the native HNSW index needs 0.3.0 or later');
568
+ }
569
+ return (this.plane = null);
570
+ }
571
+ const filePath = this.planeFilePath();
572
+ if (!filePath) {
573
+ this.disablePlane(new Error('the index store exposes no path to place the plane file next to'));
574
+ return null;
575
+ }
576
+ try {
577
+ // a tombstone marks a plane a previous unlink could not remove (Windows EBUSY while
578
+ // mapped): the file is stale and must never be opened over a fresh graph
579
+ const stalePath = (0, hnswPlaneBinding_ts_1.planeStalePathFor)(filePath);
580
+ if ((0, node_fs_1.existsSync)(stalePath)) {
581
+ try {
582
+ // force: either artifact may already be gone (the documented rollback deletes the
583
+ // plane file by hand), and an ENOENT here disables the plane on every later attach
584
+ (0, node_fs_1.rmSync)(filePath, { force: true });
585
+ (0, node_fs_1.rmSync)(stalePath, { force: true });
586
+ }
587
+ catch {
588
+ this.planeRetryAt = now + NODE_COUNT_TTL;
589
+ return null;
590
+ }
591
+ }
592
+ if ((0, node_fs_1.existsSync)(filePath)) {
593
+ try {
594
+ // Crash recovery is per-slot inside the crate. The clean flag is advisory;
595
+ // another worker may still be constructing this shared file.
596
+ const opened = Plane.open(filePath);
597
+ // A header that disagrees with the declaration is a different graph, not a
598
+ // repairable file, and only the owner may destroy it: invalidating through this
599
+ // handle would write a path-based tombstone that could name a replacement the
600
+ // owner had already created.
601
+ const geometryMismatch = opened.keyCap !== this.nativePlaneKeyCap
602
+ ? `keyCap ${opened.keyCap} differs from the configured ${this.nativePlaneKeyCap}`
603
+ : opened.layer0Cap !== this.nativePlaneLayer0Cap
604
+ ? `layer0Cap ${opened.layer0Cap} differs from the configured ${this.nativePlaneLayer0Cap}`
605
+ : undefined;
606
+ if (geometryMismatch) {
607
+ this.disablePlane(new Error(`plane ${geometryMismatch}`));
608
+ return null;
609
+ }
610
+ return (this.plane = opened);
611
+ }
612
+ catch (openError) {
613
+ if (now - (0, node_fs_1.statSync)(filePath).mtimeMs <= PLANE_STALE_CREATE_MS) {
614
+ // another worker is between its exclusive create and the header write
615
+ this.planeRetryAt = now + PLANE_ATTACH_RETRY_MS;
616
+ return null;
617
+ }
618
+ logger.warn?.('deleting an unopenable HNSW plane file left by an interrupted create', openError);
619
+ (0, node_fs_1.unlinkSync)(filePath);
620
+ if (this.filePrimary) {
621
+ // The surviving mappings still name node ids from the file just removed.
622
+ // Creating a fresh plane here would resolve them against an empty graph;
623
+ // only reconstruction, which clears the mappings first, is safe.
624
+ this.planeRetryAt = now + PLANE_ATTACH_RETRY_MS;
625
+ return null;
626
+ }
627
+ }
628
+ }
629
+ if (!dims)
630
+ return null; // open-only call and no file: nothing to attach yet
631
+ // A search target must not pin an empty index's dimensionality: creation is deferred to
632
+ // the first committed vector or to the rebuild scan. Return before the exclusive create
633
+ // rather than creating and unlinking — a concurrent insert that saw the empty file would
634
+ // read it as another worker's in-progress create and 503 for PLANE_STALE_CREATE_MS.
635
+ if (!dimsFromVector)
636
+ return null;
637
+ let fd;
638
+ try {
639
+ fd = (0, node_fs_1.openSync)(filePath, 'wx');
640
+ }
641
+ catch {
642
+ // another worker won the create race; its header lands within moments
643
+ this.planeRetryAt = now + PLANE_ATTACH_RETRY_MS;
644
+ return null;
645
+ }
646
+ (0, node_fs_1.closeSync)(fd);
647
+ try {
648
+ return (this.plane = Plane.create(filePath, dims, this.nativePlaneLayer0Cap, this.nativePlaneMaxNodes, this.nativePlaneKeyCap));
649
+ }
650
+ catch (createError) {
651
+ // Never leave a partial file that a later process could trust as current.
652
+ try {
653
+ (0, node_fs_1.unlinkSync)(filePath);
654
+ }
655
+ catch {
656
+ // the disable below already forces the JS path for this process
657
+ }
658
+ this.disablePlane(createError);
659
+ return null;
660
+ }
661
+ }
662
+ catch (error) {
663
+ this.planeRetryAt = now + NODE_COUNT_TTL;
664
+ logger.warn?.('could not attach the HNSW plane file; will retry', error);
665
+ return null;
666
+ }
667
+ }
668
+ /** True only while the owning runtime publishes the index as ready, on whichever worker owns it. */
669
+ planeSearchReady(plane) {
670
+ if (plane.invalidated()) {
671
+ this.plane = undefined;
672
+ return false;
673
+ }
674
+ return this.derivedReadiness() === 'ready';
675
+ }
676
+ derivedReadiness() {
677
+ return this.derivedHost?.readiness().state ?? 'unknown';
678
+ }
679
+ /**
680
+ * Detach this process from the plane after a failure and ask the index's owner for a rebuild.
681
+ * Only the owner destroys native state (`resetDerivedStorage` under its epoch), so a failure
682
+ * observed here after a peer has already replaced the file cannot take out the replacement.
683
+ */
684
+ disablePlane(error) {
685
+ this.plane = undefined;
686
+ this.planeRetryAt = Date.now() + PLANE_ATTACH_RETRY_MS;
687
+ if (!this.planeDisabledLogged) {
688
+ this.planeDisabledLogged = true;
689
+ logger.error?.('the HNSW native index failed; requesting a rebuild from its owner', error);
690
+ }
691
+ this.derivedHost?.requestRebuild();
692
+ }
693
+ /**
694
+ * Delete the derived plane state before an audit-backed reconstruction. Path invalidation
695
+ * makes peers stop using an old mapping even when unlink leaves their mmap inode alive.
696
+ */
697
+ resetDerivedStorage() {
698
+ this.pendingDerivedMappings.clear();
699
+ const attached = this.plane;
700
+ this.plane = undefined;
701
+ this.planeRetryAt = 0;
702
+ const filePath = this.planeFilePath();
703
+ if (!filePath)
704
+ return;
705
+ if (this.filePrimary && (0, node_fs_1.existsSync)(filePath))
706
+ this.invalidatePlaneFile(filePath, attached);
707
+ try {
708
+ (0, node_fs_1.unlinkSync)(filePath);
709
+ }
710
+ catch (error) {
711
+ if (error?.code !== 'ENOENT') {
712
+ // a stale file that cannot be deleted (e.g. Windows EBUSY while mapped) must not
713
+ // be reopened as if current — mark it so no process ever adopts it
714
+ this.plane = null;
715
+ logger.warn?.('could not delete the HNSW plane file; marking it stale', error);
716
+ this.invalidatePlaneFile(filePath, attached);
717
+ }
718
+ }
719
+ }
720
+ /** True while any primary-key mapping survives, which is the only proof this index holds nodes. */
721
+ hasNodeMappings() {
722
+ for (const { value } of this.indexStore.getRange({})) {
723
+ if (value && typeof value === 'object' && typeof value.id === 'number')
724
+ return true;
725
+ }
726
+ return false;
727
+ }
728
+ /** Make an undeletable plane unadoptable before this process releases it. */
729
+ invalidatePlaneFile(filePath, attached) {
730
+ try {
731
+ (0, hnswPlaneBinding_ts_1.invalidatePlaneFile)(filePath, attached);
732
+ }
733
+ catch (error) {
734
+ logger.warn?.('could not invalidate the stale HNSW plane file', error);
735
+ }
736
+ }
737
+ /**
738
+ * Native search over the plane: one NAPI crossing, traversal on the libuv pool, every hit
739
+ * carrying its primary key out of the slot. The predicate adapter runs on this thread's event
740
+ * loop (batched over a ThreadsafeFunction), so this promise must never be awaited by code the
741
+ * predicate itself blocks on; the normal request path awaits it safely.
742
+ */
743
+ searchPlane(plane, target, k, ef, filter, filterState) {
744
+ const query = Float32Array.from(target);
745
+ let resultPromise;
746
+ let predicateError;
747
+ let planeError;
748
+ if (filter && filterState) {
749
+ const predicate = (ids, keys, keyEnds) => {
750
+ const verdicts = new Uint8Array(ids.length);
751
+ if (predicateError !== undefined || planeError !== undefined)
752
+ return verdicts;
753
+ for (let i = 0; i < ids.length; i++) {
754
+ const start = i === 0 ? 0 : keyEnds[i - 1];
755
+ const end = keyEnds[i];
756
+ if (end === start)
757
+ continue;
758
+ let primaryKey;
759
+ try {
760
+ primaryKey = (0, msgpackr_1.unpack)(keys.subarray(start, end));
761
+ }
762
+ catch (error) {
763
+ planeError = error;
764
+ break;
765
+ }
766
+ try {
767
+ if (this.admit(filter, filterState, primaryKey))
768
+ verdicts[i] = 1;
769
+ }
770
+ catch (error) {
771
+ predicateError = error;
772
+ break;
773
+ }
774
+ }
775
+ return verdicts;
776
+ };
777
+ resultPromise = plane.searchWithPredicate(query, k, ef, predicate, undefined, filterState.maxVisits);
778
+ }
779
+ else {
780
+ resultPromise = plane.search(query, k, ef);
781
+ }
782
+ return resultPromise.then((hits) => {
783
+ if (planeError !== undefined)
784
+ throw planeError;
785
+ if (predicateError !== undefined) {
786
+ // the plane itself is healthy; mark the failure as the application's so the caller
787
+ // re-raises it rather than disabling the plane and retrying
788
+ try {
789
+ predicateError[NOT_A_PLANE_FAILURE] = true;
790
+ }
791
+ catch {
792
+ // a frozen/primitive throw still propagates, it just also disables the plane
793
+ }
794
+ throw predicateError;
795
+ }
796
+ const entries = [];
797
+ const { distances, keys, keyEnds } = hits;
798
+ // a key appears twice only after a crash (see applyDerivedValue); keep the nearer hit
799
+ const seen = new Set();
800
+ for (let i = 0; i < keyEnds.length; i++) {
801
+ const start = i === 0 ? 0 : keyEnds[i - 1];
802
+ const end = keyEnds[i];
803
+ if (end === start)
804
+ continue;
805
+ const encoded = keys.toString('latin1', start, end);
806
+ if (seen.has(encoded))
807
+ continue;
808
+ seen.add(encoded);
809
+ entries.push({ key: (0, msgpackr_1.unpack)(keys.subarray(start, end)), distance: distances[i] });
810
+ }
811
+ // nodesVisited stays 0 here: layer-0 visits happen inside the native traversal
812
+ // (filterEvaluations is still counted by the predicate adapter)
813
+ return withStats(entries, filterState);
814
+ });
815
+ }
816
+ attachDerivedHost(host) {
817
+ this.derivedHost = host;
818
+ }
819
+ queryCoverage(maxLagMilliseconds) {
820
+ const coverage = this.derivedHost?.coverage(maxLagMilliseconds);
821
+ if (!coverage || coverage.state === 'unknown') {
822
+ const age = coverage?.lagUpperBoundMilliseconds;
823
+ throw new hdbError_ts_1.DerivedIndexLagError(`Cannot certify native HNSW index coverage within ${maxLagMilliseconds} ms` +
824
+ (age === undefined ? '; freshness is unknown' : `; last certified ${Math.ceil(age)} ms ago`) +
825
+ '; retry this query');
826
+ }
827
+ return coverage;
828
+ }
829
+ /**
830
+ * The commit path only validates: a malformed vector is the client's 400 here rather than an
831
+ * unindexable record later. The shared derived-index runtime reads the committed log; nothing
832
+ * is staged on the transaction.
833
+ */
834
+ prepareCommitted(primaryKey, vector, existingVector) {
835
+ // O(dims) on every write to the table, so skip it when the projection did not change — an
836
+ // unchanged vector was validated when it was first written.
837
+ if (derivedValuesEqual(vector, existingVector))
838
+ return;
839
+ this.validateVector(primaryKey, vector);
840
+ }
841
+ /** The runtime's projection guard: what fails here is delivered as unindexable, not applied. */
842
+ assertDerivedValue(vector, label) {
843
+ this.assertPlaneVector(vector, label);
844
+ }
845
+ validateVector(primaryKey, vector) {
846
+ if (!vector)
847
+ return;
848
+ this.assertPlaneVector(vector, `Vector for attribute "${String(primaryKey)}"`);
849
+ }
850
+ /**
851
+ * Everything reaching the plane — a committed record's projection or a query target — has to
852
+ * convert to the f32 it stores, and to a representable magnitude. What fails here would instead
853
+ * throw out of `Float32Array.from` or out of the crate, as an error neither the search path nor
854
+ * reconstruction can attribute to the record: a query would unlink a healthy file, and a record
855
+ * would abort every rebuild attempt at the same entry.
856
+ */
857
+ assertPlaneVector(vector, label) {
858
+ // A positive integer length, not merely a numeric one: a negative or fractional length skips
859
+ // the component loop and the emptiness check, and reaches Plane.create with it.
860
+ const length = vector?.length;
861
+ if (!Number.isInteger(length) || length < 1) {
862
+ throw new hdbError_ts_1.ClientError(`${label} must be an array of at least one number.`);
863
+ }
864
+ let sumOfSquares = 0;
865
+ for (let i = 0; i < vector.length; i++) {
866
+ const component = vector[i];
867
+ // The type check precedes Math.fround, which throws a TypeError on the BigInt a msgpackr
868
+ // or cbor-x decode produces for a large int64.
869
+ if (typeof component !== 'number' || !Number.isFinite(Math.fround(component))) {
870
+ throw new hdbError_ts_1.ClientError(`${label} has a component at index ${i} that is not a finite 32-bit float: ${String(component)}.`);
871
+ }
872
+ const asFloat32 = Math.fround(component);
873
+ sumOfSquares += asFloat32 * asFloat32;
874
+ }
875
+ // Components can each be f32-finite while their squares are not, which stores invMag 0 and
876
+ // makes every distance involving that node NaN.
877
+ if (!Number.isFinite(Math.fround(sumOfSquares))) {
878
+ throw new hdbError_ts_1.ClientError(`${label} has a magnitude too large to represent in 32-bit floats.`);
879
+ }
880
+ // The plane's dimensionality is fixed at create time by the first committed vector.
881
+ const dims = this.plane?.dims;
882
+ if (dims !== undefined && vector.length !== dims) {
883
+ throw new hdbError_ts_1.ClientError(`${label} has ${vector.length} components, but this index stores ${dims}.`);
884
+ }
885
+ }
886
+ applyDerivedValue(primaryKey, vector, version) {
887
+ this.validateVector(primaryKey, vector);
888
+ const safeKey = typeof primaryKey === 'number' ? [KEY_PREFIX, primaryKey] : primaryKey;
889
+ const pendingKey = pendingMappingKey(primaryKey);
890
+ const pendingMapping = this.pendingDerivedMappings.get(pendingKey);
891
+ const storedMapping = pendingMapping ?? this.indexStore.getSync(safeKey);
892
+ const oldNodeId = typeof storedMapping === 'number' ? storedMapping : storedMapping?.id;
893
+ // Neither a plane removal nor an insert is durable before the barrier, so a pending
894
+ // mapping keeps naming the last published node (previousId) for replay to remove.
895
+ const displacedNodeId = storedMapping?.previousId;
896
+ const durableNodeId = storedMapping?.pending ? displacedNodeId : oldNodeId;
897
+ if (storedMapping?.version != null && version != null && storedMapping.version > version)
898
+ return;
899
+ const nativeVector = vector ? Float32Array.from(vector) : undefined;
900
+ const signature = nativeVector
901
+ ? (0, node_crypto_1.createHash)('sha256')
902
+ .update(Buffer.from(nativeVector.buffer, nativeVector.byteOffset, nativeVector.byteLength))
903
+ .digest('base64url')
904
+ : undefined;
905
+ if (oldNodeId != null &&
906
+ signature &&
907
+ storedMapping.signature === signature &&
908
+ !pendingMapping &&
909
+ !storedMapping.pending) {
910
+ this.indexStore.putSync(safeKey, { id: oldNodeId, signature, version });
911
+ return;
912
+ }
913
+ let plane = vector ? this.getPlane(vector.length, true) : this.getPlane();
914
+ if (plane?.invalidated()) {
915
+ this.plane = undefined;
916
+ plane = vector ? this.getPlane(vector.length, true) : this.getPlane();
917
+ }
918
+ // The pre-commit check above ran before this worker had a plane to compare against, so it
919
+ // cannot have caught a mismatch. Reject before the removal below, or the record loses its
920
+ // old node on the way to failing.
921
+ if (vector && plane && vector.length !== plane.dims) {
922
+ throw new hdbError_ts_1.ClientError(`Vector for attribute "${String(primaryKey)}" has ${vector.length} components, but this index stores ${plane.dims}.`);
923
+ }
924
+ // in process a pending mapping's removals already happened and their ids may be reused
925
+ const removeDisplaced = displacedNodeId != null && !pendingMapping && displacedNodeId !== oldNodeId;
926
+ const removeOld = oldNodeId != null && !(pendingMapping && storedMapping.cleared);
927
+ if (removeDisplaced)
928
+ plane?.remove(displacedNodeId);
929
+ if (removeOld)
930
+ plane?.remove(oldNodeId);
931
+ if (!vector) {
932
+ const cleared = { id: oldNodeId, previousId: durableNodeId, version, pending: true, cleared: true };
933
+ if (oldNodeId != null || durableNodeId != null)
934
+ this.indexStore.putSync(safeKey, cleared);
935
+ else
936
+ this.indexStore.removeSync(safeKey);
937
+ this.pendingDerivedMappings.set(pendingKey, { ...cleared, primaryKey });
938
+ return;
939
+ }
940
+ if (!plane)
941
+ throw new hdbError_ts_1.ServerError('The native HNSW module is unavailable for a file-primary index', 503);
942
+ const nodeId = plane.insert(nativeVector, (0, msgpackr_1.pack)(primaryKey));
943
+ const mapping = { id: nodeId, previousId: durableNodeId, signature, version, pending: true };
944
+ this.indexStore.putSync(safeKey, mapping);
945
+ this.pendingDerivedMappings.set(pendingKey, { ...mapping, primaryKey });
946
+ }
947
+ async flushDerived(watermark) {
948
+ const plane = this.getPlane();
949
+ if (!plane) {
950
+ if (!(0, hnswPlaneBinding_ts_1.getPlaneBinding)())
951
+ throw new hdbError_ts_1.ServerError('The native HNSW module is unavailable', 503);
952
+ return this.publishDerivedMappings();
953
+ }
954
+ await plane.flushAsync(watermark);
955
+ this.publishDerivedMappings();
956
+ }
957
+ publishDerivedMappings() {
958
+ for (const { primaryKey, ...mapping } of this.pendingDerivedMappings.values()) {
959
+ const safeKey = typeof primaryKey === 'number' ? [KEY_PREFIX, primaryKey] : primaryKey;
960
+ if (mapping.cleared || mapping.id === undefined) {
961
+ this.indexStore.removeSync(safeKey);
962
+ }
963
+ else {
964
+ const published = { id: mapping.id, signature: mapping.signature, version: mapping.version };
965
+ this.indexStore.putSync(safeKey, published);
966
+ }
259
967
  }
968
+ this.pendingDerivedMappings.clear();
260
969
  }
261
970
  index(primaryKey, vector, existingVector, options = {}) {
971
+ if (this.filePrimary) {
972
+ if (options.transaction)
973
+ return this.prepareCommitted(primaryKey, vector, existingVector);
974
+ // runIndexing invokes custom indexes without a transaction. The shared runtime owns the
975
+ // primary-record rebuild and the log replay; populating here would duplicate native
976
+ // construction and race the owner's generation reset.
977
+ return;
978
+ }
262
979
  // Reject non-finite components before touching the graph. NaN in particular poisons
263
980
  // bisectInsert (arr[mid].distance <= NaN is always false → returns 0, pinning the
264
981
  // candidate to rank 1 of every future search). Infinity causes analogous ordering
@@ -504,14 +1221,15 @@ class HierarchicalNavigableSmallWorld {
504
1221
  }
505
1222
  }
506
1223
  // Store the new element
507
- this.indexStore.put(nodeId, {
1224
+ const storedNode = {
508
1225
  vector: storedVector,
509
1226
  scale: storedScale,
510
1227
  invMag,
511
1228
  level,
512
1229
  primaryKey,
513
1230
  ...connections,
514
- }, options);
1231
+ };
1232
+ this.indexStore.put(nodeId, storedNode, options);
515
1233
  }
516
1234
  else {
517
1235
  // removal of this node, but first make sure we have a valid entry point
@@ -822,8 +1540,10 @@ class HierarchicalNavigableSmallWorld {
822
1540
  throw error;
823
1541
  }
824
1542
  }
825
- /** O(1) node count — the shared id counter, else a single reverse seek to the largest node id. */
1543
+ /** O(1) node count: the plane's id high-water, the shared id counter, or one reverse seek. */
826
1544
  resolveNodeCount(options) {
1545
+ if (this.filePrimary)
1546
+ return this.getPlane()?.idHighWater() ?? 0;
827
1547
  if (this.idIncrementer)
828
1548
  return Number(Atomics.load(this.idIncrementer, 0));
829
1549
  try {
@@ -1071,24 +1791,17 @@ class HierarchicalNavigableSmallWorld {
1071
1791
  * This the main entry from Harper's query functionality, where we actually search for an ordered list of nearest
1072
1792
  * neighbors, using the provided sort/order definition object and performing the multi-layer skip-list search.
1073
1793
  * This returns an iterable of the nearest neighbors to the provided target vector, with nearest ordered first.
1074
- * @param target
1075
- * @param value
1076
- * @param descending
1077
- * @param distance
1078
- * @param comparator
1079
- * @param context
1794
+ *
1795
+ * This is also the contract an index implemented outside this repo has to satisfy, so everything
1796
+ * optional is named: a future capability (a paging cursor, a deadline, a recall target) is a new
1797
+ * field on `options` rather than a positional argument that breaks every existing implementation.
1798
+ * @param searchCondition the vector query: `target`, `comparator`, and the optional `value`,
1799
+ * `descending`, `distance`, `ef` and `filterExpansion` tuning knobs
1800
+ * @param context the query context; its `transaction` is the nested RocksDB transaction reads use
1801
+ * @param options optional, named: `filter` (predicate-aware traversal) and `minResults`
1080
1802
  */
1081
- search({ target, value, descending, distance, comparator, ef, filterExpansion, }, context,
1082
- // Predicate-aware traversal (#1241). When provided, only nodes for which `filter(primaryKey)`
1083
- // returns true are admitted to the result list at layer 0; routing is unaffected. Composed by
1084
- // search.ts from companion AND conditions and caller-supplied vector/row filters. Must be
1085
- // synchronous and side-effect free. JS-API only (never from a REST query string).
1086
- filter,
1087
- // offset + limit for a bounded query. A layer-0 search returns at most `ef` candidates, so a
1088
- // query asking for more rows than that used to come back short with no error — capped at 512
1089
- // (AUTO_EF_MAX) however large the limit was. Raising ef to cover the request keeps `limit`
1090
- // meaningful; the caller pays for what it asked for.
1091
- minResults) {
1803
+ search({ target, value, descending, distance, comparator, ef, filterExpansion, maxIndexLagMilliseconds = hnswDerivedIndex_ts_1.DEFAULT_MAX_INDEX_LAG_MILLISECONDS, waitForIndexMilliseconds = 0, }, context, { filter, minResults, } = {}) {
1804
+ const waiting = this.filePrimary && waitForIndexMilliseconds > 0;
1092
1805
  let limit; // only set for threshold comparators; 0 is a valid threshold (e.g. dotProduct)
1093
1806
  let limitInclusive = false; // true for `le`, false for `lt`
1094
1807
  switch (comparator) {
@@ -1124,7 +1837,18 @@ class HierarchicalNavigableSmallWorld {
1124
1837
  throw new hdbError_ts_1.ClientError('A target vector must be provided for an HNSW query');
1125
1838
  if (!Array.isArray(target))
1126
1839
  throw new hdbError_ts_1.ClientError('The target vector must be an array');
1127
- const options = context.transaction; // should have a nested RocksDB transaction
1840
+ if (this.filePrimary &&
1841
+ (typeof maxIndexLagMilliseconds !== 'number' ||
1842
+ !Number.isFinite(maxIndexLagMilliseconds) ||
1843
+ maxIndexLagMilliseconds < 0))
1844
+ throw new hdbError_ts_1.ClientError('maxIndexLagMilliseconds must be a finite nonnegative number');
1845
+ if (this.filePrimary &&
1846
+ (typeof waitForIndexMilliseconds !== 'number' ||
1847
+ !Number.isFinite(waitForIndexMilliseconds) ||
1848
+ waitForIndexMilliseconds < 0 ||
1849
+ waitForIndexMilliseconds > hnswDerivedIndex_ts_1.MAX_WAIT_FOR_INDEX_MILLISECONDS))
1850
+ throw new hdbError_ts_1.ClientError(`waitForIndexMilliseconds must be a finite number between 0 and ${hnswDerivedIndex_ts_1.MAX_WAIT_FOR_INDEX_MILLISECONDS}`);
1851
+ const txnOptions = context.transaction; // should have a nested RocksDB transaction
1128
1852
  // Resolve search ef: per-query ef wins; else use the schema-pinned value (from either ef option);
1129
1853
  // otherwise auto-scale with the graph size so recall holds as the table grows.
1130
1854
  let effectiveEf = this.efConstructionSearch;
@@ -1167,7 +1891,109 @@ class HierarchicalNavigableSmallWorld {
1167
1891
  filterEvaluations: 0,
1168
1892
  }
1169
1893
  : undefined;
1170
- let entryPoint = this.getEntryPoint(options);
1894
+ const rerankK = comparator === 'sort' && minResults !== undefined
1895
+ ? Math.min(effectiveEf, Math.max(RERANK_MIN, minResults * RERANK_FACTOR))
1896
+ : effectiveEf;
1897
+ if (this.filePrimary && distanceFunction !== this.distance) {
1898
+ throw new hdbError_ts_1.ClientError('A nativePlane index only supports its configured cosine distance');
1899
+ }
1900
+ const searchNative = (certified) => {
1901
+ if (this.filePrimary && this.derivedReadiness() !== 'ready') {
1902
+ throw new hdbError_ts_1.ServerError(`The native HNSW index is ${this.derivedReadiness() === 'unavailable' ? 'unavailable' : 'rebuilding'}`, 503);
1903
+ }
1904
+ // The plane traverses the index's own metric (cosine — the eligibility requirement), so a
1905
+ // query overriding `distance` has to take the JS path: rescoreResults only corrects the
1906
+ // reported distances of whatever candidates came back, not which candidates the beam kept.
1907
+ if (this.planeEligible && distanceFunction === this.distance) {
1908
+ const plane = this.getPlane(target.length, false);
1909
+ // A file-primary index has no JS path to fall through to, so a target the plane cannot
1910
+ // accept has to fail as the client error it is. Otherwise it throws out of
1911
+ // Float32Array.from or the traversal, is read as plane corruption, and unlinks a healthy
1912
+ // file — one malformed query costing a full reconstruction.
1913
+ if (this.filePrimary && plane)
1914
+ this.assertPlaneVector(target, 'Search target');
1915
+ // a non-file-primary query whose dimensionality differs from the graph's takes the JS
1916
+ // path, which tolerates the mismatch, rather than disabling the healthy plane
1917
+ if (plane && plane.dims === target.length && this.planeSearchReady(plane)) {
1918
+ const coverage = certified ?? (this.filePrimary ? this.queryCoverage(maxIndexLagMilliseconds) : undefined);
1919
+ try {
1920
+ const searched = this.searchPlane(plane, target, rerankK, effectiveEf, filter, filterState)
1921
+ .catch((error) => {
1922
+ if (error?.[NOT_A_PLANE_FAILURE])
1923
+ throw error;
1924
+ // There is no JS graph behind a file-primary index: it stays unavailable until
1925
+ // its audit-backed rebuild succeeds.
1926
+ this.disablePlane(error);
1927
+ throw new hdbError_ts_1.ServerError('The native HNSW index is rebuilding', 503);
1928
+ })
1929
+ .then((entries) => {
1930
+ if (coverage)
1931
+ Object.defineProperty(entries, 'indexCoverage', { value: coverage });
1932
+ return entries;
1933
+ });
1934
+ if (coverage)
1935
+ Object.defineProperty(searched, 'indexCoverage', { value: coverage });
1936
+ return searched;
1937
+ }
1938
+ catch (error) {
1939
+ // Handle a throw raised before the asynchronous native search returns its promise.
1940
+ this.disablePlane(error);
1941
+ throw new hdbError_ts_1.ServerError('The native HNSW index is rebuilding', 503);
1942
+ }
1943
+ }
1944
+ }
1945
+ if (this.filePrimary) {
1946
+ const state = this.derivedReadiness();
1947
+ if (state === 'unavailable')
1948
+ throw new hdbError_ts_1.ServerError('The native HNSW index is unavailable', 503);
1949
+ const planePath = this.planeFilePath();
1950
+ if (state !== 'ready' || (planePath && (0, node_fs_1.existsSync)(planePath))) {
1951
+ throw new hdbError_ts_1.ServerError('The native HNSW index is rebuilding', 503);
1952
+ }
1953
+ // The absence of a file is not proof of an empty index — it is also the state just after
1954
+ // any process removes an unopenable one. Only the surviving node mappings prove it.
1955
+ if (this.hasNodeMappings()) {
1956
+ // A table taking no further writes never drains, so a query is the only thing left
1957
+ // that can notice the graph is gone and ask for it back.
1958
+ logger.error?.(`${this.indexStore.name} lost its native file while its node mappings survive`);
1959
+ this.derivedHost?.requestRebuild();
1960
+ throw new hdbError_ts_1.ServerError('The native HNSW index is rebuilding', 503);
1961
+ }
1962
+ const entries = withStats([], filterState);
1963
+ Object.defineProperty(entries, 'indexCoverage', {
1964
+ value: certified ?? this.queryCoverage(maxIndexLagMilliseconds),
1965
+ });
1966
+ return entries;
1967
+ }
1968
+ };
1969
+ if (waiting) {
1970
+ context.signal?.throwIfAborted();
1971
+ const host = this.derivedHost;
1972
+ const state = host?.readiness().state;
1973
+ if (state !== 'ready') {
1974
+ throw new hdbError_ts_1.ServerError(`The native HNSW index is ${state === 'unavailable' ? 'unavailable' : 'rebuilding'}`, 503);
1975
+ }
1976
+ const searched = Promise.resolve(context.indexSearchStart).then(async () => {
1977
+ context.signal?.throwIfAborted();
1978
+ if (minResults === 0)
1979
+ return [];
1980
+ const started = (0, derivedIndexRuntime_ts_1.derivedIndexTime)(this.indexStore.rootStore);
1981
+ if (host.coverage(0)?.state !== 'current')
1982
+ await host.waitForCoverage(started, waitForIndexMilliseconds, context.signal);
1983
+ context.signal?.throwIfAborted();
1984
+ return searchNative({
1985
+ state: 'current',
1986
+ maxLagMilliseconds: maxIndexLagMilliseconds,
1987
+ lagUpperBoundMilliseconds: 0,
1988
+ });
1989
+ });
1990
+ searched.catch(() => { });
1991
+ return searched;
1992
+ }
1993
+ const native = searchNative();
1994
+ if (native)
1995
+ return native;
1996
+ let entryPoint = this.getEntryPoint(txnOptions);
1171
1997
  if (!entryPoint)
1172
1998
  return withStats([], filterState);
1173
1999
  let entryPointId = entryPoint.id;
@@ -1180,7 +2006,7 @@ class HierarchicalNavigableSmallWorld {
1180
2006
  // nodes) rather than to ef. See DESIGN.md.
1181
2007
  for (let l = entryPoint.level; l >= 0; l--) {
1182
2008
  // Search for closest neighbors at current level
1183
- results = this.searchLayer(target, entryPointId, entryPoint, l === 0 ? effectiveEf : ROUTING_EF, l, options, distanceFunction, l === 0 ? filter : undefined, l === 0 ? filterState : undefined);
2009
+ results = this.searchLayer(target, entryPointId, entryPoint, l === 0 ? effectiveEf : ROUTING_EF, l, txnOptions, distanceFunction, l === 0 ? filter : undefined, l === 0 ? filterState : undefined);
1184
2010
  if (results.length > 0) {
1185
2011
  const neighbor = results[0]; // closest neighbor becomes new entry point
1186
2012
  entryPoint = neighbor.node;
@@ -1189,6 +2015,8 @@ class HierarchicalNavigableSmallWorld {
1189
2015
  }
1190
2016
  if (limit !== undefined)
1191
2017
  results = results.filter((candidate) => limitInclusive ? candidate.distance <= limit : candidate.distance < limit);
2018
+ if (results.length > rerankK)
2019
+ results = results.slice(0, rerankK);
1192
2020
  return withStats(results.map((candidate) => ({
1193
2021
  // we return the result as an entry so we can provide distance as metadata
1194
2022
  key: candidate.node.primaryKey, // return value