ocean-brain 0.8.2 → 0.10.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 (232) hide show
  1. package/README.md +2 -0
  2. package/dist/mcp.js +47 -75
  3. package/package.json +3 -2
  4. package/server/client/dist/assets/{ArrowRight.es-g2YZTklZ.js → ArrowRight.es-Bm-ZXpEC.js} +1 -1
  5. package/server/client/dist/assets/Calendar-BsNs3NzO.js +25 -0
  6. package/server/client/dist/assets/{Callout-B_IPSF_5.js → Callout-CviyPHKO.js} +1 -1
  7. package/server/client/dist/assets/{Copy.es-CNxe4ujr.js → Copy.es-BPreRnP9.js} +1 -1
  8. package/server/client/dist/assets/{Graph-D9GeO71k.js → Graph-vXQQ0a-Q.js} +1 -1
  9. package/server/client/dist/assets/Graph-yPE3ljW0.js +46 -0
  10. package/server/client/dist/assets/{Image.es-DnON8cv2.js → Image.es-DnR3qc_c.js} +1 -1
  11. package/server/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  12. package/server/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  13. package/server/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  14. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  15. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  16. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  17. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  18. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  19. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  20. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  21. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  22. package/server/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  23. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  24. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  25. package/server/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  26. package/server/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  27. package/server/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  28. package/server/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  29. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  30. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  31. package/server/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  32. package/server/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  33. package/server/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  34. package/server/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  35. package/server/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  36. package/server/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  37. package/server/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  38. package/server/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  39. package/server/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  40. package/server/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  41. package/server/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  42. package/server/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  43. package/server/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  44. package/server/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  45. package/server/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  46. package/server/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  47. package/server/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  48. package/server/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  49. package/server/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  50. package/server/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  51. package/server/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  52. package/server/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  53. package/server/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  54. package/server/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  55. package/server/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  56. package/server/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  57. package/server/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  58. package/server/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  59. package/server/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  60. package/server/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  61. package/server/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  62. package/server/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  63. package/server/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  64. package/server/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  65. package/server/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  66. package/server/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  67. package/server/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  68. package/server/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  69. package/server/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  70. package/server/client/dist/assets/Note-CmreVzvQ.js +25 -0
  71. package/server/client/dist/assets/{NotePropertiesPanel-2DgXEM4B.js → NotePropertiesPanel-DXYgE8h2.js} +2 -2
  72. package/server/client/dist/assets/PencilLine.es-B9V6u64t.js +1 -0
  73. package/server/client/dist/assets/{Plus.es-CQ8wn2is.js → Plus.es-CRRp2u5J.js} +1 -1
  74. package/server/client/dist/assets/Reminders-BOtfNeYD.js +1 -0
  75. package/server/client/dist/assets/Search-C-FB3pKf.js +1 -0
  76. package/server/client/dist/assets/SurfaceCard-GMt5xM7v.js +1 -0
  77. package/server/client/dist/assets/Tag-lBxDRE77.js +1 -0
  78. package/server/client/dist/assets/TagNotes-Bgorp_rC.js +1 -0
  79. package/server/client/dist/assets/{Trash.es-0aPpA-3S.js → Trash.es-svHOb5LN.js} +1 -1
  80. package/server/client/dist/assets/ViewNotes-C7d10VFf.js +1 -0
  81. package/server/client/dist/assets/ViewSectionDialog-Bhw-ILop.js +102 -0
  82. package/server/client/dist/assets/Views-C7V3saUo.js +1 -0
  83. package/server/client/dist/assets/abnfDiagram-VRR7QNED-CWlV5dXw.js +1 -0
  84. package/server/client/dist/assets/app-DQBV36r_.js +50 -0
  85. package/server/client/dist/assets/app-ZgwR5t_I.css +1 -0
  86. package/server/client/dist/assets/arc-BV-n6RUX.js +1 -0
  87. package/server/client/dist/assets/architectureDiagram-ZJ3FMSHR-CekzMXdu.js +36 -0
  88. package/server/client/dist/assets/blockDiagram-677ZJIJ3-B49yHoxq.js +132 -0
  89. package/server/client/dist/assets/c4Diagram-LMCZKHZV-0AfB9NBC.js +10 -0
  90. package/server/client/dist/assets/channel-CigOulR-.js +1 -0
  91. package/server/client/dist/assets/chunk-2Q5K7J3B-DaPQAU1q.js +1 -0
  92. package/server/client/dist/assets/chunk-32BRIVSS-CfL3kZAg.js +1 -0
  93. package/server/client/dist/assets/chunk-5VM5RSS4-DeOwWPE_.js +15 -0
  94. package/server/client/dist/assets/chunk-EX3LRPZG-C3W1oc9y.js +231 -0
  95. package/server/client/dist/assets/chunk-JWPE2WC7-_Oqh79OY.js +1 -0
  96. package/server/client/dist/assets/chunk-MOJQB5TN-DDKNNzI6.js +88 -0
  97. package/server/client/dist/assets/chunk-RYQCIY6F-DCHah7CC.js +1 -0
  98. package/server/client/dist/assets/chunk-V7JOEXUC-uUOIzgxj.js +206 -0
  99. package/server/client/dist/assets/chunk-VR4S4FIN-DDVw2WPs.js +1 -0
  100. package/server/client/dist/assets/chunk-XXDRQBXY-Coo4UFm2.js +1 -0
  101. package/server/client/dist/assets/classDiagram-OUVF2IWQ-D13ISIOW.js +1 -0
  102. package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-D13ISIOW.js +1 -0
  103. package/server/client/dist/assets/colors-Cc3OSVma.js +1 -0
  104. package/server/client/dist/assets/cose-bilkent-JH36ORCC-C3pHmHpD.js +1 -0
  105. package/server/client/dist/assets/cynefin-VYW2F7L2-DU_G4Atc.js +166 -0
  106. package/server/client/dist/assets/cynefinDiagram-TSTJHNR4-DZ6iiJFo.js +62 -0
  107. package/server/client/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  108. package/server/client/dist/assets/dagre-VKFMJZFB-DNELbhwC.js +4 -0
  109. package/server/client/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  110. package/server/client/dist/assets/diagram-FQU43EPY-CMEvZJ61.js +3 -0
  111. package/server/client/dist/assets/diagram-G47NLZAW-BYDtf6j9.js +24 -0
  112. package/server/client/dist/assets/diagram-NH7WQ7WH-CmXfcCQA.js +24 -0
  113. package/server/client/dist/assets/diagram-OA4YK3LP-BflTKJxU.js +30 -0
  114. package/server/client/dist/assets/diagram-WEI45ONY-KW3fKqna.js +41 -0
  115. package/server/client/dist/assets/ebnfDiagram-CCIWWBDH-CbyhY5Nj.js +1 -0
  116. package/server/client/dist/assets/erDiagram-Q63AITRT-CC2eZV8g.js +85 -0
  117. package/server/client/dist/assets/{floating-ui.react-dom-wWLZ1CdM.js → floating-ui.react-dom-BWTnA00G.js} +1 -1
  118. package/server/client/dist/assets/flowDiagram-23GEKE2U-unplo7Qm.js +156 -0
  119. package/server/client/dist/assets/ganttDiagram-NO4QXBWP-BcBK5HcZ.js +292 -0
  120. package/server/client/dist/assets/gitGraphDiagram-IHSO6WYX-DgVbbb5k.js +106 -0
  121. package/server/client/dist/assets/graph-C5DFNLrC.js +1 -0
  122. package/server/client/dist/assets/image-delete-confirmation-D7xrODQV.js +1 -0
  123. package/server/client/dist/assets/image.api-QmHYjp3z.js +17 -0
  124. package/server/client/dist/assets/index-CaF9d-zG.js +1 -0
  125. package/server/client/dist/assets/infoDiagram-FWYZ7A6U-N_D9RdfG.js +2 -0
  126. package/server/client/dist/assets/init-Gi6I4Gst.js +1 -0
  127. package/server/client/dist/assets/isObject-U9U2HXiq.js +1 -0
  128. package/server/client/dist/assets/ishikawaDiagram-FXEZZL3T-BBKrzmB2.js +70 -0
  129. package/server/client/dist/assets/journeyDiagram-5HDEW3XC-NdCxLmQB.js +139 -0
  130. package/server/client/dist/assets/kanban-definition-HUTT4EX6-Ck7t4-tt.js +89 -0
  131. package/server/client/dist/assets/katex-DywYRkvg.css +1 -0
  132. package/server/client/dist/assets/katex-HP8lGamR.js +257 -0
  133. package/server/client/dist/assets/katex-x288kqHd.js +257 -0
  134. package/server/client/dist/assets/layout-Cz0MDlGi.js +1 -0
  135. package/server/client/dist/assets/linear-D6yRgDLq.js +1 -0
  136. package/server/client/dist/assets/manage-image-DCDF8AM9.js +1 -0
  137. package/server/client/dist/assets/manage-image-detail-e0ViRAj-.js +1 -0
  138. package/server/client/dist/assets/map-Mm1V-_Na.js +1 -0
  139. package/server/client/dist/assets/mcp-admin-adapter-D39G3udJ.js +1 -0
  140. package/server/client/dist/assets/{mcp-CT_9IV2c.js → mcp-uFZkdhCi.js} +4 -4
  141. package/server/client/dist/assets/mermaid.core-B14ZAmWA.js +308 -0
  142. package/server/client/dist/assets/min-tv6RD-6E.js +1 -0
  143. package/server/client/dist/assets/mindmap-definition-LN4V7U3C-BTUqSdcP.js +96 -0
  144. package/server/client/dist/assets/{note-core-Buwzyxi7.js → note-core-DUTqUh5N.js} +2 -2
  145. package/server/client/dist/assets/{note-runtime-CPDVOFEo.js → note-runtime-zN5ddTba.js} +1 -1
  146. package/server/client/dist/assets/{note-ui-CHI-tzj-.js → note-ui-CxK8ymUc.js} +1 -1
  147. package/server/client/dist/assets/{note.api-D_00ZzJC.js → note.api-BQovzEzd.js} +6 -6
  148. package/server/client/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  149. package/server/client/dist/assets/pegDiagram-2B236MQR-Cjcu3mVz.js +1 -0
  150. package/server/client/dist/assets/pieDiagram-ENE6RG2P-BxI_c22Z.js +39 -0
  151. package/server/client/dist/assets/{placeholder-DbdGiYGL.js → placeholder-D3b8lZGE.js} +4 -4
  152. package/server/client/dist/assets/properties-M-yWq1D7.js +1 -0
  153. package/server/client/dist/assets/quadrantDiagram-ABIIQ3AL-DGm0a-yR.js +7 -0
  154. package/server/client/dist/assets/railroadDiagram-RFXS5EU6-DmCDu89O.js +1 -0
  155. package/server/client/dist/assets/requirementDiagram-TGXJPOKE-CBpUt7Ig.js +84 -0
  156. package/server/client/dist/assets/route-preload.js +2 -2
  157. package/server/client/dist/assets/sankeyDiagram-HTMAVEWB-BjGnDZlp.js +40 -0
  158. package/server/client/dist/assets/search-DFnzZKLd.js +1 -0
  159. package/server/client/dist/assets/sequenceDiagram-DBY2YBRQ-B7rpCfS6.js +162 -0
  160. package/server/client/dist/assets/sizeCapture-X5ZJPWSS-CUJhD0uW.js +1 -0
  161. package/server/client/dist/assets/stateDiagram-2N3HPSRC-Y9-3XqRX.js +1 -0
  162. package/server/client/dist/assets/stateDiagram-v2-6OUMAXLB-DbHXhT02.js +1 -0
  163. package/server/client/dist/assets/swimlanes-5IMT3BWC-Cs1NO9ng.js +2 -0
  164. package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-DQnrq2ZC.js +8 -0
  165. package/server/client/dist/assets/{tag.api-CX0SE06J.js → tag.api-DUfjr8bg.js} +3 -3
  166. package/server/client/dist/assets/time-CfAYZipR.js +9 -0
  167. package/server/client/dist/assets/timeline-definition-FHXFAJF6-8fqD4WNL.js +120 -0
  168. package/server/client/dist/assets/toNumber-DE3J3kMD.js +1 -0
  169. package/server/client/dist/assets/transform-BUB79_za.js +1 -0
  170. package/server/client/dist/assets/trash-CClKfP1x.js +1 -0
  171. package/server/client/dist/assets/url-9nrbQs7Z.js +1 -0
  172. package/server/client/dist/assets/{useReminderMutate-CLK1tHvT.js → useReminderMutate--omDLmBV.js} +4 -4
  173. package/server/client/dist/assets/vennDiagram-L72KCM5P-BPqKkd5a.js +34 -0
  174. package/server/client/dist/assets/wardleyDiagram-EHGQE667-Cq11kU2M.js +78 -0
  175. package/server/client/dist/assets/xychartDiagram-FW5EYKEG-DJ6UzFj-.js +7 -0
  176. package/server/client/dist/index.html +7 -7
  177. package/server/dist/features/image/services/delete.js +25 -1
  178. package/server/dist/features/note/graphql/note.mutation.resolver.js +38 -12
  179. package/server/dist/features/note/graphql/note.type-defs.js +1 -1
  180. package/server/dist/features/note/http/mcp.js +1 -60
  181. package/server/dist/features/note/services/authoring.js +2 -70
  182. package/server/dist/features/note/services/markdown-patch.js +80 -8
  183. package/server/dist/features/note/services/properties.js +21 -1
  184. package/server/dist/features/note/services/property-restore.js +26 -0
  185. package/server/dist/features/note/services/snapshot.js +79 -59
  186. package/server/dist/features/note/services/trash.js +16 -31
  187. package/server/dist/features/search/embedding-api-key-store.js +57 -0
  188. package/server/dist/features/search/embedding-client.js +179 -0
  189. package/server/dist/features/search/graphql/index.js +6 -0
  190. package/server/dist/features/search/graphql/resolver.js +20 -0
  191. package/server/dist/features/search/graphql/type-defs.js +30 -0
  192. package/server/dist/features/search/http/handlers.js +108 -0
  193. package/server/dist/features/search/hybrid-ranking.js +55 -0
  194. package/server/dist/features/search/hybrid-search.js +174 -0
  195. package/server/dist/features/search/note-change.js +16 -0
  196. package/server/dist/features/search/note-chunking.js +75 -0
  197. package/server/dist/features/search/search-config.js +150 -0
  198. package/server/dist/features/search/search-manager.js +431 -0
  199. package/server/dist/features/search/semantic-indexer.js +138 -0
  200. package/server/dist/features/search/sqlite-vector-index.js +636 -0
  201. package/server/dist/features/search/url-normalization.js +10 -0
  202. package/server/dist/features/tag/services/organization.js +3 -0
  203. package/server/dist/features/view/services/workspace.js +1 -1
  204. package/server/dist/main.js +2 -0
  205. package/server/dist/modules/blocknote.js +4 -5
  206. package/server/dist/paths.js +7 -2
  207. package/server/dist/routes/api.js +37 -0
  208. package/server/dist/routes/mcp.js +0 -5
  209. package/server/dist/schema/index.js +3 -0
  210. package/server/client/dist/assets/Calendar-BP6HTuSn.js +0 -25
  211. package/server/client/dist/assets/Graph-Xha3fwhp.js +0 -46
  212. package/server/client/dist/assets/Image-BLEjLUqd.js +0 -1
  213. package/server/client/dist/assets/Note-DjrCYtat.js +0 -24
  214. package/server/client/dist/assets/Reminders-B4l3KdlK.js +0 -1
  215. package/server/client/dist/assets/Search-BN5sf7bE.js +0 -1
  216. package/server/client/dist/assets/SurfaceCard-BZm73nyS.js +0 -1
  217. package/server/client/dist/assets/Tag-oSnmViVp.js +0 -1
  218. package/server/client/dist/assets/TagNotes-BDTCcJcL.js +0 -1
  219. package/server/client/dist/assets/ViewNotes-CiZtGJNE.js +0 -1
  220. package/server/client/dist/assets/ViewSectionDialog-Ctl_694Y.js +0 -102
  221. package/server/client/dist/assets/Views-D-zlatYD.js +0 -1
  222. package/server/client/dist/assets/app-DmqIOMWF.css +0 -1
  223. package/server/client/dist/assets/app-cTRcKMGc.js +0 -26
  224. package/server/client/dist/assets/image.api-CVuuvTBk.js +0 -17
  225. package/server/client/dist/assets/index-DKJChPr4.js +0 -1
  226. package/server/client/dist/assets/manage-image-BZjaxp-f.js +0 -1
  227. package/server/client/dist/assets/manage-image-detail-DPv0FQ2m.js +0 -1
  228. package/server/client/dist/assets/mcp-admin-adapter-xAuyZc_M.js +0 -1
  229. package/server/client/dist/assets/properties-D447wsWk.js +0 -1
  230. package/server/client/dist/assets/time-B3pt8vt4.js +0 -9
  231. package/server/client/dist/assets/trash-8sH7EZgc.js +0 -1
  232. package/server/client/dist/assets/url-BSnWpmJx.js +0 -1
