ocean-brain 0.8.2 → 0.11.0

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 (244) hide show
  1. package/README.md +2 -0
  2. package/dist/mcp.js +103 -105
  3. package/package.json +3 -2
  4. package/server/client/dist/assets/{ArrowRight.es-g2YZTklZ.js → ArrowRight.es-BEhnlwfe.js} +1 -1
  5. package/server/client/dist/assets/AuxiliaryPanelHeader-B1k7pZpn.js +1 -0
  6. package/server/client/dist/assets/Calendar-D7QJsAlg.js +25 -0
  7. package/server/client/dist/assets/{Callout-B_IPSF_5.js → Callout-BTd1BYtV.js} +1 -1
  8. package/server/client/dist/assets/{Copy.es-CNxe4ujr.js → Copy.es-BoLjilxv.js} +1 -1
  9. package/server/client/dist/assets/File.es-Ct7fscBg.js +1 -0
  10. package/server/client/dist/assets/Graph-DYUb7gQb.js +46 -0
  11. package/server/client/dist/assets/{Graph-D9GeO71k.js → Graph-LY_IgMYy.js} +1 -1
  12. package/server/client/dist/assets/{Image.es-DnON8cv2.js → Image.es-OYyN3BYt.js} +1 -1
  13. package/server/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  14. package/server/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  15. package/server/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  16. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  17. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  18. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  19. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  20. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  21. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  22. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  23. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  24. package/server/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  25. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  26. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  27. package/server/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  28. package/server/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  29. package/server/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  30. package/server/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  31. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  32. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  33. package/server/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  34. package/server/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  35. package/server/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  36. package/server/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  37. package/server/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  38. package/server/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  39. package/server/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  40. package/server/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  41. package/server/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  42. package/server/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  43. package/server/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  44. package/server/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  45. package/server/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  46. package/server/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  47. package/server/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  48. package/server/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  49. package/server/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  50. package/server/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  51. package/server/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  52. package/server/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  53. package/server/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  54. package/server/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  55. package/server/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  56. package/server/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  57. package/server/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  58. package/server/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  59. package/server/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  60. package/server/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  61. package/server/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  62. package/server/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  63. package/server/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  64. package/server/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  65. package/server/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  66. package/server/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  67. package/server/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  68. package/server/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  69. package/server/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  70. package/server/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  71. package/server/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  72. package/server/client/dist/assets/Note-DsMhwrHw.js +25 -0
  73. package/server/client/dist/assets/NotePropertiesPanel-aCKXAe5F.js +2 -0
  74. package/server/client/dist/assets/PencilLine.es-0JpEgxFY.js +1 -0
  75. package/server/client/dist/assets/{Plus.es-CQ8wn2is.js → Plus.es-DRfLW_bI.js} +1 -1
  76. package/server/client/dist/assets/Reminders-COr1OHvF.js +1 -0
  77. package/server/client/dist/assets/Search-Ctkx0BQk.js +31 -0
  78. package/server/client/dist/assets/SurfaceCard-t7pdfhEe.js +1 -0
  79. package/server/client/dist/assets/Tag-BjxtrIQW.js +1 -0
  80. package/server/client/dist/assets/TagNotes-DV_fdRbg.js +1 -0
  81. package/server/client/dist/assets/{Trash.es-0aPpA-3S.js → Trash.es-Bo1VyK9Q.js} +1 -1
  82. package/server/client/dist/assets/ViewChip-DlCkL4z4.js +1 -0
  83. package/server/client/dist/assets/ViewNotes-wWWeFJyU.js +1 -0
  84. package/server/client/dist/assets/ViewSectionDialog-DhH8harC.js +102 -0
  85. package/server/client/dist/assets/Views-CWE99SnI.js +1 -0
  86. package/server/client/dist/assets/abnfDiagram-VRR7QNED-CWlV5dXw.js +1 -0
  87. package/server/client/dist/assets/app-B9WZRrAH.css +1 -0
  88. package/server/client/dist/assets/app-BpSMjzET.js +26 -0
  89. package/server/client/dist/assets/arc-BV-n6RUX.js +1 -0
  90. package/server/client/dist/assets/architectureDiagram-ZJ3FMSHR-CekzMXdu.js +36 -0
  91. package/server/client/dist/assets/blockDiagram-677ZJIJ3-B49yHoxq.js +132 -0
  92. package/server/client/dist/assets/c4Diagram-LMCZKHZV-0AfB9NBC.js +10 -0
  93. package/server/client/dist/assets/channel-CigOulR-.js +1 -0
  94. package/server/client/dist/assets/chunk-2Q5K7J3B-DaPQAU1q.js +1 -0
  95. package/server/client/dist/assets/chunk-32BRIVSS-CfL3kZAg.js +1 -0
  96. package/server/client/dist/assets/chunk-5VM5RSS4-DeOwWPE_.js +15 -0
  97. package/server/client/dist/assets/chunk-EX3LRPZG-C3W1oc9y.js +231 -0
  98. package/server/client/dist/assets/chunk-JWPE2WC7-_Oqh79OY.js +1 -0
  99. package/server/client/dist/assets/chunk-MOJQB5TN-DDKNNzI6.js +88 -0
  100. package/server/client/dist/assets/chunk-RYQCIY6F-DCHah7CC.js +1 -0
  101. package/server/client/dist/assets/chunk-V7JOEXUC-uUOIzgxj.js +206 -0
  102. package/server/client/dist/assets/chunk-VR4S4FIN-DDVw2WPs.js +1 -0
  103. package/server/client/dist/assets/chunk-XXDRQBXY-Coo4UFm2.js +1 -0
  104. package/server/client/dist/assets/classDiagram-OUVF2IWQ-D13ISIOW.js +1 -0
  105. package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-D13ISIOW.js +1 -0
  106. package/server/client/dist/assets/colors-Cc3OSVma.js +1 -0
  107. package/server/client/dist/assets/cose-bilkent-JH36ORCC-C3pHmHpD.js +1 -0
  108. package/server/client/dist/assets/cynefin-VYW2F7L2-DU_G4Atc.js +166 -0
  109. package/server/client/dist/assets/cynefinDiagram-TSTJHNR4-DZ6iiJFo.js +62 -0
  110. package/server/client/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  111. package/server/client/dist/assets/dagre-VKFMJZFB-DNELbhwC.js +4 -0
  112. package/server/client/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  113. package/server/client/dist/assets/diagram-FQU43EPY-CMEvZJ61.js +3 -0
  114. package/server/client/dist/assets/diagram-G47NLZAW-BYDtf6j9.js +24 -0
  115. package/server/client/dist/assets/diagram-NH7WQ7WH-CmXfcCQA.js +24 -0
  116. package/server/client/dist/assets/diagram-OA4YK3LP-BflTKJxU.js +30 -0
  117. package/server/client/dist/assets/diagram-WEI45ONY-KW3fKqna.js +41 -0
  118. package/server/client/dist/assets/ebnfDiagram-CCIWWBDH-CbyhY5Nj.js +1 -0
  119. package/server/client/dist/assets/erDiagram-Q63AITRT-CC2eZV8g.js +85 -0
  120. package/server/client/dist/assets/{floating-ui.react-dom-wWLZ1CdM.js → floating-ui.react-dom-BWTnA00G.js} +1 -1
  121. package/server/client/dist/assets/flowDiagram-23GEKE2U-unplo7Qm.js +156 -0
  122. package/server/client/dist/assets/ganttDiagram-NO4QXBWP-BcBK5HcZ.js +292 -0
  123. package/server/client/dist/assets/gitGraphDiagram-IHSO6WYX-DgVbbb5k.js +106 -0
  124. package/server/client/dist/assets/graph-C5DFNLrC.js +1 -0
  125. package/server/client/dist/assets/image-delete-confirmation-D7xrODQV.js +1 -0
  126. package/server/client/dist/assets/image.api-CyQisInP.js +17 -0
  127. package/server/client/dist/assets/index-B-E1Ilh9.js +1 -0
  128. package/server/client/dist/assets/infoDiagram-FWYZ7A6U-N_D9RdfG.js +2 -0
  129. package/server/client/dist/assets/init-Gi6I4Gst.js +1 -0
  130. package/server/client/dist/assets/isObject-U9U2HXiq.js +1 -0
  131. package/server/client/dist/assets/ishikawaDiagram-FXEZZL3T-BBKrzmB2.js +70 -0
  132. package/server/client/dist/assets/journeyDiagram-5HDEW3XC-NdCxLmQB.js +139 -0
  133. package/server/client/dist/assets/kanban-definition-HUTT4EX6-Ck7t4-tt.js +89 -0
  134. package/server/client/dist/assets/katex-DywYRkvg.css +1 -0
  135. package/server/client/dist/assets/katex-HP8lGamR.js +257 -0
  136. package/server/client/dist/assets/katex-x288kqHd.js +257 -0
  137. package/server/client/dist/assets/layout-Cz0MDlGi.js +1 -0
  138. package/server/client/dist/assets/linear-D6yRgDLq.js +1 -0
  139. package/server/client/dist/assets/manage-image-azmo5iVw.js +1 -0
  140. package/server/client/dist/assets/manage-image-detail-BF-uSqb_.js +1 -0
  141. package/server/client/dist/assets/map-Mm1V-_Na.js +1 -0
  142. package/server/client/dist/assets/{mcp-CT_9IV2c.js → mcp-CCaFd0zm.js} +4 -4
  143. package/server/client/dist/assets/{mcp-admin-adapter-xAuyZc_M.js → mcp-admin-adapter-PMmgPupc.js} +1 -1
  144. package/server/client/dist/assets/mermaid.core-B14ZAmWA.js +308 -0
  145. package/server/client/dist/assets/min-tv6RD-6E.js +1 -0
  146. package/server/client/dist/assets/mindmap-definition-LN4V7U3C-BTUqSdcP.js +96 -0
  147. package/server/client/dist/assets/{note-core-Buwzyxi7.js → note-core-DUTqUh5N.js} +2 -2
  148. package/server/client/dist/assets/{note-runtime-CPDVOFEo.js → note-runtime-zN5ddTba.js} +1 -1
  149. package/server/client/dist/assets/{note-ui-CHI-tzj-.js → note-ui-CtijppHK.js} +1 -1
  150. package/server/client/dist/assets/{note.api-D_00ZzJC.js → note.api-D3oJmG3q.js} +6 -6
  151. package/server/client/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  152. package/server/client/dist/assets/pegDiagram-2B236MQR-Cjcu3mVz.js +1 -0
  153. package/server/client/dist/assets/pieDiagram-ENE6RG2P-BxI_c22Z.js +39 -0
  154. package/server/client/dist/assets/{placeholder-DbdGiYGL.js → placeholder-AusSo8p9.js} +4 -4
  155. package/server/client/dist/assets/properties-BrNJLkxR.js +1 -0
  156. package/server/client/dist/assets/quadrantDiagram-ABIIQ3AL-DGm0a-yR.js +7 -0
  157. package/server/client/dist/assets/railroadDiagram-RFXS5EU6-DmCDu89O.js +1 -0
  158. package/server/client/dist/assets/requirementDiagram-TGXJPOKE-CBpUt7Ig.js +84 -0
  159. package/server/client/dist/assets/route-preload.js +2 -2
  160. package/server/client/dist/assets/sankeyDiagram-HTMAVEWB-BjGnDZlp.js +40 -0
  161. package/server/client/dist/assets/search-D-xKTPdO.js +1 -0
  162. package/server/client/dist/assets/search-admin-adapter-CJnJmL6w.js +1 -0
  163. package/server/client/dist/assets/sequenceDiagram-DBY2YBRQ-B7rpCfS6.js +162 -0
  164. package/server/client/dist/assets/sizeCapture-X5ZJPWSS-CUJhD0uW.js +1 -0
  165. package/server/client/dist/assets/stateDiagram-2N3HPSRC-Y9-3XqRX.js +1 -0
  166. package/server/client/dist/assets/stateDiagram-v2-6OUMAXLB-DbHXhT02.js +1 -0
  167. package/server/client/dist/assets/swimlanes-5IMT3BWC-Cs1NO9ng.js +2 -0
  168. package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-DQnrq2ZC.js +8 -0
  169. package/server/client/dist/assets/{tag.api-CX0SE06J.js → tag.api-DNSQpqwk.js} +3 -3
  170. package/server/client/dist/assets/time-D5LRMumI.js +9 -0
  171. package/server/client/dist/assets/timeline-definition-FHXFAJF6-8fqD4WNL.js +120 -0
  172. package/server/client/dist/assets/toNumber-DE3J3kMD.js +1 -0
  173. package/server/client/dist/assets/transform-BUB79_za.js +1 -0
  174. package/server/client/dist/assets/trash-D4-9sv8q.js +1 -0
  175. package/server/client/dist/assets/url-9nrbQs7Z.js +1 -0
  176. package/server/client/dist/assets/{useReminderMutate-CLK1tHvT.js → useReminderMutate-BrGO9M0o.js} +4 -4
  177. package/server/client/dist/assets/vennDiagram-L72KCM5P-BPqKkd5a.js +34 -0
  178. package/server/client/dist/assets/wardleyDiagram-EHGQE667-Cq11kU2M.js +78 -0
  179. package/server/client/dist/assets/xychartDiagram-FW5EYKEG-DJ6UzFj-.js +7 -0
  180. package/server/client/dist/index.html +7 -7
  181. package/server/dist/features/image/services/delete.js +25 -1
  182. package/server/dist/features/note/graphql/note.field.resolver.js +2 -0
  183. package/server/dist/features/note/graphql/note.mutation.resolver.js +54 -23
  184. package/server/dist/features/note/graphql/note.query.resolver.js +15 -5
  185. package/server/dist/features/note/graphql/note.type-defs.js +2 -1
  186. package/server/dist/features/note/http/mcp.js +1 -60
  187. package/server/dist/features/note/services/authoring.js +17 -80
  188. package/server/dist/features/note/services/markdown-patch.js +80 -8
  189. package/server/dist/features/note/services/note-reference-index.js +98 -0
  190. package/server/dist/features/note/services/properties.js +21 -1
  191. package/server/dist/features/note/services/property-restore.js +26 -0
  192. package/server/dist/features/note/services/search.js +5 -0
  193. package/server/dist/features/note/services/snapshot.js +82 -58
  194. package/server/dist/features/note/services/trash.js +18 -31
  195. package/server/dist/features/note/services/write.js +10 -3
  196. package/server/dist/features/search/embedding-api-key-store.js +57 -0
  197. package/server/dist/features/search/embedding-client.js +179 -0
  198. package/server/dist/features/search/graphql/index.js +6 -0
  199. package/server/dist/features/search/graphql/resolver.js +32 -0
  200. package/server/dist/features/search/graphql/type-defs.js +37 -0
  201. package/server/dist/features/search/http/handlers.js +108 -0
  202. package/server/dist/features/search/hybrid-ranking.js +57 -0
  203. package/server/dist/features/search/hybrid-search.js +176 -0
  204. package/server/dist/features/search/note-change.js +16 -0
  205. package/server/dist/features/search/note-chunking.js +75 -0
  206. package/server/dist/features/search/related-notes.js +186 -0
  207. package/server/dist/features/search/search-config.js +150 -0
  208. package/server/dist/features/search/search-manager.js +431 -0
  209. package/server/dist/features/search/semantic-indexer.js +138 -0
  210. package/server/dist/features/search/sqlite-vector-index.js +636 -0
  211. package/server/dist/features/search/url-normalization.js +10 -0
  212. package/server/dist/features/tag/services/organization.js +3 -0
  213. package/server/dist/features/view/services/workspace.js +1 -1
  214. package/server/dist/main.js +12 -3
  215. package/server/dist/modules/blocknote.js +4 -5
  216. package/server/dist/paths.js +7 -2
  217. package/server/dist/routes/api.js +37 -0
  218. package/server/dist/routes/mcp.js +0 -5
  219. package/server/dist/schema/index.js +3 -0
  220. package/server/prisma/migrations/20260728090000_0019_note_reference_index/migration.sql +15 -0
  221. package/server/prisma/schema.prisma +16 -0
  222. package/server/client/dist/assets/Calendar-BP6HTuSn.js +0 -25
  223. package/server/client/dist/assets/Graph-Xha3fwhp.js +0 -46
  224. package/server/client/dist/assets/Image-BLEjLUqd.js +0 -1
  225. package/server/client/dist/assets/Note-DjrCYtat.js +0 -24
  226. package/server/client/dist/assets/NotePropertiesPanel-2DgXEM4B.js +0 -2
  227. package/server/client/dist/assets/Reminders-B4l3KdlK.js +0 -1
  228. package/server/client/dist/assets/Search-BN5sf7bE.js +0 -1
  229. package/server/client/dist/assets/SurfaceCard-BZm73nyS.js +0 -1
  230. package/server/client/dist/assets/Tag-oSnmViVp.js +0 -1
  231. package/server/client/dist/assets/TagNotes-BDTCcJcL.js +0 -1
  232. package/server/client/dist/assets/ViewNotes-CiZtGJNE.js +0 -1
  233. package/server/client/dist/assets/ViewSectionDialog-Ctl_694Y.js +0 -102
  234. package/server/client/dist/assets/Views-D-zlatYD.js +0 -1
  235. package/server/client/dist/assets/app-DmqIOMWF.css +0 -1
  236. package/server/client/dist/assets/app-cTRcKMGc.js +0 -26
  237. package/server/client/dist/assets/image.api-CVuuvTBk.js +0 -17
  238. package/server/client/dist/assets/index-DKJChPr4.js +0 -1
  239. package/server/client/dist/assets/manage-image-BZjaxp-f.js +0 -1
  240. package/server/client/dist/assets/manage-image-detail-DPv0FQ2m.js +0 -1
  241. package/server/client/dist/assets/properties-D447wsWk.js +0 -1
  242. package/server/client/dist/assets/time-B3pt8vt4.js +0 -9
  243. package/server/client/dist/assets/trash-8sH7EZgc.js +0 -1
  244. package/server/client/dist/assets/url-BSnWpmJx.js +0 -1
