@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,174 @@
1
+ import { closeSync, fsyncSync, openSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { loggerWithTag } from '../../utility/logging/logger.ts';
4
+
5
+ const logger = loggerWithTag('HNSW');
6
+
7
+ /**
8
+ * Parallel arrays, ascending by distance: hit i is (ids[i], distances[i]) with its host key at
9
+ * keys.subarray(keyEnds[i - 1] ?? 0, keyEnds[i]); an empty key means the node vanished mid-query.
10
+ */
11
+ export interface PlaneSearchHits {
12
+ ids: Uint32Array;
13
+ distances: Float32Array;
14
+ keys: Buffer;
15
+ keyEnds: Uint32Array;
16
+ }
17
+
18
+ /** A predicate batch: candidate ids and their host keys, laid out as in PlaneSearchHits. */
19
+ export type PlanePredicate = (ids: number[], keys: Buffer, keyEnds: Uint32Array) => Uint8Array;
20
+
21
+ /** NAPI surface of the native file-primary HNSW index (`@harperfast/hnsw`). */
22
+ export interface HnswPlane {
23
+ readonly dims: number;
24
+ readonly layer0Cap: number;
25
+ readonly keyCap: number;
26
+ insert(vector: Float32Array, key?: Buffer): number;
27
+ remove(id: number): void;
28
+ writeNodeRaw(
29
+ id: number,
30
+ level: number,
31
+ vector: Buffer,
32
+ scale: number,
33
+ invMag: number,
34
+ neighbors: Uint32Array,
35
+ upper: Uint32Array[] | null
36
+ ): void;
37
+ clearNode(id: number): void;
38
+ setEntryPoint(id: number, level: number): void;
39
+ getEntryPoint(): number[];
40
+ search(
41
+ vector: Float32Array,
42
+ k: number,
43
+ ef: number,
44
+ filter?: Uint8Array | null,
45
+ filterExpansion?: number | null
46
+ ): Promise<PlaneSearchHits>;
47
+ searchWithPredicate(
48
+ vector: Float32Array,
49
+ k: number,
50
+ ef: number,
51
+ predicate: PlanePredicate,
52
+ filterExpansion?: number | null,
53
+ visitBudget?: number | null
54
+ ): Promise<PlaneSearchHits>;
55
+ searchSync(vector: Float32Array, k: number, ef: number): PlaneSearchHits;
56
+ writeNodeRawIfAbsent(
57
+ id: number,
58
+ level: number,
59
+ vector: Buffer,
60
+ scale: number,
61
+ invMag: number,
62
+ neighbors: Uint32Array,
63
+ upper: Uint32Array[] | null
64
+ ): boolean;
65
+ openedClean(): boolean;
66
+ idHighWater(): number;
67
+ getWatermark(): number;
68
+ setWatermark(txn: number): void;
69
+ flush(watermark?: number): void;
70
+ flushAsync(watermark?: number): Promise<void>;
71
+ invalidateFile(): PlaneInvalidationOutcome;
72
+ invalidated(): boolean;
73
+ }
74
+
75
+ export interface HnswPlaneConstructor {
76
+ readonly prototype: HnswPlane;
77
+ create(path: string, dims: number, layer0Cap: number, maxNodes: number, keyCap?: number): HnswPlane;
78
+ open(path: string): HnswPlane;
79
+ }
80
+
81
+ export interface PlaneInvalidationOutcome {
82
+ inBand: boolean;
83
+ sidecar: boolean;
84
+ inBandError?: string;
85
+ sidecarError?: string;
86
+ }
87
+
88
+ interface HnswPlanePackage {
89
+ Plane: HnswPlaneConstructor;
90
+ invalidatePlane(path: string): PlaneInvalidationOutcome;
91
+ stalePathFor(path: string): string;
92
+ }
93
+
94
+ /** Entry-point id meaning "none" (u32::MAX in the plane header). */
95
+ export const PLANE_NO_ID = 0xffffffff;
96
+
97
+ /**
98
+ * Where an index's plane file lives: next to its store, named by the dbiKey
99
+ * (`table/attribute`, flattened to a single file name). Exposed separately from the index
100
+ * instance so crash-recovery drop paths can remove the file without opening the index.
101
+ */
102
+ export function planeFilePathFor(storePath: string, storeName: string): string {
103
+ // encodeURIComponent is injective and never emits a path separator: table `a` attribute
104
+ // `b.c` and table `a.b` attribute `c` must not share a plane file (dot-flattening let two
105
+ // indexes serve each other's node ids as their own primary keys)
106
+ return join(storePath, `${encodeURIComponent(storeName)}.hnsw`);
107
+ }
108
+
109
+ /**
110
+ * Tombstone marking a plane file that could not be deleted (e.g. Windows EBUSY while still
111
+ * mapped). Its presence means the plane file is STALE: never open it — delete both when
112
+ * possible and rebuild.
113
+ */
114
+ export function planeStalePathFor(planePath: string): string {
115
+ // the package owns the convention; the literal covers the calls that precede its load
116
+ return binding?.stalePathFor(planePath) ?? `${planePath}.stale`;
117
+ }
118
+
119
+ let binding: HnswPlanePackage | null | undefined;
120
+ let bindingLoadError: Error | undefined;
121
+ let bindingWarningLogged = false;
122
+
123
+ function getHnswPackage(warn = true): HnswPlanePackage | null {
124
+ if (binding === undefined) {
125
+ try {
126
+ binding = require('@harperfast/hnsw') as HnswPlanePackage;
127
+ } catch (error) {
128
+ binding = null;
129
+ bindingLoadError = error as Error;
130
+ }
131
+ }
132
+ if (binding === null && warn && !bindingWarningLogged) {
133
+ bindingWarningLogged = true;
134
+ logger.warn?.(
135
+ `The @harperfast/hnsw native module is not available (${bindingLoadError?.message}); ` +
136
+ 'indexes with nativePlane enabled will remain unavailable until the module can load'
137
+ );
138
+ }
139
+ return binding;
140
+ }
141
+
142
+ /** The native plane constructor, or null when the compiled artifact is unavailable (warns once by default). */
143
+ export function getPlaneBinding(warn = true): HnswPlaneConstructor | null {
144
+ return getHnswPackage(warn)?.Plane ?? null;
145
+ }
146
+
147
+ /** Make a derived plane unadoptable before it is replaced or removed. */
148
+ export function invalidatePlaneFile(filePath: string, attached?: HnswPlane | null): PlaneInvalidationOutcome {
149
+ if (attached) return attached.invalidateFile();
150
+ const hnswPackage = getHnswPackage();
151
+ if (hnswPackage) return hnswPackage.invalidatePlane(filePath);
152
+ // A plane can outlive the package that made it (uninstall, or a prebuild that stopped
153
+ // loading), and a reinstall would then adopt it. Only the sidecar is reachable without the
154
+ // package, and it has to survive a power loss to be worth writing, so fsync it and the
155
+ // directory entry that names it — the same durability the package's own sidecar has.
156
+ const fd = openSync(planeStalePathFor(filePath), 'w');
157
+ try {
158
+ fsyncSync(fd);
159
+ } finally {
160
+ closeSync(fd);
161
+ }
162
+ try {
163
+ const dirFd = openSync(dirname(filePath), 'r');
164
+ try {
165
+ fsyncSync(dirFd);
166
+ } finally {
167
+ closeSync(dirFd);
168
+ }
169
+ } catch {
170
+ // Windows cannot open a directory as a file; it also does not need this — the metadata
171
+ // journal already orders the create ahead of anything that could read the sidecar
172
+ }
173
+ return { inBand: false, sidecar: true };
174
+ }
@@ -0,0 +1,181 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isAbsolute, join, resolve } from 'node:path';
3
+ import { loggerWithTag } from '../../utility/logging/logger.ts';
4
+ import {
5
+ FullTextDerivedIndexBackend,
6
+ HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES,
7
+ type FullTextDerivedIndexBackendOptions,
8
+ type FullTextDerivedIndexEngine,
9
+ type FullTextDerivedIndexInspection,
10
+ } from './fullTextDerivedIndex.ts';
11
+ import {
12
+ getValidatedFullTextRuntimeInfo,
13
+ loadFullTextNativeBinding,
14
+ type NativeFullTextIndexConfiguration,
15
+ type NativeFullTextModule,
16
+ validateFullTextNativeBinding,
17
+ } from './fullTextNativeBinding.ts';
18
+
19
+ const logger = loggerWithTag('fulltext-derived-index');
20
+
21
+ export type NativeFullTextDerivedIndexLifecycleOptions = NativeFullTextIndexConfiguration & {
22
+ storePath: string;
23
+ storeName: string;
24
+ indexId: string;
25
+ sourceGeneration: string;
26
+ binding?: NativeFullTextModule | (() => Promise<NativeFullTextModule>);
27
+ };
28
+
29
+ export type NativeFullTextDerivedIndexBackendOptions = Omit<FullTextDerivedIndexBackendOptions, 'lifecycle'> &
30
+ Omit<NativeFullTextDerivedIndexLifecycleOptions, 'indexId'>;
31
+
32
+ export class NativeFullTextDerivedIndexLifecycle {
33
+ readonly #options: NativeFullTextDerivedIndexLifecycleOptions;
34
+ readonly #path: string;
35
+ readonly #generation: string;
36
+ #binding?: NativeFullTextModule;
37
+ #maxCommitPayloadBytes?: number;
38
+ #reclaimOperation = Promise.resolve();
39
+
40
+ constructor(options: NativeFullTextDerivedIndexLifecycleOptions) {
41
+ if (!isAbsolute(options.storePath)) throw new TypeError('Full-text storePath must be absolute');
42
+ if (!options.storeName) throw new TypeError('Full-text storeName is required');
43
+ if (!options.indexId) throw new TypeError('Full-text indexId is required');
44
+ if (!options.sourceGeneration) throw new TypeError('Full-text sourceGeneration is required');
45
+ this.#options = {
46
+ ...options,
47
+ fields: options.fields.map((field) => ({ ...field })),
48
+ limits: { ...options.limits },
49
+ };
50
+ // Hashing bounds filenames for arbitrary store names; the index id remains in native metadata and logs.
51
+ this.#path = join(resolve(options.storePath), `${digest(options.storeName)}.fulltext`);
52
+ this.#generation = digest(options.sourceGeneration);
53
+ }
54
+
55
+ get path(): string {
56
+ return this.#path;
57
+ }
58
+
59
+ async initialize(options: { reclaimRetired?: boolean } = {}): Promise<void> {
60
+ const binding = await this.#getBinding();
61
+ binding.validateNativeFullTextIndexOptions({
62
+ ...this.#nativeOptions(),
63
+ limits: this.#options.limits,
64
+ });
65
+ this.#maxCommitPayloadBytes = getValidatedFullTextRuntimeInfo(binding).limits.maxCommitPayloadBytes;
66
+ if (options.reclaimRetired !== false) this.#queueReclaimRetired();
67
+ }
68
+
69
+ startRetiredStorageReclamation(): void {
70
+ this.#requireBinding();
71
+ // Fulltext publishes unique retired paths and removes them with force, so workers may sweep concurrently.
72
+ this.#queueReclaimRetired();
73
+ }
74
+
75
+ get maxCommitPayloadBytes(): number {
76
+ if (this.#maxCommitPayloadBytes === undefined) throw new Error('Full-text lifecycle has not been initialized');
77
+ return this.#maxCommitPayloadBytes;
78
+ }
79
+
80
+ inspect(): FullTextDerivedIndexInspection {
81
+ return this.#requireBinding().inspectNativeFullTextIndex(this.#nativeOptions());
82
+ }
83
+
84
+ async open(): Promise<FullTextDerivedIndexEngine> {
85
+ return this.#requireBinding().openNativeFullTextIndex({
86
+ ...this.#nativeOptions(),
87
+ limits: this.#options.limits,
88
+ });
89
+ }
90
+
91
+ async reset(): Promise<void> {
92
+ const result = await this.#requireBinding().resetNativeFullTextIndex({
93
+ path: this.#path,
94
+ indexId: this.#options.indexId,
95
+ });
96
+ this.#queueReclaimRetired(result.state === 'reset' ? result.retiredPath : undefined);
97
+ }
98
+
99
+ #queueReclaimRetired(retiredPath?: string): void {
100
+ const reclaim = () => this.#reclaimRetired(retiredPath);
101
+ this.#reclaimOperation = this.#reclaimOperation.then(reclaim, reclaim).catch((error) => {
102
+ logWarning(`Could not complete reclamation for full-text index '${this.#options.indexId}'`, error);
103
+ });
104
+ }
105
+
106
+ async #reclaimRetired(retiredPath?: string): Promise<void> {
107
+ try {
108
+ const result = await this.#requireBinding().reclaimRetiredNativeFullTextIndexes({
109
+ path: this.#path,
110
+ retiredPath,
111
+ });
112
+ if (result.failed > 0)
113
+ logWarning(`Could not remove ${result.failed} retired paths for full-text index '${this.#options.indexId}'`);
114
+ } catch (error) {
115
+ logWarning(`Could not reclaim retired paths for full-text index '${this.#options.indexId}'`, error);
116
+ }
117
+ }
118
+
119
+ #nativeOptions(): Omit<NativeFullTextIndexConfiguration, 'limits'> & {
120
+ path: string;
121
+ indexId: string;
122
+ generation: string;
123
+ } {
124
+ return {
125
+ path: this.#path,
126
+ indexId: this.#options.indexId,
127
+ generation: this.#generation,
128
+ fields: this.#options.fields,
129
+ analyzer: this.#options.analyzer,
130
+ stopWords: this.#options.stopWords,
131
+ positions: this.#options.positions,
132
+ surfaceTerms: this.#options.surfaceTerms,
133
+ };
134
+ }
135
+
136
+ async #getBinding(): Promise<NativeFullTextModule> {
137
+ if (this.#binding) return this.#binding;
138
+ const configured = this.#options.binding;
139
+ this.#binding = configured
140
+ ? await validateFullTextNativeBinding(typeof configured === 'function' ? await configured() : configured)
141
+ : await loadFullTextNativeBinding();
142
+ return this.#binding;
143
+ }
144
+
145
+ #requireBinding(): NativeFullTextModule {
146
+ if (!this.#binding) throw new Error('Full-text native binding has not been initialized');
147
+ return this.#binding;
148
+ }
149
+ }
150
+
151
+ export async function createNativeFullTextDerivedIndexBackend(
152
+ options: NativeFullTextDerivedIndexBackendOptions
153
+ ): Promise<FullTextDerivedIndexBackend> {
154
+ const maxCursorPayloadBytes = options.maxCursorPayloadBytes ?? HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES;
155
+ if (maxCursorPayloadBytes > HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES)
156
+ throw new RangeError(`Full-text maxCursorPayloadBytes must not exceed ${HARPER_FULLTEXT_MAX_CURSOR_PAYLOAD_BYTES}`);
157
+ const lifecycle = new NativeFullTextDerivedIndexLifecycle({ ...options, indexId: options.id });
158
+ await lifecycle.initialize({ reclaimRetired: false });
159
+ if (maxCursorPayloadBytes > lifecycle.maxCommitPayloadBytes)
160
+ throw new RangeError('Full-text maxCursorPayloadBytes exceeds the native commit payload capacity');
161
+ const backend = new FullTextDerivedIndexBackend({
162
+ ...options,
163
+ id: options.id,
164
+ lifecycle,
165
+ maxCursorPayloadBytes,
166
+ });
167
+ lifecycle.startRetiredStorageReclamation();
168
+ return backend;
169
+ }
170
+
171
+ function digest(value: string): string {
172
+ return createHash('sha256').update(value).digest('hex');
173
+ }
174
+
175
+ function logWarning(message: string, error?: unknown): void {
176
+ const code =
177
+ error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
178
+ try {
179
+ logger.warn?.(`${message}${code ? ` (${code})` : ''}`);
180
+ } catch {}
181
+ }
@@ -50,6 +50,7 @@ function getIdMappingRecord(auditStore) {
50
50
  // now we can take over the local node id
51
51
  nameToId[node_name] = 0;
52
52
  auditStore.putSync(REMOTE_NODE_IDS, pack(idMappingRecord));
53
+ invalidateNodeNames(auditStore);
53
54
  }