@@ -21,6 +21,66 @@ const getLineRange = (markdown, match) => {
21
21
  lineEnd
22
22
  };
23
23
  };
24
+ const MARKDOWN_PATCH_WHITESPACE_PATTERN = /\p{Zs}| |&#(?:x[0-9a-f]+|\d+);/giu;
25
+ const SPACE_SEPARATOR_PATTERN = /^\p{Zs}$/u;
26
+ const normalizePatchWhitespaceToken = (value) => {
27
+ if (!value.startsWith("&")) {
28
+ return " ";
29
+ }
30
+ if (/^ $/i.test(value)) {
31
+ return " ";
32
+ }
33
+ const numericValue = value.slice(2, -1);
34
+ const isHexadecimal = numericValue.toLowerCase().startsWith("x");
35
+ const codePoint = Number.parseInt(isHexadecimal ? numericValue.slice(1) : numericValue, isHexadecimal ? 16 : 10);
36
+ if (!Number.isSafeInteger(codePoint) || codePoint < 0 || codePoint > 1114111) {
37
+ return value;
38
+ }
39
+ return SPACE_SEPARATOR_PATTERN.test(String.fromCodePoint(codePoint)) ? " " : value;
40
+ };
41
+ const normalizeMarkdownPatchText = (value) => {
42
+ let text = "";
43
+ const rawStarts = [];
44
+ const rawEnds = [];
45
+ const appendText = (appendValue, rawStart, rawEnd) => {
46
+ text += appendValue;
47
+ for (let index = 0; index < appendValue.length; index += 1) {
48
+ rawStarts.push(rawStart);
49
+ rawEnds.push(rawEnd);
50
+ }
51
+ };
52
+ const appendRawRange = (start, end) => {
53
+ for (let index = start; index < end; ) {
54
+ const codePoint = value.codePointAt(index);
55
+ if (codePoint === void 0) {
56
+ break;
57
+ }
58
+ const character = String.fromCodePoint(codePoint);
59
+ appendText(character, index, index + character.length);
60
+ index += character.length;
61
+ }
62
+ };
63
+ let sourceCursor = 0;
64
+ for (const match of value.matchAll(MARKDOWN_PATCH_WHITESPACE_PATTERN)) {
65
+ const start = match.index;
66
+ const token = match[0];
67
+ const end = start + token.length;
68
+ appendRawRange(sourceCursor, start);
69
+ const normalizedToken = normalizePatchWhitespaceToken(token);
70
+ if (normalizedToken === token) {
71
+ appendRawRange(start, end);
72
+ } else {
73
+ appendText(normalizedToken, start, end);
74
+ }
75
+ sourceCursor = end;
76
+ }
77
+ appendRawRange(sourceCursor, value.length);
78
+ return {
79
+ text,
80
+ rawStarts,
81
+ rawEnds
82
+ };
83
+ };
24
84
  const buildExcerpt = (markdown, start, end) => {
25
85
  const excerpt = markdown.slice(start, end).trim();
26
86
  return excerpt.length > 0 ? excerpt : void 0;
@@ -46,19 +106,27 @@ const toPatchMatch = (markdown, match, totalMatches) => {
46
106
  };
47
107
  const findExactTextMatches = (markdown, text) => {
48
108
  const matches = [];
109
+ const normalizedMarkdown = normalizeMarkdownPatchText(markdown);
110
+ const normalizedText = normalizeMarkdownPatchText(text).text;
49
111
  let searchStart = 0;
50
- while (searchStart <= markdown.length) {
51
- const start = markdown.indexOf(text, searchStart);
112
+ while (searchStart <= normalizedMarkdown.text.length) {
113
+ const start = normalizedMarkdown.text.indexOf(normalizedText, searchStart);
52
114
  if (start === -1) {
53
115
  break;
54
116
  }
117
+ const end = start + normalizedText.length;
118
+ const rawStart = normalizedMarkdown.rawStarts[start];
119
+ const rawEnd = normalizedMarkdown.rawEnds[end - 1];
120
+ if (rawStart === void 0 || rawEnd === void 0) {
121
+ break;
122
+ }
55
123
  matches.push({
56
124
  matchIndex: matches.length,
57
- start,
58
- end: start + text.length,
59
- text
125
+ start: rawStart,
126
+ end: rawEnd,
127
+ text: markdown.slice(rawStart, rawEnd)
60
128
  });
61
- searchStart = start + text.length;
129
+ searchStart = end;
62
130
  }
63
131
  return matches;
64
132
  };
@@ -238,10 +306,14 @@ const createChangePlan = ({
238
306
  };
239
307
  };
240
308
  const matchHasAnchors = (markdown, match, selector) => {
241
- if (selector.before !== void 0 && !markdown.slice(0, match.start).endsWith(selector.before)) {
309
+ if (selector.before !== void 0 && !normalizeMarkdownPatchText(markdown.slice(0, match.start)).text.endsWith(
310
+ normalizeMarkdownPatchText(selector.before).text
311
+ )) {
242
312
  return false;
243
313
  }
244
- if (selector.after !== void 0 && !markdown.slice(match.end).startsWith(selector.after)) {
314
+ if (selector.after !== void 0 && !normalizeMarkdownPatchText(markdown.slice(match.end)).text.startsWith(
315
+ normalizeMarkdownPatchText(selector.after).text
316
+ )) {
245
317
  return false;
246
318
  }
247
319
  return true;
@@ -329,6 +329,13 @@ const findReferencedRemovedPropertyOptionValue = ({
329
329
  }
330
330
  return null;
331
331
  };
332
+ const viewQueryReferencesProperty = ({ query, key }) => {
333
+ const parsed = parseViewQueryRecord(query);
334
+ if (!parsed || !Array.isArray(parsed.propertyFilters)) {
335
+ return false;
336
+ }
337
+ return parsed.propertyFilters.some((filter) => isRecord(filter) && getNormalizedViewFilterKey(filter) === key);
338
+ };
332
339
  const buildTypedValueData = async (input, definitionId, tx = models) => {
333
340
  const resetValues = {
334
341
  textValue: null,
@@ -695,6 +702,18 @@ const deleteNotePropertyDefinition = async ({
695
702
  if (!definition) {
696
703
  return null;
697
704
  }
705
+ const referencingViewSections = await tx.viewSection.findMany({
706
+ where: { query: { contains: definition.key } },
707
+ select: { title: true, query: true }
708
+ });
709
+ const referencingView = referencingViewSections.find(
710
+ (section) => viewQueryReferencesProperty({ query: section.query, key: definition.key })
711
+ );
712
+ if (referencingView) {
713
+ throw new InvalidNotePropertyInputError(
714
+ `Property ${definition.key} is used by view ${referencingView.title} and cannot be deleted.`
715
+ );
716
+ }
698
717
  const affectedNoteCount = definition._count.properties;
699
718
  if (affectedNoteCount > 0 && !confirmImpact) {
700
719
  throw new NotePropertyDeleteConfirmationRequiredError(normalizedKey, affectedNoteCount);
@@ -869,5 +888,6 @@ export {
869
888
  updateNotePropertiesWithVersionGuard,
870
889
  updateNotePropertiesWithVersionGuardAndSnapshot,
871
890
  updateNotePropertyDefinition,
872
- validateNotePropertiesPatchValues
891
+ validateNotePropertiesPatchValues,
892
+ viewQueryReferencesProperty
873
893
  };
@@ -0,0 +1,26 @@
1
+ const resolveRestoredPropertyTarget = async (property, deps) => {
2
+ const definition = await deps.findDefinitionByKey(property.key);
3
+ if (!definition || definition.valueType !== property.valueType) {
4
+ return null;
5
+ }
6
+ if (property.valueType !== "select") {
7
+ return {
8
+ propertyDefinitionId: definition.id,
9
+ optionId: null
10
+ };
11
+ }
12
+ if (!property.optionValue) {
13
+ return null;
14
+ }
15
+ const option = await deps.findOptionByValue(definition.id, property.optionValue);
16
+ if (!option) {
17
+ return null;
18
+ }
19
+ return {
20
+ propertyDefinitionId: definition.id,
21
+ optionId: option.id
22
+ };
23
+ };
24
+ export {
25
+ resolveRestoredPropertyTarget
26
+ };
@@ -1,6 +1,6 @@
1
1
  import { FILE_HEADERS_ONLY, formatPatch, structuredPatch } from "diff";
2
2
  import { ensureTagByName } from "../../../features/tag/services/organization.js";
3
- import models from "../../../models.js";
3
+ import models, { Prisma } from "../../../models.js";
4
4
  import { blocksToMarkdown, extractTagIdsFromContentJson } from "../../../modules/blocknote.js";
5
5
  import { parseBlockNoteContent, walkBlockNoteTree } from "../../../modules/blocknote-tree.js";
6
6
  import {
@@ -10,7 +10,9 @@ import {
10
10
  SNAPSHOT_RETENTION_DAYS
11
11
  } from "../../../modules/recovery-retention.js";
12
12
  import { calculateMarkdownSha256 } from "./markdown-patch.js";
13
+ import { resolveRestoredPropertyTarget } from "./property-restore.js";
13
14
  import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
15
+ import { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from "./write-conflict.js";
14
16
  const SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;
15
17
  const parseMeta = (value) => {
16
18
  if (!value) {
@@ -384,6 +386,19 @@ const createNoteSnapshotService = (deps) => ({
384
386
  if (!note) {
385
387
  return null;
386
388
  }
389
+ if (!note.updatedAt) {
390
+ throw new Error("Note update time is required to restore a snapshot.");
391
+ }
392
+ const expectedTimestamp = parseNoteVersion(options.expectedUpdatedAt);
393
+ if (expectedTimestamp === null) {
394
+ throw new MissingNoteVersionError();
395
+ }
396
+ if (note.updatedAt.getTime() !== expectedTimestamp) {
397
+ throw createNoteVersionConflictError({
398
+ expectedUpdatedAt: expectedTimestamp,
399
+ currentUpdatedAt: note.updatedAt.getTime()
400
+ });
401
+ }
387
402
  const payload = parsePayload(snapshot.payload);
388
403
  const resolvedTags = deps.resolveRestoredTags ? await deps.resolveRestoredTags(payload.content) : extractRestoredTagIds(payload.content);
389
404
  const restoredPayload = resolvedTags && !Array.isArray(resolvedTags) ? {
@@ -403,7 +418,8 @@ const createNoteSnapshotService = (deps) => ({
403
418
  }
404
419
  const restoredNote = await deps.updateNote(snapshot.noteId, {
405
420
  ...restoredPayload,
406
- ...restoredTagIds !== void 0 ? { tagIds: restoredTagIds } : {}
421
+ ...restoredTagIds !== void 0 ? { tagIds: restoredTagIds } : {},
422
+ expectedUpdatedAt: options.expectedUpdatedAt
407
423
  });
408
424
  await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);
409
425
  return restoredNote;
@@ -527,68 +543,72 @@ const defaultNoteSnapshotService = createNoteSnapshotService({
527
543
  });
528
544
  },
529
545
  updateNote: async (id, input) => {
530
- const { tagIds, properties, ...noteInput } = input;
531
- return models.$transaction(async (tx) => {
532
- const note = await tx.note.update({
533
- where: { id },
534
- data: {
535
- ...noteInput,
536
- ...buildNoteSearchProjection({
537
- title: noteInput.title,
538
- content: noteInput.content
539
- }),
540
- ...tagIds !== void 0 ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}
541
- }
542
- });
543
- if (properties !== void 0) {
544
- await tx.noteProperty.deleteMany({ where: { noteId: id } });
545
- for (const property of properties) {
546
- const definition = await tx.propertyDefinition.upsert({
547
- where: { key: property.key },
548
- create: {
549
- key: property.key,
550
- name: property.name,
551
- valueType: property.valueType
552
- },
553
- update: {
554
- name: property.name,
555
- valueType: property.valueType
546
+ const { tagIds, properties, expectedUpdatedAt, ...noteInput } = input;
547
+ const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);
548
+ if (expectedTimestamp === null) {
549
+ throw new MissingNoteVersionError();
550
+ }
551
+ try {
552
+ return await models.$transaction(async (tx) => {
553
+ const note = await tx.note.update({
554
+ where: { id, updatedAt: new Date(expectedTimestamp) },
555
+ data: {
556
+ ...noteInput,
557
+ ...buildNoteSearchProjection({
558
+ title: noteInput.title,
559
+ content: noteInput.content
560
+ }),
561
+ ...tagIds !== void 0 ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}
562
+ }
563
+ });
564
+ if (properties !== void 0) {
565
+ await tx.noteProperty.deleteMany({ where: { noteId: id } });
566
+ for (const property of properties) {
567
+ const target = await resolveRestoredPropertyTarget(property, {
568
+ findDefinitionByKey: (key) => tx.propertyDefinition.findUnique({ where: { key } }),
569
+ findOptionByValue: (propertyDefinitionId, value) => tx.propertyOption.findUnique({
570
+ where: {
571
+ propertyDefinitionId_value: {
572
+ propertyDefinitionId,
573
+ value
574
+ }
575
+ }
576
+ })
577
+ });
578
+ if (!target) {
579
+ continue;
556
580
  }
557
- });
558
- const option = property.valueType === "select" && property.optionValue ? await tx.propertyOption.upsert({
559
- where: {
560
- propertyDefinitionId_value: {
561
- propertyDefinitionId: definition.id,
562
- value: property.optionValue
581
+ await tx.noteProperty.create({
582
+ data: {
583
+ noteId: id,
584
+ propertyDefinitionId: target.propertyDefinitionId,
585
+ optionId: target.optionId,
586
+ textValue: property.textValue ?? null,
587
+ textValueNormalized: property.textValueNormalized ?? null,
588
+ numberValue: property.numberValue ?? null,
589
+ dateValue: property.dateValue ? new Date(property.dateValue) : null,
590
+ boolValue: property.boolValue ?? null
563
591
  }
564
- },
565
- create: {
566
- propertyDefinitionId: definition.id,
567
- value: property.optionValue,
568
- label: property.optionLabel ?? property.optionValue,
569
- color: property.optionColor ?? null
570
- },
571
- update: {
572
- label: property.optionLabel ?? property.optionValue,
573
- color: property.optionColor ?? null
574
- }
575
- }) : null;
576
- await tx.noteProperty.create({
577
- data: {
578
- noteId: id,
579
- propertyDefinitionId: definition.id,
580
- optionId: option?.id ?? null,
581
- textValue: property.textValue ?? null,
582
- textValueNormalized: property.textValueNormalized ?? null,
583
- numberValue: property.numberValue ?? null,
584
- dateValue: property.dateValue ? new Date(property.dateValue) : null,
585
- boolValue: property.boolValue ?? null
586
- }
592
+ });
593
+ }
594
+ }
595
+ return note;
596
+ });
597
+ } catch (error) {
598
+ if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025") {
599
+ const currentNote = await models.note.findUnique({
600
+ where: { id },
601
+ select: { updatedAt: true }
602
+ });
603
+ if (currentNote && currentNote.updatedAt.getTime() !== expectedTimestamp) {
604
+ throw createNoteVersionConflictError({
605
+ expectedUpdatedAt: expectedTimestamp,
606
+ currentUpdatedAt: currentNote.updatedAt.getTime()
587
607
  });
588
608
  }
589
609
  }
590
- return note;
591
- });
610
+ throw error;
611
+ }
592
612
  }
593
613
  });
594
614
  const captureNoteBaseline = async (input) => {
@@ -4,6 +4,7 @@ import {
4
4
  RECOVERY_CLEANUP_BATCH_LIMIT,
5
5
  TRASH_RETENTION_DAYS
6
6
  } from "../../../modules/recovery-retention.js";
7
+ import { resolveRestoredPropertyTarget } from "./property-restore.js";
7
8
  import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
8
9
  const TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH = 240;
9
10
  class NoteRestoreConflictError extends Error {
@@ -289,41 +290,25 @@ const noteTrashService = createNoteTrashService({
289
290
  }
290
291
  });
291
292
  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
293
+ const target = await resolveRestoredPropertyTarget(property, {
294
+ findDefinitionByKey: (key) => tx.propertyDefinition.findUnique({ where: { key } }),
295
+ findOptionByValue: (propertyDefinitionId, value) => tx.propertyOption.findUnique({
296
+ where: {
297
+ propertyDefinitionId_value: {
298
+ propertyDefinitionId,
299
+ value
300
+ }
309
301
  }
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;
302
+ })
303
+ });
304
+ if (!target) {
305
+ continue;
306
+ }
322
307
  await tx.noteProperty.create({
323
308
  data: {
324
309
  noteId: note.id,
325
- propertyDefinitionId: definition.id,
326
- optionId: option?.id ?? null,
310
+ propertyDefinitionId: target.propertyDefinitionId,
311
+ optionId: target.optionId,
327
312
  textValue: property.textValue,
328
313
  textValueNormalized: property.textValueNormalized,
329
314
  numberValue: property.numberValue,
@@ -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
+ };