@@ -4,6 +4,8 @@ import {
4
4
  RECOVERY_CLEANUP_BATCH_LIMIT,
5
5
  TRASH_RETENTION_DAYS
6
6
  } from "../../../modules/recovery-retention.js";
7
+ import { replaceNoteReferences } from "./note-reference-index.js";
8
+ import { resolveRestoredPropertyTarget } from "./property-restore.js";
7
9
  import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
8
10
  const TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH = 240;
9
11
  class NoteRestoreConflictError extends Error {
@@ -288,42 +290,27 @@ const noteTrashService = createNoteTrashService({
288
290
  } : {}
289
291
  }
290
292
  });
293
+ await replaceNoteReferences(tx, note.id, restoredContent);
291
294
  for (const property of deletedNote.properties ?? []) {
292
- const definition = await tx.propertyDefinition.upsert({
293
- where: { key: property.key },
294
- create: {
295
- key: property.key,
296
- name: property.name,
297
- valueType: property.valueType
298
- },
299
- update: {
300
- name: property.name,
301
- valueType: property.valueType
302
- }
303
- });
304
- const option = property.valueType === "select" && property.optionValue ? await tx.propertyOption.upsert({
305
- where: {
306
- propertyDefinitionId_value: {
307
- propertyDefinitionId: definition.id,
308
- value: property.optionValue
295
+ const target = await resolveRestoredPropertyTarget(property, {
296
+ findDefinitionByKey: (key) => tx.propertyDefinition.findUnique({ where: { key } }),
297
+ findOptionByValue: (propertyDefinitionId, value) => tx.propertyOption.findUnique({
298
+ where: {
299
+ propertyDefinitionId_value: {
300
+ propertyDefinitionId,
301
+ value
302
+ }
309
303
  }
310
- },
311
- create: {
312
- propertyDefinitionId: definition.id,
313
- value: property.optionValue,
314
- label: property.optionLabel ?? property.optionValue,
315
- color: property.optionColor
316
- },
317
- update: {
318
- label: property.optionLabel ?? property.optionValue,
319
- color: property.optionColor
320
- }
321
- }) : null;
304
+ })
305
+ });
306
+ if (!target) {
307
+ continue;
308
+ }
322
309
  await tx.noteProperty.create({
323
310
  data: {
324
311
  noteId: note.id,
325
- propertyDefinitionId: definition.id,
326
- optionId: option?.id ?? null,
312
+ propertyDefinitionId: target.propertyDefinitionId,
313
+ optionId: target.optionId,
327
314
  textValue: property.textValue,
328
315
  textValueNormalized: property.textValueNormalized,
329
316
  numberValue: property.numberValue,
@@ -1,4 +1,5 @@
1
1
  import models, { Prisma } from "../../../models.js";
2
+ import { replaceNoteReferences } from "./note-reference-index.js";
2
3
  import { buildNoteSearchProjection } from "./search.js";
3
4
  import { captureNoteBaseline } from "./snapshot.js";
4
5
  import { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from "./write-conflict.js";
@@ -101,9 +102,15 @@ const defaultNoteWriteService = createNoteWriteService({
101
102
  }
102
103
  }),
103
104
  captureBaseline: captureNoteBaseline,
104
- updateNote: ({ where, data }) => models.note.update({
105
- where,
106
- data
105
+ updateNote: ({ where, data }) => models.$transaction(async (tx) => {
106
+ const note = await tx.note.update({
107
+ where,
108
+ data
109
+ });
110
+ if (data.content !== void 0) {
111
+ await replaceNoteReferences(tx, note.id, data.content);
112
+ }
113
+ return note;
107
114
  }),
108
115
  isRecordNotFoundError
109
116
  });
@@ -0,0 +1,57 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ const MAX_EMBEDDING_API_KEY_LENGTH = 8192;
5
+ const normalizeEmbeddingApiKey = (value) => {
6
+ const apiKey = value.trim();
7
+ if (apiKey.length > MAX_EMBEDDING_API_KEY_LENGTH) {
8
+ throw new Error("Embedding API key is too long.");
9
+ }
10
+ return apiKey || void 0;
11
+ };
12
+ const createEmbeddingApiKeyFingerprint = (apiKey) => {
13
+ if (!apiKey) {
14
+ return "";
15
+ }
16
+ return createHash("sha256").update(apiKey).digest("hex");
17
+ };
18
+ class FileEmbeddingApiKeyStore {
19
+ constructor(filePath) {
20
+ this.filePath = filePath;
21
+ try {
22
+ this.apiKey = normalizeEmbeddingApiKey(readFileSync(filePath, "utf8"));
23
+ } catch (error) {
24
+ if (error.code !== "ENOENT") {
25
+ throw error;
26
+ }
27
+ }
28
+ }
29
+ filePath;
30
+ apiKey;
31
+ get() {
32
+ return this.apiKey;
33
+ }
34
+ set(value) {
35
+ const apiKey = value === void 0 ? void 0 : normalizeEmbeddingApiKey(value);
36
+ if (!apiKey) {
37
+ rmSync(this.filePath, { force: true });
38
+ this.apiKey = void 0;
39
+ return;
40
+ }
41
+ mkdirSync(path.dirname(this.filePath), { recursive: true });
42
+ const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
43
+ try {
44
+ writeFileSync(temporaryPath, apiKey, { encoding: "utf8", mode: 384, flag: "wx" });
45
+ renameSync(temporaryPath, this.filePath);
46
+ this.apiKey = apiKey;
47
+ } finally {
48
+ rmSync(temporaryPath, { force: true });
49
+ }
50
+ }
51
+ }
52
+ export {
53
+ FileEmbeddingApiKeyStore,
54
+ MAX_EMBEDDING_API_KEY_LENGTH,
55
+ createEmbeddingApiKeyFingerprint,
56
+ normalizeEmbeddingApiKey
57
+ };
@@ -0,0 +1,179 @@
1
+ import { stripTrailingSlashes } from "./url-normalization.js";
2
+ const DEFAULT_EMBEDDING_TIMEOUT_MS = 6e4;
3
+ const DEFAULT_MODEL_DISCOVERY_TIMEOUT_MS = 1e4;
4
+ const buildOpenAiCompatibleUrl = (baseUrl, resource) => {
5
+ let parsedUrl;
6
+ try {
7
+ parsedUrl = new URL(baseUrl);
8
+ } catch {
9
+ throw new Error("Embedding API URL must be a valid http or https URL.");
10
+ }
11
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
12
+ throw new Error("Embedding API URL must use http or https.");
13
+ }
14
+ if (parsedUrl.username || parsedUrl.password) {
15
+ throw new Error("Embedding API URL must not contain credentials.");
16
+ }
17
+ const normalizedPath = stripTrailingSlashes(parsedUrl.pathname);
18
+ const normalizedPathLower = normalizedPath.toLowerCase();
19
+ const resourceSuffix = ["/embeddings", "/models"].find((suffix) => normalizedPathLower.endsWith(suffix));
20
+ const basePath = resourceSuffix ? normalizedPath.slice(0, -resourceSuffix.length) : normalizedPath;
21
+ parsedUrl.pathname = `${basePath || "/v1"}/${resource}`;
22
+ parsedUrl.search = "";
23
+ parsedUrl.hash = "";
24
+ return parsedUrl.toString();
25
+ };
26
+ const buildEmbeddingsUrl = (baseUrl) => buildOpenAiCompatibleUrl(baseUrl, "embeddings");
27
+ const buildModelsUrl = (baseUrl) => buildOpenAiCompatibleUrl(baseUrl, "models");
28
+ const validateEmbedding = (value, expectedIndex) => {
29
+ if (!Array.isArray(value) || value.length === 0) {
30
+ throw new Error(`Embedding API returned an empty vector for input ${expectedIndex}.`);
31
+ }
32
+ const embedding = value.map((item) => {
33
+ if (typeof item !== "number" || !Number.isFinite(item)) {
34
+ throw new Error(`Embedding API returned an invalid vector value for input ${expectedIndex}.`);
35
+ }
36
+ return item;
37
+ });
38
+ return embedding;
39
+ };
40
+ const parseEmbeddingResponse = (payload, expectedCount) => {
41
+ if (!payload || typeof payload !== "object" || !("data" in payload) || !Array.isArray(payload.data)) {
42
+ throw new Error("Embedding API response does not contain a data array.");
43
+ }
44
+ const items = payload.data;
45
+ if (items.length !== expectedCount) {
46
+ throw new Error(`Embedding API returned ${items.length} vectors for ${expectedCount} inputs.`);
47
+ }
48
+ const orderedItems = [...items].sort((left, right) => {
49
+ const leftIndex = typeof left.index === "number" ? left.index : items.indexOf(left);
50
+ const rightIndex = typeof right.index === "number" ? right.index : items.indexOf(right);
51
+ return leftIndex - rightIndex;
52
+ });
53
+ const embeddings = orderedItems.map((item, index) => validateEmbedding(item.embedding, index));
54
+ const dimensions = embeddings[0]?.length;
55
+ if (!dimensions || embeddings.some((embedding) => embedding.length !== dimensions)) {
56
+ throw new Error("Embedding API returned vectors with inconsistent dimensions.");
57
+ }
58
+ return embeddings;
59
+ };
60
+ const redactSecret = (message, secret) => {
61
+ if (!secret || !message.includes(secret)) {
62
+ return message;
63
+ }
64
+ return message.split(secret).join("[redacted]");
65
+ };
66
+ const readApiErrorMessage = async (response, secret) => {
67
+ try {
68
+ const payload = await response.json();
69
+ const message = payload.error?.message ?? payload.message;
70
+ return typeof message === "string" && message.trim() ? redactSecret(message.trim(), secret) : void 0;
71
+ } catch {
72
+ return void 0;
73
+ }
74
+ };
75
+ const looksLikeEmbeddingModel = (modelId) => /(^|[-_/])(embed|embedding|bge|e5|gte)([-_/.]|$)/i.test(modelId);
76
+ const listOpenAiCompatibleEmbeddingModels = async (baseUrl, options = {}) => {
77
+ const endpoint = buildModelsUrl(baseUrl);
78
+ const fetchImpl = options.fetch ?? fetch;
79
+ const timeoutMs = options.timeoutMs ?? DEFAULT_MODEL_DISCOVERY_TIMEOUT_MS;
80
+ const response = await fetchImpl(endpoint, {
81
+ method: "GET",
82
+ headers: {
83
+ Accept: "application/json",
84
+ ...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}
85
+ },
86
+ redirect: "error",
87
+ signal: AbortSignal.timeout(timeoutMs)
88
+ });
89
+ if (!response.ok) {
90
+ const apiMessage = await readApiErrorMessage(response, options.apiKey);
91
+ throw new Error(
92
+ apiMessage ? `Model discovery failed (${response.status}): ${apiMessage}` : `Model discovery failed with status ${response.status}.`
93
+ );
94
+ }
95
+ const payload = await response.json();
96
+ if (!Array.isArray(payload.data)) {
97
+ throw new Error("Model discovery response does not contain a data array.");
98
+ }
99
+ const modelIds = [
100
+ ...new Set(
101
+ payload.data.map((item) => {
102
+ if (!item || typeof item !== "object") return "";
103
+ const id = item.id;
104
+ return typeof id === "string" ? id.trim() : "";
105
+ }).filter(Boolean)
106
+ )
107
+ ];
108
+ if (modelIds.length === 0) {
109
+ throw new Error("The API returned no models.");
110
+ }
111
+ return modelIds.map((id) => ({ id, likelyEmbedding: looksLikeEmbeddingModel(id) })).sort((left, right) => {
112
+ if (left.likelyEmbedding !== right.likelyEmbedding) {
113
+ return left.likelyEmbedding ? -1 : 1;
114
+ }
115
+ return left.id.localeCompare(right.id);
116
+ });
117
+ };
118
+ const buildQueryInput = (query, instruction) => {
119
+ const normalizedInstruction = instruction?.trim();
120
+ if (!normalizedInstruction) {
121
+ return query;
122
+ }
123
+ return `Instruct: ${normalizedInstruction}
124
+ Query: ${query}`;
125
+ };
126
+ const createOpenAiCompatibleEmbeddingClient = (config, options = {}) => {
127
+ const endpoint = buildEmbeddingsUrl(config.baseUrl);
128
+ const model = config.model.trim();
129
+ const fetchImpl = options.fetch ?? fetch;
130
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_TIMEOUT_MS;
131
+ if (!model) {
132
+ throw new Error("Embedding model is required.");
133
+ }
134
+ const embed = async (inputs) => {
135
+ if (inputs.length === 0) {
136
+ return [];
137
+ }
138
+ const response = await fetchImpl(endpoint, {
139
+ method: "POST",
140
+ headers: {
141
+ "Content-Type": "application/json",
142
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
143
+ },
144
+ body: JSON.stringify({
145
+ model,
146
+ input: inputs,
147
+ encoding_format: "float"
148
+ }),
149
+ redirect: "error",
150
+ signal: AbortSignal.timeout(timeoutMs)
151
+ });
152
+ if (!response.ok) {
153
+ const apiMessage = await readApiErrorMessage(response, config.apiKey);
154
+ throw new Error(
155
+ apiMessage ? `Embedding API request failed (${response.status}): ${apiMessage}` : `Embedding API request failed with status ${response.status}.`
156
+ );
157
+ }
158
+ return parseEmbeddingResponse(await response.json(), inputs.length);
159
+ };
160
+ return {
161
+ embedDocuments: embed,
162
+ async embedQuery(query) {
163
+ const normalizedQuery = query.trim();
164
+ if (!normalizedQuery) {
165
+ throw new Error("Embedding query must not be empty.");
166
+ }
167
+ const [embedding] = await embed([buildQueryInput(normalizedQuery, config.queryInstruction)]);
168
+ return embedding;
169
+ }
170
+ };
171
+ };
172
+ const normalizeEmbeddingApiUrl = buildEmbeddingsUrl;
173
+ const normalizeEmbeddingModelsUrl = buildModelsUrl;
174
+ export {
175
+ createOpenAiCompatibleEmbeddingClient,
176
+ listOpenAiCompatibleEmbeddingModels,
177
+ normalizeEmbeddingApiUrl,
178
+ normalizeEmbeddingModelsUrl
179
+ };
@@ -0,0 +1,6 @@
1
+ import { searchResolvers } from "./resolver.js";
2
+ import { searchTypeDefs } from "./type-defs.js";
3
+ export {
4
+ searchResolvers,
5
+ searchTypeDefs
6
+ };
@@ -0,0 +1,32 @@
1
+ import { searchNotesHybrid } from "../hybrid-search.js";
2
+ import { searchRelatedNotes } from "../related-notes.js";
3
+ const createSearchNotesResolver = (search = searchNotesHybrid) => {
4
+ return async (_, { query, pagination, mode = "HYBRID" }) => {
5
+ return search({
6
+ query,
7
+ limit: Math.min(50, Math.max(0, pagination.limit)),
8
+ offset: Math.max(0, pagination.offset),
9
+ mode: mode.toLowerCase()
10
+ });
11
+ };
12
+ };
13
+ const createSearchRelatedNotesResolver = (findRelatedNotes = searchRelatedNotes) => {
14
+ return async (_, { noteId, limit = 5 }) => {
15
+ const numericNoteId = Number(noteId);
16
+ if (!Number.isSafeInteger(numericNoteId) || numericNoteId <= 0) {
17
+ return [];
18
+ }
19
+ return findRelatedNotes(numericNoteId, Math.min(5, Math.max(0, Number(limit))));
20
+ };
21
+ };
22
+ const searchResolvers = {
23
+ Query: {
24
+ searchNotes: createSearchNotesResolver(),
25
+ searchRelatedNotes: createSearchRelatedNotesResolver()
26
+ }
27
+ };
28
+ export {
29
+ createSearchNotesResolver,
30
+ createSearchRelatedNotesResolver,
31
+ searchResolvers
32
+ };
@@ -0,0 +1,37 @@
1
+ import { gql } from "../../../modules/graphql.js";
2
+ const searchTypeDefs = gql`
3
+ enum SearchMode {
4
+ HYBRID
5
+ LEXICAL
6
+ SEMANTIC
7
+ }
8
+
9
+ type SearchNoteMatch {
10
+ noteId: ID!
11
+ lexical: Boolean!
12
+ semantic: Boolean!
13
+ }
14
+
15
+ type SearchRelatedNote {
16
+ id: ID!
17
+ title: String!
18
+ reasons: [String!]!
19
+ }
20
+
21
+ type SearchNotesResult {
22
+ totalCount: Int!
23
+ notes: [Note!]!
24
+ matches: [SearchNoteMatch!]!
25
+ semanticAvailable: Boolean!
26
+ semanticUsed: Boolean!
27
+ semanticError: String
28
+ }
29
+
30
+ extend type Query {
31
+ searchNotes(query: String!, pagination: PaginationInput!, mode: SearchMode = HYBRID): SearchNotesResult!
32
+ searchRelatedNotes(noteId: ID!, limit: Int = 5): [SearchRelatedNote!]!
33
+ }
34
+ `;
35
+ export {
36
+ searchTypeDefs
37
+ };
@@ -0,0 +1,108 @@
1
+ import { createAppError } from "../../../modules/error-handler.js";
2
+ import { normalizeEmbeddingApiKey } from "../embedding-api-key-store.js";
3
+ import { normalizeEmbeddingModelsUrl } from "../embedding-client.js";
4
+ import { normalizeSemanticSearchConfig } from "../search-config.js";
5
+ import {
6
+ getDefaultSemanticSearchManager
7
+ } from "../search-manager.js";
8
+ import { stripTrailingSlashes } from "../url-normalization.js";
9
+ const parseApiKeyInput = (value) => {
10
+ if (!value || typeof value !== "object" || !Object.hasOwn(value, "apiKey")) {
11
+ return { provided: false };
12
+ }
13
+ const rawApiKey = value.apiKey;
14
+ if (rawApiKey !== null && typeof rawApiKey !== "string") {
15
+ throw createAppError(400, "INVALID_EMBEDDING_API_KEY", "Embedding API key must be a string or null.");
16
+ }
17
+ try {
18
+ return {
19
+ provided: true,
20
+ apiKey: rawApiKey === null ? void 0 : normalizeEmbeddingApiKey(rawApiKey)
21
+ };
22
+ } catch (error) {
23
+ throw createAppError(
24
+ 400,
25
+ "INVALID_EMBEDDING_API_KEY",
26
+ error instanceof Error ? error.message : "Embedding API key is invalid."
27
+ );
28
+ }
29
+ };
30
+ const parseConfig = (value) => {
31
+ if (!value || typeof value !== "object") {
32
+ throw createAppError(400, "INVALID_SEARCH_CONFIG", "Semantic search settings must be an object.");
33
+ }
34
+ const config = value;
35
+ if (typeof config.enabled !== "boolean" || typeof config.baseUrl !== "string" || typeof config.model !== "string" || typeof config.queryInstruction !== "string") {
36
+ throw createAppError(
37
+ 400,
38
+ "INVALID_SEARCH_CONFIG",
39
+ "enabled, baseUrl, model, and queryInstruction are required."
40
+ );
41
+ }
42
+ try {
43
+ return normalizeSemanticSearchConfig(config);
44
+ } catch (error) {
45
+ throw createAppError(
46
+ 400,
47
+ "INVALID_SEARCH_CONFIG",
48
+ error instanceof Error ? error.message : "Semantic search settings are invalid."
49
+ );
50
+ }
51
+ };
52
+ const parseBaseUrl = (value) => {
53
+ if (!value || typeof value !== "object" || typeof value.baseUrl !== "string") {
54
+ throw createAppError(400, "INVALID_EMBEDDING_API_URL", "Embedding API URL is required.");
55
+ }
56
+ const inputBaseUrl = value.baseUrl;
57
+ if (inputBaseUrl.length > 2048) {
58
+ throw createAppError(400, "INVALID_EMBEDDING_API_URL", "Embedding API URL is too long.");
59
+ }
60
+ const baseUrl = stripTrailingSlashes(inputBaseUrl.trim());
61
+ try {
62
+ normalizeEmbeddingModelsUrl(baseUrl);
63
+ } catch (error) {
64
+ throw createAppError(
65
+ 400,
66
+ "INVALID_EMBEDDING_API_URL",
67
+ error instanceof Error ? error.message : "Embedding API URL is invalid."
68
+ );
69
+ }
70
+ return baseUrl;
71
+ };
72
+ const createSearchAdminStatusHandler = (manager = getDefaultSemanticSearchManager()) => {
73
+ return async (_req, res) => {
74
+ res.status(200).json(await manager.getStatus()).end();
75
+ };
76
+ };
77
+ const createSearchAdminSaveConfigHandler = (manager = getDefaultSemanticSearchManager()) => {
78
+ return async (req, res) => {
79
+ const status = await manager.saveConfig(parseConfig(req.body), parseApiKeyInput(req.body));
80
+ res.status(200).json(status).end();
81
+ };
82
+ };
83
+ const createSearchAdminTestConnectionHandler = (manager = getDefaultSemanticSearchManager()) => {
84
+ return async (req, res) => {
85
+ const config = parseConfig({ ...req.body, enabled: true });
86
+ const result = await manager.testConnection(config, parseApiKeyInput(req.body));
87
+ res.status(200).json(result).end();
88
+ };
89
+ };
90
+ const createSearchAdminListModelsHandler = (listModels = (baseUrl, apiKeyInput) => getDefaultSemanticSearchManager().listModels(baseUrl, apiKeyInput)) => {
91
+ return async (req, res) => {
92
+ const models = await listModels(parseBaseUrl(req.body), parseApiKeyInput(req.body));
93
+ res.status(200).json({ models }).end();
94
+ };
95
+ };
96
+ const createSearchAdminReindexHandler = (manager = getDefaultSemanticSearchManager()) => {
97
+ return async (_req, res) => {
98
+ const result = await manager.startReindex();
99
+ res.status(result.started ? 202 : 200).json(result).end();
100
+ };
101
+ };
102
+ export {
103
+ createSearchAdminListModelsHandler,
104
+ createSearchAdminReindexHandler,
105
+ createSearchAdminSaveConfigHandler,
106
+ createSearchAdminStatusHandler,
107
+ createSearchAdminTestConnectionHandler
108
+ };
@@ -0,0 +1,57 @@
1
+ const DEFAULT_LEXICAL_WEIGHT = 1;
2
+ const DEFAULT_SEMANTIC_WEIGHT = 1.25;
3
+ const DEFAULT_RANK_CONSTANT = 60;
4
+ const uniqueNoteIds = (noteIds) => {
5
+ return [...new Set(noteIds.filter((noteId) => Number.isInteger(noteId) && noteId > 0))];
6
+ };
7
+ const fuseHybridSearchRanks = ({
8
+ lexicalNoteIds,
9
+ semanticNoteIds,
10
+ lexicalWeight = DEFAULT_LEXICAL_WEIGHT,
11
+ semanticWeight = DEFAULT_SEMANTIC_WEIGHT,
12
+ rankConstant = DEFAULT_RANK_CONSTANT
13
+ }) => {
14
+ const lexicalIds = uniqueNoteIds(lexicalNoteIds);
15
+ const semanticIds = uniqueNoteIds(semanticNoteIds);
16
+ const candidates = /* @__PURE__ */ new Map();
17
+ const addRankedIds = (noteIds, source, weight) => {
18
+ noteIds.forEach((noteId, index) => {
19
+ const rank = index + 1;
20
+ const candidate = candidates.get(noteId) ?? {
21
+ noteId,
22
+ lexicalRank: null,
23
+ semanticRank: null,
24
+ score: 0
25
+ };
26
+ candidate.score += weight / (rankConstant + rank);
27
+ if (source === "lexical") {
28
+ candidate.lexicalRank = rank;
29
+ } else {
30
+ candidate.semanticRank = rank;
31
+ }
32
+ candidates.set(noteId, candidate);
33
+ });
34
+ };
35
+ addRankedIds(lexicalIds, "lexical", lexicalWeight);
36
+ addRankedIds(semanticIds, "semantic", semanticWeight);
37
+ return [...candidates.values()].sort((left, right) => {
38
+ if (left.score !== right.score) {
39
+ return right.score - left.score;
40
+ }
41
+ const leftBestRank = Math.min(left.lexicalRank ?? Number.POSITIVE_INFINITY, left.semanticRank ?? Infinity);
42
+ const rightBestRank = Math.min(right.lexicalRank ?? Number.POSITIVE_INFINITY, right.semanticRank ?? Infinity);
43
+ if (leftBestRank !== rightBestRank) {
44
+ return leftBestRank - rightBestRank;
45
+ }
46
+ if (left.lexicalRank !== null && right.lexicalRank === null) {
47
+ return -1;
48
+ }
49
+ if (left.lexicalRank === null && right.lexicalRank !== null) {
50
+ return 1;
51
+ }
52
+ return left.noteId - right.noteId;
53
+ });
54
+ };
55
+ export {
56
+ fuseHybridSearchRanks
57
+ };