54
55
  return idMappingRecord;
55
56
  }
@@ -84,6 +85,7 @@ export function remoteToLocalNodeId(remoteMapping: any, auditStore: any) {
84
85
  }
85
86
  if (hasChanges) {
86
87
  auditStore.putSync(REMOTE_NODE_IDS, pack(idMappingRecord));
88
+ invalidateNodeNames(auditStore);
87
89
  }
88
90
  return remoteToLocalId;
89
91
  }
@@ -103,6 +105,7 @@ export function getIdOfRemoteNode(remoteNodeName, auditStore) {
103
105
  id = lastId + 1;
104
106
  nameToId[remoteNodeName] = id;
105
107
  auditStore.putSync(REMOTE_NODE_IDS, pack(idMappingRecord));
108
+ invalidateNodeNames(auditStore);
106
109
  }
107
110
  logger.trace?.('The remote node name map', remoteNodeName, nameToId, id);
108
111
  return id;
@@ -123,3 +126,50 @@ export function lastTimeInAuditStore(auditStore: Database) {
123
126
  export function getThisNodeId(auditStore: any) {
124
127
  return exportIdMapping(auditStore)?.[server.hostname];
125
128
  }
129
+
130
+ // Inverted id -> name map. exportIdMapping() re-reads and unpacks the mapping record on every call,
131
+ // which is far too much per replicated entry on the apply thread; ids are stable once minted, so the
132
+ // inversion is cached and dropped wherever nameToId is written. Keyed per audit store because short
133
+ // ids are minted per database in first-seen order — id 1 names a different node in each one.
134
+ // Refresh window for a MISS. A hit is always trusted, but a miss cannot be: ids are minted by
135
+ // whichever worker first talks to a peer, and invalidation only reaches that worker's own copy — so
136
+ // a newly admitted node is absent from every other worker's map until it is rebuilt. Rebuilding on
137
+ // every miss would put a store read and unpack back on the apply thread for each unmapped entry, so
138
+ // misses re-read at most this often — short enough that a joining node loses at most a stray entry,
139
+ // long enough that a burst of unmapped ids cannot drive the store. `onMiss` records whether the
140
+ // cached map was built by a `rebuildOnMiss` caller, which is what bounds that caller to one read per
141
+ // window too rather than exempting it from the interval entirely.
142
+ const NODE_NAME_REFRESH_MS = 50;
143
+ const idToNodeName = new WeakMap<object, { names: Map<number, string>; refreshedAt: number; onMiss: boolean }>();
144
+ function invalidateNodeNames(auditStore: any) {
145
+ idToNodeName.delete(auditStore);
146
+ }
147
+
148
+ /**
149
+ * The node name a local short id refers to. Callers that must attribute a replicated entry to its
150
+ * origin node need the globally stable name, not the id, which is assigned per node.
151
+ */
152
+ export function getNodeNameForId(
153
+ auditStore: any,
154
+ nodeId: number | undefined,
155
+ rebuildOnMiss = false
156
+ ): string | undefined {
157
+ if (typeof nodeId !== 'number' || !auditStore) return undefined;
158
+ const cached = idToNodeName.get(auditStore);
159
+ const hit = cached?.names.get(nodeId);
160
+ if (hit !== undefined) return hit;
161
+ const now = Date.now();
162
+ // `rebuildOnMiss` is for entry classes whose loss costs more than the read: a dropped record lock
163
+ // release leaves the home holding a grant until its own deadline, so a just-minted id is worth one
164
+ // store read rather than up to `NODE_NAME_REFRESH_MS` of dropped entries. ONE read, though, not one
165
+ // per entry — an id this map will never resolve (a node whose mapping was purged, an origin
166
+ // relayed from a database that never minted one) is exactly the burst the interval exists to keep
167
+ // off the apply thread, and a rebuild inside the window is not stale: a write to the mapping drops
168
+ // this entry outright (`invalidateNodeNames`).
169
+ if (cached && now - cached.refreshedAt < NODE_NAME_REFRESH_MS && (!rebuildOnMiss || cached.onMiss)) return undefined;
170
+ const nameToId = exportIdMapping(auditStore);
171
+ const names = new Map<number, string>();
172
+ for (const name in nameToId) names.set(nameToId[name], name);
173
+ idToNodeName.set(auditStore, { names, refreshedAt: now, onMiss: rebuildOnMiss });
174
+ return names.get(nodeId);
175
+ }