ocean-brain 0.3.4 → 0.4.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 (253) hide show
  1. package/package.json +5 -5
  2. package/scripts/postinstall-prisma.mjs +3 -2
  3. package/server/client/dist/assets/Calendar-CSXEuWmQ.js +25 -0
  4. package/server/client/dist/assets/Callout-Ap1OcxWN.js +1 -0
  5. package/server/client/dist/assets/Graph-C0Ax9qjI.js +1 -0
  6. package/server/client/dist/assets/{Image-ZxF459sC.js → Image-CSbcG-5a.js} +1 -1
  7. package/server/client/dist/assets/{Image.es-BQfyKc2-.js → Image.es-Fbe7aDDo.js} +1 -1
  8. package/server/client/dist/assets/{ModalActionRow-DyOb58KJ.js → ModalActionRow-DjI5b4bl.js} +1 -1
  9. package/server/client/dist/assets/Note-BY6WgYLq.js +1 -0
  10. package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
  11. package/server/client/dist/assets/Reminders-vRksvSQE.js +1 -0
  12. package/server/client/dist/assets/Search-Cq4tuiJO.js +1 -0
  13. package/server/client/dist/assets/{SurfaceCard-nZNf9lyE.js → SurfaceCard-Br6TjX5W.js} +1 -1
  14. package/server/client/dist/assets/Tag-ARDLA-Pb.js +1 -0
  15. package/server/client/dist/assets/TagNotes-CDGVrCJU.js +1 -0
  16. package/server/client/dist/assets/{Trash.es-BGoN5q9L.js → Trash.es-BfCEiL3-.js} +1 -1
  17. package/server/client/dist/assets/ViewNotes-ZBOQ1adH.js +1 -0
  18. package/server/client/dist/assets/Views-DQHPfdwM.js +1 -0
  19. package/server/client/dist/assets/{image.api-0mMkElZk.js → image.api-DGXlQ_Ca.js} +1 -1
  20. package/server/client/dist/assets/index-B28TO4FC.js +1 -0
  21. package/server/client/dist/assets/{index-DL7pUwq2.css → index-Bm9l_L20.css} +1 -1
  22. package/server/client/dist/assets/index-Bsx4P7EA.js +210 -0
  23. package/server/client/dist/assets/manage-image-Dg297A_h.js +1 -0
  24. package/server/client/dist/assets/manage-image-detail-hk6jWyiB.js +1 -0
  25. package/server/client/dist/assets/mcp-CSjmBjFR.js +1 -0
  26. package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
  27. package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-5N5mRkIT.js} +3 -3
  28. package/server/client/dist/assets/{placeholder-i-uNe71U.js → placeholder-Bmiowo28.js} +4 -4
  29. package/server/client/dist/assets/trash-CM7SbVtW.js +1 -0
  30. package/server/client/dist/assets/{useReminderMutate-tNMwwRnx.js → useReminderMutate-DsIvVpCQ.js} +2 -2
  31. package/server/client/dist/assets/view-dashboard-CP8nhfxQ.js +90 -0
  32. package/server/client/dist/index.html +4 -4
  33. package/server/dist/app.js +8 -84
  34. package/server/dist/app.js.map +1 -1
  35. package/server/dist/features/auth/http/api.js +43 -0
  36. package/server/dist/features/auth/http/api.js.map +1 -0
  37. package/server/dist/features/auth/http/login-page.js +28 -0
  38. package/server/dist/features/auth/http/login-page.js.map +1 -0
  39. package/server/dist/features/auth/http/pages.js +50 -0
  40. package/server/dist/features/auth/http/pages.js.map +1 -0
  41. package/server/dist/features/auth/service.js +94 -0
  42. package/server/dist/features/auth/service.js.map +1 -0
  43. package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
  44. package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
  45. package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
  46. package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
  47. package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
  48. package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
  49. package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
  50. package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
  51. package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
  52. package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
  53. package/server/dist/features/cache/graphql/index.js +21 -0
  54. package/server/dist/features/cache/graphql/index.js.map +1 -0
  55. package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
  56. package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
  57. package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
  58. package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
  59. package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
  60. package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
  61. package/server/dist/features/image/graphql/image.resolver.js +12 -0
  62. package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
  63. package/server/dist/features/image/graphql/image.type-defs.js +31 -0
  64. package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
  65. package/server/dist/features/image/graphql/index.js +16 -0
  66. package/server/dist/features/image/graphql/index.js.map +1 -0
  67. package/server/dist/features/image/http/upload.js +48 -0
  68. package/server/dist/features/image/http/upload.js.map +1 -0
  69. package/server/dist/features/image/services/delete.js +52 -0
  70. package/server/dist/features/image/services/delete.js.map +1 -0
  71. package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
  72. package/server/dist/features/image/services/remote-fetch.js.map +1 -0
  73. package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
  74. package/server/dist/features/image/services/upload.js.map +1 -0
  75. package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
  76. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
  77. package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
  78. package/server/dist/features/mcp-admin/service.js.map +1 -0
  79. package/server/dist/features/note/graphql/index.js +8 -0
  80. package/server/dist/features/note/graphql/index.js.map +1 -0
  81. package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
  82. package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
  83. package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
  84. package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
  85. package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
  86. package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
  87. package/server/dist/features/note/graphql/note.query.resolver.js +382 -0
  88. package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
  89. package/server/dist/features/note/graphql/note.resolver.js +14 -0
  90. package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
  91. package/server/dist/features/note/graphql/note.type-defs.js +184 -0
  92. package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
  93. package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
  94. package/server/dist/features/note/http/mcp.js.map +1 -0
  95. package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
  96. package/server/dist/features/note/services/authoring.js.map +1 -0
  97. package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
  98. package/server/dist/features/note/services/cleanup.js.map +1 -0
  99. package/server/dist/features/note/services/search-backfill.js +84 -0
  100. package/server/dist/features/note/services/search-backfill.js.map +1 -0
  101. package/server/dist/features/note/services/search.js +197 -0
  102. package/server/dist/features/note/services/search.js.map +1 -0
  103. package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
  104. package/server/dist/features/note/services/snapshot.js.map +1 -0
  105. package/server/dist/{modules/note-tag-filter.js → features/note/services/tag-filter.js} +2 -6
  106. package/server/dist/features/note/services/tag-filter.js.map +1 -0
  107. package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +28 -23
  108. package/server/dist/features/note/services/trash.js.map +1 -0
  109. package/server/dist/features/placeholder/graphql/index.js +27 -0
  110. package/server/dist/features/placeholder/graphql/index.js.map +1 -0
  111. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
  112. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
  113. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
  114. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
  115. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
  116. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
  117. package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
  118. package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
  119. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
  120. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
  121. package/server/dist/features/reminder/graphql/index.js +27 -0
  122. package/server/dist/features/reminder/graphql/index.js.map +1 -0
  123. package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
  124. package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
  125. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
  126. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
  127. package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
  128. package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
  129. package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
  130. package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
  131. package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
  132. package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
  133. package/server/dist/features/tag/graphql/index.js +14 -0
  134. package/server/dist/features/tag/graphql/index.js.map +1 -0
  135. package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
  136. package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
  137. package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
  138. package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
  139. package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
  140. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
  141. package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
  142. package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
  143. package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
  144. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
  145. package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
  146. package/server/dist/features/tag/http/mcp.js.map +1 -0
  147. package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
  148. package/server/dist/features/tag/services/organization.js.map +1 -0
  149. package/server/dist/features/view/graphql/index.js +11 -0
  150. package/server/dist/features/view/graphql/index.js.map +1 -0
  151. package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
  152. package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
  153. package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
  154. package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
  155. package/server/dist/features/view/graphql/view.resolver.js +10 -0
  156. package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
  157. package/server/dist/features/view/graphql/view.type-defs.js +63 -0
  158. package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
  159. package/server/dist/features/view/services/workspace.js +432 -0
  160. package/server/dist/features/view/services/workspace.js.map +1 -0
  161. package/server/dist/main.js +14 -0
  162. package/server/dist/main.js.map +1 -1
  163. package/server/dist/modules/auth-guard.js +10 -9
  164. package/server/dist/modules/auth-guard.js.map +1 -1
  165. package/server/dist/modules/auth-mode.js +1 -3
  166. package/server/dist/modules/auth-mode.js.map +1 -1
  167. package/server/dist/modules/blocknote.js +8 -17
  168. package/server/dist/modules/blocknote.js.map +1 -1
  169. package/server/dist/modules/data-maintenance.js +75 -0
  170. package/server/dist/modules/data-maintenance.js.map +1 -0
  171. package/server/dist/modules/error-handler.js.map +1 -1
  172. package/server/dist/modules/logger.js +5 -23
  173. package/server/dist/modules/logger.js.map +1 -1
  174. package/server/dist/modules/mcp-auth.js +4 -5
  175. package/server/dist/modules/mcp-auth.js.map +1 -1
  176. package/server/dist/modules/mcp-token.js.map +1 -1
  177. package/server/dist/modules/prisma-runtime.js +2 -7
  178. package/server/dist/modules/prisma-runtime.js.map +1 -1
  179. package/server/dist/modules/recovery-retention.js +1 -1
  180. package/server/dist/modules/recovery-retention.js.map +1 -1
  181. package/server/dist/modules/server-events-handler.js +34 -0
  182. package/server/dist/modules/server-events-handler.js.map +1 -0
  183. package/server/dist/modules/server-events.js +32 -0
  184. package/server/dist/modules/server-events.js.map +1 -0
  185. package/server/dist/modules/use-async.js.map +1 -1
  186. package/server/dist/paths.js.map +1 -1
  187. package/server/dist/routes/api.js +34 -0
  188. package/server/dist/routes/api.js.map +1 -0
  189. package/server/dist/routes/auth-pages.js +11 -0
  190. package/server/dist/routes/auth-pages.js.map +1 -0
  191. package/server/dist/routes/client.js +27 -0
  192. package/server/dist/routes/client.js.map +1 -0
  193. package/server/dist/routes/graphql.js +33 -0
  194. package/server/dist/routes/graphql.js.map +1 -0
  195. package/server/dist/routes/index.js +11 -0
  196. package/server/dist/routes/index.js.map +1 -0
  197. package/server/dist/routes/mcp.js +30 -0
  198. package/server/dist/routes/mcp.js.map +1 -0
  199. package/server/dist/schema/index.js +11 -8
  200. package/server/dist/schema/index.js.map +1 -1
  201. package/server/dist/types/index.js.map +1 -1
  202. package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
  203. package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
  204. package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
  205. package/server/prisma/schema.prisma +66 -12
  206. package/server/client/dist/assets/Calendar-Kjy1PX6L.js +0 -25
  207. package/server/client/dist/assets/Graph-Bl5K2qUw.js +0 -1
  208. package/server/client/dist/assets/Note-BfGma2Qc.css +0 -1
  209. package/server/client/dist/assets/Note-DN_ko2M3.js +0 -1
  210. package/server/client/dist/assets/Reminders-BK-Uv9Ps.js +0 -1
  211. package/server/client/dist/assets/Search-ClHeWvmI.js +0 -1
  212. package/server/client/dist/assets/Tag-Dph4V_kl.js +0 -1
  213. package/server/client/dist/assets/TagNotes-CxP5qHZj.js +0 -1
  214. package/server/client/dist/assets/index-6lIw6jYW.js +0 -1
  215. package/server/client/dist/assets/index-Ba2XPRI8.js +0 -205
  216. package/server/client/dist/assets/manage-image-Br_kKxbb.js +0 -1
  217. package/server/client/dist/assets/manage-image-detail-9DzqbiqI.js +0 -1
  218. package/server/client/dist/assets/mcp-C_So91eX.js +0 -1
  219. package/server/client/dist/assets/trash-Cg3nJ1m5.js +0 -1
  220. package/server/dist/modules/auth.js +0 -25
  221. package/server/dist/modules/auth.js.map +0 -1
  222. package/server/dist/modules/image-upload.js.map +0 -1
  223. package/server/dist/modules/mcp-admin.js.map +0 -1
  224. package/server/dist/modules/note-authoring.js.map +0 -1
  225. package/server/dist/modules/note-cleanup.js.map +0 -1
  226. package/server/dist/modules/note-snapshot.js.map +0 -1
  227. package/server/dist/modules/note-tag-filter.js.map +0 -1
  228. package/server/dist/modules/note-trash.js.map +0 -1
  229. package/server/dist/modules/remote-image.js.map +0 -1
  230. package/server/dist/modules/tag-organization.js.map +0 -1
  231. package/server/dist/schema/cache/index.js +0 -58
  232. package/server/dist/schema/cache/index.js.map +0 -1
  233. package/server/dist/schema/image/index.js +0 -88
  234. package/server/dist/schema/image/index.js.map +0 -1
  235. package/server/dist/schema/note/index.js +0 -582
  236. package/server/dist/schema/note/index.js.map +0 -1
  237. package/server/dist/schema/placeholder/index.js +0 -105
  238. package/server/dist/schema/placeholder/index.js.map +0 -1
  239. package/server/dist/schema/reminder/index.js +0 -161
  240. package/server/dist/schema/reminder/index.js.map +0 -1
  241. package/server/dist/schema/tag/index.js +0 -81
  242. package/server/dist/schema/tag/index.js.map +0 -1
  243. package/server/dist/urls.js +0 -9
  244. package/server/dist/urls.js.map +0 -1
  245. package/server/dist/views/auth.js +0 -305
  246. package/server/dist/views/auth.js.map +0 -1
  247. package/server/dist/views/image.js +0 -44
  248. package/server/dist/views/image.js.map +0 -1
  249. package/server/dist/views/index.js +0 -6
  250. package/server/dist/views/index.js.map +0 -1
  251. package/server/dist/views/mcp-admin.js.map +0 -1
  252. package/server/dist/views/note.js.map +0 -1
  253. package/server/dist/views/tag.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/graphql/note.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { noteFieldResolvers } from './note.field.resolver.js';\nimport { noteMutationResolvers } from './note.mutation.resolver.js';\nimport { createAllNotesQueryResolver, noteQueryResolvers } from './note.query.resolver.js';\n\nexport { createAllNotesQueryResolver } from './note.query.resolver.js';\n\nexport const noteResolvers: IResolvers = {\n Query: noteQueryResolvers,\n Mutation: noteMutationResolvers,\n Note: noteFieldResolvers,\n};\n"],"mappings":"AACA,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,SAAsC,0BAA0B;AAEhE,SAAS,+BAAAA,oCAAmC;AAErC,MAAM,gBAA4B;AAAA,EACrC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AACV;","names":["createAllNotesQueryResolver"]}
@@ -0,0 +1,184 @@
1
+ import { gql } from "../../../modules/graphql.js";
2
+ const noteType = gql`
3
+ input PaginationInput {
4
+ limit: Int!
5
+ offset: Int!
6
+ }
7
+
8
+ input SearchFilterInput {
9
+ query: String!
10
+ sortBy: String
11
+ sortOrder: String
12
+ pinnedFirst: Boolean
13
+ }
14
+
15
+ input DateRangeInput {
16
+ start: String!
17
+ end: String!
18
+ }
19
+
20
+ enum NoteLayout {
21
+ narrow
22
+ wide
23
+ full
24
+ }
25
+
26
+ enum TagMatchMode {
27
+ and
28
+ or
29
+ }
30
+
31
+ input NoteInput {
32
+ title: String
33
+ content: String
34
+ layout: NoteLayout
35
+ }
36
+
37
+ input NoteOrderInput {
38
+ id: ID!
39
+ order: Int!
40
+ }
41
+
42
+ type Tag {
43
+ id: ID!
44
+ name: String!
45
+ createdAt: String!
46
+ updatedAt: String!
47
+ }
48
+
49
+ type Note {
50
+ id: ID!
51
+ title: String!
52
+ content: String!
53
+ contentAsMarkdown: String!
54
+ createdAt: String!
55
+ updatedAt: String!
56
+ pinned: Boolean!
57
+ order: Int!
58
+ layout: NoteLayout!
59
+ tags: [Tag!]!
60
+ }
61
+
62
+ type Notes {
63
+ totalCount: Int!
64
+ notes: [Note!]!
65
+ }
66
+
67
+ type NoteCleanupBackReference {
68
+ id: ID!
69
+ title: String!
70
+ }
71
+
72
+ type NoteCleanupCandidate {
73
+ id: ID!
74
+ title: String!
75
+ updatedAt: String!
76
+ pinned: Boolean!
77
+ tagNames: [String!]!
78
+ reminderCount: Int!
79
+ backReferenceCount: Int!
80
+ matchedTerms: [String!]!
81
+ reasons: [String!]!
82
+ requiresForce: Boolean!
83
+ forceReasons: [String!]!
84
+ }
85
+
86
+ type NoteCleanupPreview {
87
+ id: ID!
88
+ title: String!
89
+ updatedAt: String!
90
+ pinned: Boolean!
91
+ tagNames: [String!]!
92
+ reminderCount: Int!
93
+ backReferences: [NoteCleanupBackReference!]!
94
+ orphanedTagNames: [String!]!
95
+ requiresForce: Boolean!
96
+ forceReasons: [String!]!
97
+ }
98
+
99
+ type NoteSnapshotMeta {
100
+ entrypoint: String
101
+ label: String
102
+ }
103
+
104
+ type NoteSnapshot {
105
+ id: ID!
106
+ title: String!
107
+ createdAt: String!
108
+ meta: NoteSnapshotMeta!
109
+ }
110
+
111
+ type DeletedNote {
112
+ id: ID!
113
+ title: String!
114
+ createdAt: String!
115
+ updatedAt: String!
116
+ deletedAt: String!
117
+ pinned: Boolean!
118
+ order: Int!
119
+ layout: NoteLayout!
120
+ tagNames: [String!]!
121
+ }
122
+
123
+ type DeletedNotes {
124
+ totalCount: Int!
125
+ notes: [DeletedNote!]!
126
+ }
127
+ `;
128
+ const noteQuery = gql`
129
+ type GraphNode {
130
+ id: ID!
131
+ title: String!
132
+ connections: Int!
133
+ }
134
+
135
+ type GraphLink {
136
+ source: ID!
137
+ target: ID!
138
+ }
139
+
140
+ type NoteGraph {
141
+ nodes: [GraphNode!]!
142
+ links: [GraphLink!]!
143
+ }
144
+
145
+ type Query {
146
+ allNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!
147
+ tagNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!
148
+ notesByTagNames(tagNames: [String!]!, mode: TagMatchMode!, pagination: PaginationInput): Notes!
149
+ notesInDateRange(dateRange: DateRangeInput): [Note!]!
150
+ pinnedNotes: [Note!]!
151
+ imageNotes(src: String!): [Note!]!
152
+ backReferences(id: ID!): [Note]!
153
+ note(id: ID!): Note!
154
+ noteCleanupCandidates(query: String, pagination: PaginationInput): [NoteCleanupCandidate!]!
155
+ noteCleanupPreview(id: ID!): NoteCleanupPreview
156
+ noteSnapshots(id: ID!, limit: Int): [NoteSnapshot!]!
157
+ trashedNotes(pagination: PaginationInput): DeletedNotes!
158
+ noteGraph: NoteGraph!
159
+ }
160
+ `;
161
+ const noteMutation = gql`
162
+ type Mutation {
163
+ createNote(note: NoteInput!): Note!
164
+ updateNote(id: ID!, note: NoteInput!, editSessionId: String): Note!
165
+ deleteNote(id: ID!): Boolean!
166
+ restoreNoteSnapshot(id: ID!): Note!
167
+ restoreTrashedNote(id: ID!): Note!
168
+ purgeTrashedNote(id: ID!): Boolean!
169
+ pinNote(id: ID!, pinned: Boolean!): Note!
170
+ reorderNotes(notes: [NoteOrderInput!]!): [Note!]!
171
+ }
172
+ `;
173
+ const noteTypeDefs = `
174
+ ${noteType}
175
+ ${noteQuery}
176
+ ${noteMutation}
177
+ `;
178
+ export {
179
+ noteMutation,
180
+ noteQuery,
181
+ noteType,
182
+ noteTypeDefs
183
+ };
184
+ //# sourceMappingURL=note.type-defs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/graphql/note.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const noteType = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n input SearchFilterInput {\n query: String!\n sortBy: String\n sortOrder: String\n pinnedFirst: Boolean\n }\n\n input DateRangeInput {\n start: String!\n end: String!\n }\n\n enum NoteLayout {\n narrow\n wide\n full\n }\n\n enum TagMatchMode {\n and\n or\n }\n\n input NoteInput {\n title: String\n content: String\n layout: NoteLayout\n }\n\n input NoteOrderInput {\n id: ID!\n order: Int!\n }\n\n type Tag {\n id: ID!\n name: String!\n createdAt: String!\n updatedAt: String!\n }\n\n type Note {\n id: ID!\n title: String!\n content: String!\n contentAsMarkdown: String!\n createdAt: String!\n updatedAt: String!\n pinned: Boolean!\n order: Int!\n layout: NoteLayout!\n tags: [Tag!]!\n }\n\n type Notes {\n totalCount: Int!\n notes: [Note!]!\n }\n\n type NoteCleanupBackReference {\n id: ID!\n title: String!\n }\n\n type NoteCleanupCandidate {\n id: ID!\n title: String!\n updatedAt: String!\n pinned: Boolean!\n tagNames: [String!]!\n reminderCount: Int!\n backReferenceCount: Int!\n matchedTerms: [String!]!\n reasons: [String!]!\n requiresForce: Boolean!\n forceReasons: [String!]!\n }\n\n type NoteCleanupPreview {\n id: ID!\n title: String!\n updatedAt: String!\n pinned: Boolean!\n tagNames: [String!]!\n reminderCount: Int!\n backReferences: [NoteCleanupBackReference!]!\n orphanedTagNames: [String!]!\n requiresForce: Boolean!\n forceReasons: [String!]!\n }\n\n type NoteSnapshotMeta {\n entrypoint: String\n label: String\n }\n\n type NoteSnapshot {\n id: ID!\n title: String!\n createdAt: String!\n meta: NoteSnapshotMeta!\n }\n\n type DeletedNote {\n id: ID!\n title: String!\n createdAt: String!\n updatedAt: String!\n deletedAt: String!\n pinned: Boolean!\n order: Int!\n layout: NoteLayout!\n tagNames: [String!]!\n }\n\n type DeletedNotes {\n totalCount: Int!\n notes: [DeletedNote!]!\n }\n`;\n\nexport const noteQuery = gql`\n type GraphNode {\n id: ID!\n title: String!\n connections: Int!\n }\n\n type GraphLink {\n source: ID!\n target: ID!\n }\n\n type NoteGraph {\n nodes: [GraphNode!]!\n links: [GraphLink!]!\n }\n\n type Query {\n allNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!\n tagNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!\n notesByTagNames(tagNames: [String!]!, mode: TagMatchMode!, pagination: PaginationInput): Notes!\n notesInDateRange(dateRange: DateRangeInput): [Note!]!\n pinnedNotes: [Note!]!\n imageNotes(src: String!): [Note!]!\n backReferences(id: ID!): [Note]!\n note(id: ID!): Note!\n noteCleanupCandidates(query: String, pagination: PaginationInput): [NoteCleanupCandidate!]!\n noteCleanupPreview(id: ID!): NoteCleanupPreview\n noteSnapshots(id: ID!, limit: Int): [NoteSnapshot!]!\n trashedNotes(pagination: PaginationInput): DeletedNotes!\n noteGraph: NoteGraph!\n }\n`;\n\nexport const noteMutation = gql`\n type Mutation {\n createNote(note: NoteInput!): Note!\n updateNote(id: ID!, note: NoteInput!, editSessionId: String): Note!\n deleteNote(id: ID!): Boolean!\n restoreNoteSnapshot(id: ID!): Note!\n restoreTrashedNote(id: ID!): Note!\n purgeTrashedNote(id: ID!): Boolean!\n pinNote(id: ID!, pinned: Boolean!): Note!\n reorderNotes(notes: [NoteOrderInput!]!): [Note!]!\n }\n`;\n\nexport const noteTypeDefs = `\n ${noteType}\n ${noteQuery}\n ${noteMutation}\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+HjB,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkClB,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAarB,MAAM,eAAe;AAAA,MACtB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA;","names":[]}
@@ -1,11 +1,12 @@
1
- import { createAppError } from "../modules/error-handler.js";
2
1
  import {
3
2
  createNoteFromMarkdown,
4
3
  InvalidNoteAuthoringInputError,
5
4
  updateNoteFromMarkdown
6
- } from "../modules/note-authoring.js";
7
- import { deleteNoteById } from "../modules/note-cleanup.js";
8
- import { MCP_SNAPSHOT_META } from "../modules/note-snapshot.js";
5
+ } from "../../../features/note/services/authoring.js";
6
+ import { deleteNoteById } from "../../../features/note/services/cleanup.js";
7
+ import { MCP_SNAPSHOT_META } from "../../../features/note/services/snapshot.js";
8
+ import { createAppError } from "../../../modules/error-handler.js";
9
+ import { emitServerEvent } from "../../../modules/server-events.js";
9
10
  const NOTE_LAYOUTS = /* @__PURE__ */ new Set(["narrow", "wide", "full"]);
10
11
  const resolveNoteLayout = (value) => {
11
12
  if (value === void 0) {
@@ -16,7 +17,7 @@ const resolveNoteLayout = (value) => {
16
17
  }
17
18
  return null;
18
19
  };
19
- const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown) => {
20
+ const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown, emitEvent = emitServerEvent) => {
20
21
  return async (req, res) => {
21
22
  const { title, markdown, layout } = req.body ?? {};
22
23
  const resolvedLayout = resolveNoteLayout(layout);
@@ -35,6 +36,12 @@ const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown) => {
35
36
  ...markdown !== void 0 ? { markdown } : {},
36
37
  ...resolvedLayout ? { layout: resolvedLayout } : {}
37
38
  });
39
+ emitEvent({
40
+ type: "mcp.note.created",
41
+ source: "mcp",
42
+ noteId: note.id,
43
+ updatedAt: note.updatedAt
44
+ });
38
45
  res.status(200).json({
39
46
  created: true,
40
47
  note
@@ -47,15 +54,9 @@ const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown) => {
47
54
  }
48
55
  };
49
56
  };
50
- const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown) => {
57
+ const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown, emitEvent = emitServerEvent) => {
51
58
  return async (req, res) => {
52
- const {
53
- id,
54
- title,
55
- markdown,
56
- layout,
57
- editSessionId
58
- } = req.body ?? {};
59
+ const { id, title, markdown, layout, editSessionId } = req.body ?? {};
59
60
  const noteId = Number(id);
60
61
  const resolvedLayout = resolveNoteLayout(layout);
61
62
  if (!Number.isInteger(noteId) || noteId <= 0) {
@@ -88,6 +89,12 @@ const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown) => {
88
89
  if (!note) {
89
90
  throw createAppError(404, "NOTE_NOT_FOUND", "The requested note was not found.");
90
91
  }
92
+ emitEvent({
93
+ type: "mcp.note.updated",
94
+ source: "mcp",
95
+ noteId: note.id,
96
+ updatedAt: note.updatedAt
97
+ });
91
98
  res.status(200).json({
92
99
  updated: true,
93
100
  note
@@ -100,7 +107,7 @@ const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown) => {
100
107
  }
101
108
  };
102
109
  };
103
- const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById) => {
110
+ const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById, emitEvent = emitServerEvent) => {
104
111
  return async (req, res) => {
105
112
  const id = Number(req.body?.id);
106
113
  if (!Number.isInteger(id) || id <= 0) {
@@ -110,6 +117,11 @@ const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById) => {
110
117
  if (!deletedNote) {
111
118
  throw createAppError(404, "NOTE_NOT_FOUND", "The requested note was not found.");
112
119
  }
120
+ emitEvent({
121
+ type: "mcp.note.deleted",
122
+ source: "mcp",
123
+ noteId: deletedNote.id
124
+ });
113
125
  res.status(200).json({
114
126
  deleted: true,
115
127
  note: deletedNote
@@ -121,4 +133,4 @@ export {
121
133
  createMcpDeleteNoteHandler,
122
134
  createMcpUpdateNoteHandler
123
135
  };
124
- //# sourceMappingURL=note.js.map
136
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/http/mcp.ts"],"sourcesContent":["import {\n createNoteFromMarkdown,\n InvalidNoteAuthoringInputError,\n updateNoteFromMarkdown,\n} from '~/features/note/services/authoring.js';\nimport { deleteNoteById } from '~/features/note/services/cleanup.js';\nimport { MCP_SNAPSHOT_META } from '~/features/note/services/snapshot.js';\nimport type { NoteLayout } from '~/models.js';\nimport { createAppError } from '~/modules/error-handler.js';\nimport { emitServerEvent, type ServerEventInput } from '~/modules/server-events.js';\nimport type { Controller } from '~/types/index.js';\n\nconst NOTE_LAYOUTS = new Set<NoteLayout>(['narrow', 'wide', 'full']);\n\nconst resolveNoteLayout = (value: unknown): NoteLayout | null | undefined => {\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value === 'string' && NOTE_LAYOUTS.has(value as NoteLayout)) {\n return value as NoteLayout;\n }\n\n return null;\n};\n\ntype EmitServerEvent = (event: ServerEventInput) => unknown;\n\nexport const createMcpCreateNoteHandler = (\n createNote = createNoteFromMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { title, markdown, layout } = req.body ?? {};\n const resolvedLayout = resolveNoteLayout(layout);\n\n if (typeof title !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_TITLE', 'A note title is required.');\n }\n\n if (markdown !== undefined && typeof markdown !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_MARKDOWN', 'Note markdown must be a string.');\n }\n\n if (layout !== undefined && resolvedLayout === null) {\n throw createAppError(400, 'INVALID_NOTE_LAYOUT', 'Note layout must be one of narrow, wide, or full.');\n }\n\n try {\n const note = await createNote({\n title,\n ...(markdown !== undefined ? { markdown } : {}),\n ...(resolvedLayout ? { layout: resolvedLayout } : {}),\n });\n\n emitEvent({\n type: 'mcp.note.created',\n source: 'mcp',\n noteId: note.id,\n updatedAt: note.updatedAt,\n });\n\n res.status(200)\n .json({\n created: true,\n note,\n })\n .end();\n } catch (error) {\n if (error instanceof InvalidNoteAuthoringInputError) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', error.message);\n }\n\n throw error;\n }\n };\n};\n\nexport const createMcpUpdateNoteHandler = (\n updateNote = updateNoteFromMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { id, title, markdown, layout, editSessionId } = req.body ?? {};\n const noteId = Number(id);\n const resolvedLayout = resolveNoteLayout(layout);\n\n if (!Number.isInteger(noteId) || noteId <= 0) {\n throw createAppError(400, 'INVALID_NOTE_ID', 'A valid note id is required.');\n }\n\n if (title !== undefined && typeof title !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_TITLE', 'Note title must be a string.');\n }\n\n if (markdown !== undefined && typeof markdown !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_MARKDOWN', 'Note markdown must be a string.');\n }\n\n if (layout !== undefined && resolvedLayout === null) {\n throw createAppError(400, 'INVALID_NOTE_LAYOUT', 'Note layout must be one of narrow, wide, or full.');\n }\n\n if (editSessionId !== undefined && typeof editSessionId !== 'string') {\n throw createAppError(400, 'INVALID_EDIT_SESSION_ID', 'Edit session id must be a string.');\n }\n\n if (title === undefined && markdown === undefined && layout === undefined) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', 'At least one note field must be provided for update.');\n }\n\n try {\n const note = await updateNote({\n id: noteId,\n ...(title !== undefined ? { title } : {}),\n ...(markdown !== undefined ? { markdown } : {}),\n ...(resolvedLayout ? { layout: resolvedLayout } : {}),\n ...(editSessionId !== undefined ? { editSessionId } : {}),\n snapshotMeta: MCP_SNAPSHOT_META,\n });\n\n if (!note) {\n throw createAppError(404, 'NOTE_NOT_FOUND', 'The requested note was not found.');\n }\n\n emitEvent({\n type: 'mcp.note.updated',\n source: 'mcp',\n noteId: note.id,\n updatedAt: note.updatedAt,\n });\n\n res.status(200)\n .json({\n updated: true,\n note,\n })\n .end();\n } catch (error) {\n if (error instanceof InvalidNoteAuthoringInputError) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', error.message);\n }\n\n throw error;\n }\n };\n};\n\nexport const createMcpDeleteNoteHandler = (\n deleteNote = deleteNoteById,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const id = Number(req.body?.id);\n\n if (!Number.isInteger(id) || id <= 0) {\n throw createAppError(400, 'INVALID_NOTE_ID', 'A valid note id is required.');\n }\n\n const deletedNote = await deleteNote(id);\n\n if (!deletedNote) {\n throw createAppError(404, 'NOTE_NOT_FOUND', 'The requested note was not found.');\n }\n\n emitEvent({\n type: 'mcp.note.deleted',\n source: 'mcp',\n noteId: deletedNote.id,\n });\n\n res.status(200)\n .json({\n deleted: true,\n note: deletedNote,\n })\n .end();\n };\n};\n"],"mappings":"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","names":[]}
@@ -1,9 +1,7 @@
1
- import models from "../models.js";
2
- import {
3
- extractTagIdsFromContentJson,
4
- markdownToBlocksJson
5
- } from "./blocknote.js";
6
- import { captureNoteBaseline } from "./note-snapshot.js";
1
+ import models from "../../../models.js";
2
+ import { extractTagIdsFromContentJson, markdownToBlocksJson } from "../../../modules/blocknote.js";
3
+ import { buildNoteSearchProjection } from "./search.js";
4
+ import { captureNoteBaseline } from "./snapshot.js";
7
5
  class InvalidNoteAuthoringInputError extends Error {
8
6
  constructor(message) {
9
7
  super(message);
@@ -103,6 +101,10 @@ const defaultNoteAuthoringService = createNoteAuthoringService({
103
101
  data: {
104
102
  title: input.title,
105
103
  content: input.content,
104
+ ...buildNoteSearchProjection({
105
+ title: input.title,
106
+ content: input.content
107
+ }),
106
108
  ...input.layout ? { layout: input.layout } : {},
107
109
  ...input.tagIds ? { tags: { connect: input.tagIds.map((id) => ({ id: Number(id) })) } } : {}
108
110
  }
@@ -127,12 +129,28 @@ const defaultNoteAuthoringService = createNoteAuthoringService({
127
129
  extractTagIds: extractTagIdsFromContentJson,
128
130
  captureBaseline: captureNoteBaseline,
129
131
  updateNote: async (id, input) => {
132
+ const existingNote = await models.note.findUnique({
133
+ where: { id },
134
+ select: {
135
+ title: true,
136
+ content: true
137
+ }
138
+ });
139
+ if (!existingNote) {
140
+ throw new Error("NOTE_NOT_FOUND");
141
+ }
142
+ const nextTitle = input.title ?? existingNote.title;
143
+ const nextContent = input.content ?? existingNote.content;
130
144
  return models.note.update({
131
145
  where: { id },
132
146
  data: {
133
147
  title: input.title,
134
148
  content: input.content,
135
149
  layout: input.layout,
150
+ ...buildNoteSearchProjection({
151
+ title: nextTitle,
152
+ content: nextContent
153
+ }),
136
154
  ...input.tagIds ? { tags: { set: input.tagIds.map((tagId) => ({ id: Number(tagId) })) } } : {}
137
155
  }
138
156
  });
@@ -150,4 +168,4 @@ export {
150
168
  createNoteFromMarkdown,
151
169
  updateNoteFromMarkdown
152
170
  };
153
- //# sourceMappingURL=note-authoring.js.map
171
+ //# sourceMappingURL=authoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/services/authoring.ts"],"sourcesContent":["import models, { type NoteLayout } from '~/models.js';\nimport { extractTagIdsFromContentJson, markdownToBlocksJson } from '~/modules/blocknote.js';\nimport { buildNoteSearchProjection } from './search.js';\nimport { captureNoteBaseline } from './snapshot.js';\n\ninterface PlaceholderRecord {\n template: string;\n replacement: string;\n}\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n layout: NoteLayout;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface NoteAuthoringDeps {\n createNote: (input: {\n title: string;\n content: string;\n layout?: NoteLayout;\n tagIds?: string[];\n }) => Promise<NoteRecord>;\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findPlaceholders: (templates: string[]) => Promise<PlaceholderRecord[]>;\n parseMarkdownToContentJson: (markdown: string) => Promise<string>;\n extractTagIds: (contentJson: string) => string[];\n captureBaseline: (input: { noteId: number; editSessionId?: string; meta?: string }) => Promise<unknown>;\n updateNote: (\n id: number,\n input: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: string[];\n },\n ) => Promise<NoteRecord>;\n}\n\nexport interface CreateNoteAuthoringInput {\n title: string;\n markdown?: string;\n layout?: NoteLayout;\n}\n\nexport interface UpdateNoteAuthoringInput {\n id: number;\n title?: string;\n markdown?: string;\n layout?: NoteLayout;\n editSessionId?: string;\n snapshotMeta?: string;\n}\n\nexport interface AuthoredNoteSummary {\n id: string;\n title: string;\n layout: NoteLayout;\n createdAt: string;\n updatedAt: string;\n}\n\nexport class InvalidNoteAuthoringInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidNoteAuthoringInputError';\n }\n}\n\nconst PLACEHOLDER_PREFIX = '{%';\nconst PLACEHOLDER_SUFFIX = '%}';\n\nconst serializeNote = (note: NoteRecord): AuthoredNoteSummary => ({\n id: String(note.id),\n title: note.title,\n layout: note.layout,\n createdAt: note.createdAt.toISOString(),\n updatedAt: note.updatedAt.toISOString(),\n});\n\nconst extractPlaceholderTemplates = (value: string) => {\n return Array.from(\n new Set(\n Array.from(\n value.matchAll(new RegExp(`${PLACEHOLDER_PREFIX}([^}]+)${PLACEHOLDER_SUFFIX}`, 'g')),\n (match) => match[1],\n ),\n ),\n );\n};\n\nexport const createNoteAuthoringService = (deps: NoteAuthoringDeps) => {\n const replacePlaceholders = async (value: string) => {\n const templates = extractPlaceholderTemplates(value);\n\n if (templates.length === 0) {\n return value;\n }\n\n const placeholders = await deps.findPlaceholders(templates);\n let replacedValue = value;\n\n for (const placeholder of placeholders) {\n replacedValue = replacedValue.replace(\n new RegExp(`${PLACEHOLDER_PREFIX}${placeholder.template}${PLACEHOLDER_SUFFIX}`, 'g'),\n placeholder.replacement,\n );\n }\n\n return replacedValue;\n };\n\n return {\n createNote: async (input: CreateNoteAuthoringInput): Promise<AuthoredNoteSummary> => {\n const title = input.title.trim();\n\n if (!title) {\n throw new InvalidNoteAuthoringInputError('A note title is required.');\n }\n\n const replacedTitle = await replacePlaceholders(title);\n const replacedMarkdown = await replacePlaceholders(input.markdown ?? '');\n const content = await deps.parseMarkdownToContentJson(replacedMarkdown);\n const tagIds = deps.extractTagIds(content);\n const note = await deps.createNote({\n title: replacedTitle,\n content,\n tagIds,\n ...(input.layout ? { layout: input.layout } : {}),\n });\n\n return serializeNote(note);\n },\n\n updateNote: async (input: UpdateNoteAuthoringInput): Promise<AuthoredNoteSummary | null> => {\n if (input.title === undefined && input.markdown === undefined && input.layout === undefined) {\n throw new InvalidNoteAuthoringInputError('At least one note field must be provided for update.');\n }\n\n const existingNote = await deps.findNoteById(input.id);\n\n if (!existingNote) {\n return null;\n }\n\n const nextData: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: string[];\n } = {};\n\n if (input.title !== undefined) {\n const title = input.title.trim();\n\n if (!title) {\n throw new InvalidNoteAuthoringInputError('A note title is required.');\n }\n\n nextData.title = await replacePlaceholders(title);\n }\n\n if (input.markdown !== undefined) {\n const replacedMarkdown = await replacePlaceholders(input.markdown);\n nextData.content = await deps.parseMarkdownToContentJson(replacedMarkdown);\n nextData.tagIds = deps.extractTagIds(nextData.content);\n }\n\n if (input.layout !== undefined) {\n nextData.layout = input.layout;\n }\n\n await deps.captureBaseline({\n noteId: input.id,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.snapshotMeta ? { meta: input.snapshotMeta } : {}),\n });\n\n const updatedNote = await deps.updateNote(input.id, nextData);\n return serializeNote(updatedNote);\n },\n };\n};\n\nconst defaultNoteAuthoringService = createNoteAuthoringService({\n createNote: async (input) => {\n return models.note.create({\n data: {\n title: input.title,\n content: input.content,\n ...buildNoteSearchProjection({\n title: input.title,\n content: input.content,\n }),\n ...(input.layout ? { layout: input.layout } : {}),\n ...(input.tagIds ? { tags: { connect: input.tagIds.map((id) => ({ id: Number(id) })) } } : {}),\n },\n });\n },\n findNoteById: async (id) => {\n return models.note.findUnique({ where: { id } });\n },\n findPlaceholders: async (templates) => {\n if (templates.length === 0) {\n return [];\n }\n\n return models.placeholder.findMany({\n select: {\n template: true,\n replacement: true,\n },\n where: { template: { in: templates } },\n });\n },\n parseMarkdownToContentJson: markdownToBlocksJson,\n extractTagIds: extractTagIdsFromContentJson,\n captureBaseline: captureNoteBaseline,\n updateNote: async (id, input) => {\n const existingNote = await models.note.findUnique({\n where: { id },\n select: {\n title: true,\n content: true,\n },\n });\n\n if (!existingNote) {\n throw new Error('NOTE_NOT_FOUND');\n }\n\n const nextTitle = input.title ?? existingNote.title;\n const nextContent = input.content ?? existingNote.content;\n\n return models.note.update({\n where: { id },\n data: {\n title: input.title,\n content: input.content,\n layout: input.layout,\n ...buildNoteSearchProjection({\n title: nextTitle,\n content: nextContent,\n }),\n ...(input.tagIds ? { tags: { set: input.tagIds.map((tagId) => ({ id: Number(tagId) })) } } : {}),\n },\n });\n },\n});\n\nexport const createNoteFromMarkdown = async (input: CreateNoteAuthoringInput) => {\n return defaultNoteAuthoringService.createNote(input);\n};\n\nexport const updateNoteFromMarkdown = async (input: UpdateNoteAuthoringInput) => {\n return defaultNoteAuthoringService.updateNote(input);\n};\n"],"mappings":"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","names":[]}
@@ -1,12 +1,10 @@
1
- import models from "../models.js";
2
- import { trashNoteById as moveNoteToTrashById } from "./note-trash.js";
1
+ import models from "../../../models.js";
2
+ import { trashNoteById as moveNoteToTrashById } from "./trash.js";
3
3
  const DEFAULT_CLEANUP_KEYWORDS = ["temp", "temporary", "tmp", "draft", "test", "wip"];
4
4
  const normalizeCleanupKeywords = (keywords, query) => {
5
5
  const queryKeywords = typeof query === "string" ? query.split(/[,\s]+/).map((keyword) => keyword.trim()) : [];
6
6
  const source = keywords && keywords.length > 0 ? keywords : queryKeywords.length > 0 ? queryKeywords : DEFAULT_CLEANUP_KEYWORDS;
7
- return Array.from(new Set(
8
- source.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean)
9
- ));
7
+ return Array.from(new Set(source.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean)));
10
8
  };
11
9
  const buildForceReasons = (input) => {
12
10
  const forceReasons = [];
@@ -78,10 +76,7 @@ const createNoteCleanupService = (deps) => {
78
76
  orphanedTagNames: orphanedTags.map((tag) => tag.name),
79
77
  orphanedTagIds: orphanedTags.map((tag) => tag.id),
80
78
  requiresForce: candidate.requiresForce || orphanedTags.length > 0,
81
- forceReasons: [
82
- ...candidate.forceReasons,
83
- ...orphanedTags.length > 0 ? ["orphan_tags"] : []
84
- ]
79
+ forceReasons: [...candidate.forceReasons, ...orphanedTags.length > 0 ? ["orphan_tags"] : []]
85
80
  };
86
81
  };
87
82
  const getDeletePreview = async (id) => {
@@ -103,12 +98,14 @@ const createNoteCleanupService = (deps) => {
103
98
  deps.countCandidateNotes(keywords, olderThanDays),
104
99
  deps.findCandidateNotes(keywords, offset, limit, olderThanDays)
105
100
  ]);
106
- const candidates = await Promise.all(notes.map((note) => {
107
- const haystack = `${note.title}
101
+ const candidates = await Promise.all(
102
+ notes.map((note) => {
103
+ const haystack = `${note.title}
108
104
  ${note.content}`.toLowerCase();
109
- const matchedTerms = keywords.filter((keyword) => haystack.includes(keyword));
110
- return buildCandidate(note, matchedTerms);
111
- }));
105
+ const matchedTerms = keywords.filter((keyword) => haystack.includes(keyword));
106
+ return buildCandidate(note, matchedTerms);
107
+ })
108
+ );
112
109
  return {
113
110
  keywords,
114
111
  limit,
@@ -132,10 +129,7 @@ ${note.content}`.toLowerCase();
132
129
  };
133
130
  };
134
131
  const buildKeywordClauses = (keywords) => {
135
- return keywords.flatMap((keyword) => [
136
- { title: { contains: keyword } },
137
- { content: { contains: keyword } }
138
- ]);
132
+ return keywords.flatMap((keyword) => [{ title: { contains: keyword } }, { content: { contains: keyword } }]);
139
133
  };
140
134
  const buildStaleWhere = (olderThanDays) => {
141
135
  if (olderThanDays <= 0) {
@@ -164,10 +158,7 @@ const noteCleanupService = createNoteCleanupService({
164
158
  content: { contains: `reference","props":{"id":"${noteId}"` },
165
159
  NOT: { id: noteId }
166
160
  },
167
- orderBy: [
168
- { pinned: "desc" },
169
- { updatedAt: "desc" }
170
- ]
161
+ orderBy: [{ pinned: "desc" }, { updatedAt: "desc" }]
171
162
  }),
172
163
  findCandidateNotes: (keywords, skip, take, olderThanDays) => models.note.findMany({
173
164
  include: { tags: true },
@@ -204,4 +195,4 @@ export {
204
195
  listNoteCleanupCandidates,
205
196
  normalizeCleanupKeywords
206
197
  };
207
- //# sourceMappingURL=note-cleanup.js.map
198
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/services/cleanup.ts"],"sourcesContent":["import models from '~/models.js';\nimport { trashNoteById as moveNoteToTrashById } from './trash.js';\n\nexport interface NoteCleanupBackReference {\n id: string;\n title: string;\n}\n\nexport interface NoteCleanupPreview {\n id: string;\n title: string;\n updatedAt: string;\n pinned: boolean;\n tagNames: string[];\n reminderCount: number;\n backReferences: NoteCleanupBackReference[];\n orphanedTagNames: string[];\n requiresForce: boolean;\n forceReasons: string[];\n}\n\nexport interface NoteCleanupCandidate {\n id: string;\n title: string;\n updatedAt: string;\n pinned: boolean;\n tagNames: string[];\n reminderCount: number;\n backReferenceCount: number;\n matchedTerms: string[];\n reasons: string[];\n requiresForce: boolean;\n forceReasons: string[];\n}\n\nexport interface NoteCleanupCandidatesResult {\n keywords: string[];\n limit: number;\n offset: number;\n olderThanDays: number;\n notes: NoteCleanupCandidate[];\n totalCount: number;\n}\n\nexport interface NoteCleanupService {\n deleteNoteById: (id: number) => Promise<NoteCleanupPreview | null>;\n getDeletePreview: (id: number) => Promise<NoteCleanupPreview | null>;\n listCleanupCandidates: (input?: {\n keywords?: string[];\n limit?: number;\n offset?: number;\n olderThanDays?: number;\n query?: string;\n }) => Promise<NoteCleanupCandidatesResult>;\n}\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n updatedAt: Date;\n pinned: boolean;\n tags: Array<{\n id: number;\n name: string;\n }>;\n}\n\ninterface NoteCleanupPreviewInternal extends NoteCleanupPreview {\n orphanedTagIds: number[];\n}\n\nconst DEFAULT_CLEANUP_KEYWORDS = ['temp', 'temporary', 'tmp', 'draft', 'test', 'wip'];\n\nexport const normalizeCleanupKeywords = (keywords?: string[], query?: string) => {\n const queryKeywords = typeof query === 'string' ? query.split(/[,\\s]+/).map((keyword) => keyword.trim()) : [];\n const source =\n keywords && keywords.length > 0\n ? keywords\n : queryKeywords.length > 0\n ? queryKeywords\n : DEFAULT_CLEANUP_KEYWORDS;\n\n return Array.from(new Set(source.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean)));\n};\n\nconst buildForceReasons = (input: { pinned: boolean; reminderCount: number; backReferenceCount: number }) => {\n const forceReasons: string[] = [];\n\n if (input.pinned) {\n forceReasons.push('note_is_pinned');\n }\n\n if (input.reminderCount > 0) {\n forceReasons.push('has_reminders');\n }\n\n if (input.backReferenceCount > 0) {\n forceReasons.push('has_back_references');\n }\n\n return forceReasons;\n};\n\nexport const createNoteCleanupService = (deps: {\n countCandidateNotes: (keywords: string[], olderThanDays: number) => Promise<number>;\n countReminders: (noteId: number) => Promise<number>;\n deleteNoteAndPruneTags: (noteId: number, orphanTagIds: number[]) => Promise<void>;\n findBackReferences: (noteId: number) => Promise<Array<{ id: number; title: string }>>;\n findCandidateNotes: (\n keywords: string[],\n skip: number,\n take: number,\n olderThanDays: number,\n ) => Promise<NoteRecord[]>;\n findNote: (id: number) => Promise<NoteRecord | null>;\n getTagNoteCounts: (tagIds: number[]) => Promise<Map<number, number>>;\n}): NoteCleanupService => {\n const buildCandidate = async (note: NoteRecord, matchedTerms?: string[]): Promise<NoteCleanupCandidate> => {\n const [reminderCount, backReferences] = await Promise.all([\n deps.countReminders(note.id),\n deps.findBackReferences(note.id),\n ]);\n const normalizedMatchedTerms = matchedTerms ?? [];\n const forceReasons = buildForceReasons({\n pinned: note.pinned,\n reminderCount,\n backReferenceCount: backReferences.length,\n });\n\n return {\n id: String(note.id),\n title: note.title,\n updatedAt: note.updatedAt.toISOString(),\n pinned: note.pinned,\n tagNames: note.tags.map((tag) => tag.name),\n reminderCount,\n backReferenceCount: backReferences.length,\n matchedTerms: normalizedMatchedTerms,\n reasons: [\n ...(normalizedMatchedTerms.length > 0 ? [`matched_terms:${normalizedMatchedTerms.join(',')}`] : []),\n ...(note.pinned ? [] : ['not_pinned']),\n ...(note.tags.length === 0 ? ['tagless'] : []),\n ...(reminderCount === 0 ? ['no_reminders'] : []),\n ...(backReferences.length === 0 ? ['no_back_references'] : []),\n ],\n requiresForce: forceReasons.length > 0,\n forceReasons,\n };\n };\n\n const getDeletePreviewInternal = async (id: number): Promise<NoteCleanupPreviewInternal | null> => {\n const note = await deps.findNote(id);\n\n if (!note) {\n return null;\n }\n\n const [candidate, backReferences, tagNoteCounts] = await Promise.all([\n buildCandidate(note),\n deps.findBackReferences(note.id),\n deps.getTagNoteCounts(note.tags.map((tag) => tag.id)),\n ]);\n\n const orphanedTags = note.tags.filter((tag) => (tagNoteCounts.get(tag.id) ?? 0) <= 1);\n\n return {\n id: candidate.id,\n title: candidate.title,\n updatedAt: candidate.updatedAt,\n pinned: candidate.pinned,\n tagNames: candidate.tagNames,\n reminderCount: candidate.reminderCount,\n backReferences: backReferences.map((backReference) => ({\n id: String(backReference.id),\n title: backReference.title,\n })),\n orphanedTagNames: orphanedTags.map((tag) => tag.name),\n orphanedTagIds: orphanedTags.map((tag) => tag.id),\n requiresForce: candidate.requiresForce || orphanedTags.length > 0,\n forceReasons: [...candidate.forceReasons, ...(orphanedTags.length > 0 ? ['orphan_tags'] : [])],\n };\n };\n\n const getDeletePreview = async (id: number): Promise<NoteCleanupPreview | null> => {\n const preview = await getDeletePreviewInternal(id);\n\n if (!preview) {\n return null;\n }\n\n const { orphanedTagIds, ...publicPreview } = preview;\n void orphanedTagIds;\n return publicPreview;\n };\n\n return {\n listCleanupCandidates: async (input = {}) => {\n const limit = Math.max(1, Number(input.limit ?? 20));\n const offset = Math.max(0, Number(input.offset ?? 0));\n const olderThanDays = Math.max(0, Number(input.olderThanDays ?? 0));\n const keywords = normalizeCleanupKeywords(input.keywords, input.query);\n\n const [totalCount, notes] = await Promise.all([\n deps.countCandidateNotes(keywords, olderThanDays),\n deps.findCandidateNotes(keywords, offset, limit, olderThanDays),\n ]);\n\n const candidates = await Promise.all(\n notes.map((note) => {\n const haystack = `${note.title}\\n${note.content}`.toLowerCase();\n const matchedTerms = keywords.filter((keyword) => haystack.includes(keyword));\n return buildCandidate(note, matchedTerms);\n }),\n );\n\n return {\n keywords,\n limit,\n offset,\n olderThanDays,\n notes: candidates,\n totalCount,\n };\n },\n getDeletePreview,\n deleteNoteById: async (id: number) => {\n const preview = await getDeletePreviewInternal(id);\n\n if (!preview) {\n return null;\n }\n\n await deps.deleteNoteAndPruneTags(id, preview.orphanedTagIds);\n\n const { orphanedTagIds, ...publicPreview } = preview;\n void orphanedTagIds;\n return publicPreview;\n },\n };\n};\n\nconst buildKeywordClauses = (keywords: string[]) => {\n return keywords.flatMap((keyword) => [{ title: { contains: keyword } }, { content: { contains: keyword } }]);\n};\n\nconst buildStaleWhere = (olderThanDays: number) => {\n if (olderThanDays <= 0) {\n return {};\n }\n\n return { updatedAt: { lte: new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000) } };\n};\n\nconst noteCleanupService = createNoteCleanupService({\n countCandidateNotes: (keywords: string[], olderThanDays: number) =>\n models.note.count({\n where: {\n ...buildStaleWhere(olderThanDays),\n OR: buildKeywordClauses(keywords),\n },\n }),\n countReminders: (noteId: number) => models.reminder.count({ where: { noteId } }),\n deleteNoteAndPruneTags: async (noteId: number, orphanTagIds: number[]) => {\n void orphanTagIds;\n await moveNoteToTrashById(noteId);\n },\n findBackReferences: (noteId: number) =>\n models.note.findMany({\n select: {\n id: true,\n title: true,\n },\n where: {\n content: { contains: `reference\",\"props\":{\"id\":\"${noteId}\"` },\n NOT: { id: noteId },\n },\n orderBy: [{ pinned: 'desc' }, { updatedAt: 'desc' }],\n }),\n findCandidateNotes: (keywords: string[], skip: number, take: number, olderThanDays: number) =>\n models.note.findMany({\n include: { tags: true },\n orderBy: { updatedAt: 'asc' },\n skip,\n take,\n where: {\n ...buildStaleWhere(olderThanDays),\n OR: buildKeywordClauses(keywords),\n },\n }),\n findNote: (id: number) =>\n models.note.findUnique({\n where: { id },\n include: { tags: true },\n }),\n getTagNoteCounts: async (tagIds: number[]) => {\n if (tagIds.length === 0) {\n return new Map<number, number>();\n }\n\n const tags = await models.tag.findMany({\n where: { id: { in: tagIds } },\n include: { _count: { select: { notes: true } } },\n });\n\n return new Map(tags.map((tag) => [tag.id, tag._count.notes]));\n },\n});\n\nexport const listNoteCleanupCandidates = noteCleanupService.listCleanupCandidates;\nexport const getNoteCleanupPreview = noteCleanupService.getDeletePreview;\nexport const deleteNoteById = noteCleanupService.deleteNoteById;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,84 @@
1
+ import models from "../../../models.js";
2
+ import { buildNoteSearchProjection, NOTE_SEARCH_TEXT_SCHEMA_VERSION } from "./search.js";
3
+ const DEFAULT_BACKFILL_BATCH_SIZE = 100;
4
+ let activeNoteSearchBackfillPromise = null;
5
+ const yieldToEventLoop = async () => {
6
+ await new Promise((resolve) => {
7
+ setTimeout(resolve, 0);
8
+ });
9
+ };
10
+ const createNoteSearchBackfillService = (deps) => {
11
+ const backfillBatch = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {
12
+ const staleNotes = await deps.listStaleNotes(limit);
13
+ if (staleNotes.length === 0) {
14
+ return 0;
15
+ }
16
+ await deps.updateNotes(
17
+ staleNotes.map((note) => ({
18
+ id: note.id,
19
+ projection: buildNoteSearchProjection(note)
20
+ }))
21
+ );
22
+ return staleNotes.length;
23
+ };
24
+ const backfillAll = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {
25
+ let total = 0;
26
+ while (true) {
27
+ const count = await backfillBatch(limit);
28
+ if (!count) {
29
+ return total;
30
+ }
31
+ total += count;
32
+ if (count < limit) {
33
+ return total;
34
+ }
35
+ await yieldToEventLoop();
36
+ }
37
+ };
38
+ return {
39
+ backfillBatch,
40
+ backfillAll
41
+ };
42
+ };
43
+ const defaultNoteSearchBackfillService = createNoteSearchBackfillService({
44
+ listStaleNotes: async (limit) => {
45
+ return models.note.findMany({
46
+ where: { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },
47
+ orderBy: { id: "asc" },
48
+ take: limit,
49
+ select: {
50
+ id: true,
51
+ title: true,
52
+ content: true
53
+ }
54
+ });
55
+ },
56
+ updateNotes: async (updates) => {
57
+ await models.$transaction(
58
+ updates.map(
59
+ ({ id, projection }) => models.note.update({
60
+ where: { id },
61
+ data: projection
62
+ })
63
+ )
64
+ );
65
+ }
66
+ });
67
+ const backfillStaleNoteSearchText = async (limit) => {
68
+ return defaultNoteSearchBackfillService.backfillAll(limit);
69
+ };
70
+ const runNoteSearchBackfillInBackground = (limit) => {
71
+ if (activeNoteSearchBackfillPromise) {
72
+ return activeNoteSearchBackfillPromise;
73
+ }
74
+ activeNoteSearchBackfillPromise = defaultNoteSearchBackfillService.backfillAll(limit).finally(() => {
75
+ activeNoteSearchBackfillPromise = null;
76
+ });
77
+ return activeNoteSearchBackfillPromise;
78
+ };
79
+ export {
80
+ backfillStaleNoteSearchText,
81
+ createNoteSearchBackfillService,
82
+ runNoteSearchBackfillInBackground
83
+ };
84
+ //# sourceMappingURL=search-backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/services/search-backfill.ts"],"sourcesContent":["import models from '~/models.js';\nimport { buildNoteSearchProjection, NOTE_SEARCH_TEXT_SCHEMA_VERSION, type NoteSearchProjection } from './search.js';\n\ninterface BackfillCandidate {\n id: number;\n title: string;\n content: string;\n}\n\ninterface NoteSearchBackfillDeps {\n listStaleNotes: (limit: number) => Promise<BackfillCandidate[]>;\n updateNotes: (updates: Array<{ id: number; projection: NoteSearchProjection }>) => Promise<void>;\n}\n\nconst DEFAULT_BACKFILL_BATCH_SIZE = 100;\nlet activeNoteSearchBackfillPromise: Promise<number> | null = null;\n\nconst yieldToEventLoop = async () => {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 0);\n });\n};\n\nexport const createNoteSearchBackfillService = (deps: NoteSearchBackfillDeps) => {\n const backfillBatch = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {\n const staleNotes = await deps.listStaleNotes(limit);\n\n if (staleNotes.length === 0) {\n return 0;\n }\n\n await deps.updateNotes(\n staleNotes.map((note) => ({\n id: note.id,\n projection: buildNoteSearchProjection(note),\n })),\n );\n\n return staleNotes.length;\n };\n\n const backfillAll = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {\n let total = 0;\n\n while (true) {\n const count = await backfillBatch(limit);\n\n if (!count) {\n return total;\n }\n\n total += count;\n\n if (count < limit) {\n return total;\n }\n\n await yieldToEventLoop();\n }\n };\n\n return {\n backfillBatch,\n backfillAll,\n };\n};\n\nconst defaultNoteSearchBackfillService = createNoteSearchBackfillService({\n listStaleNotes: async (limit) => {\n return models.note.findMany({\n where: { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },\n orderBy: { id: 'asc' },\n take: limit,\n select: {\n id: true,\n title: true,\n content: true,\n },\n });\n },\n updateNotes: async (updates) => {\n await models.$transaction(\n updates.map(({ id, projection }) =>\n models.note.update({\n where: { id },\n data: projection,\n }),\n ),\n );\n },\n});\n\nexport const backfillStaleNoteSearchText = async (limit?: number) => {\n return defaultNoteSearchBackfillService.backfillAll(limit);\n};\n\nexport const runNoteSearchBackfillInBackground = (limit?: number) => {\n if (activeNoteSearchBackfillPromise) {\n return activeNoteSearchBackfillPromise;\n }\n\n activeNoteSearchBackfillPromise = defaultNoteSearchBackfillService.backfillAll(limit).finally(() => {\n activeNoteSearchBackfillPromise = null;\n });\n\n return activeNoteSearchBackfillPromise;\n};\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,SAAS,2BAA2B,uCAAkE;AAatG,MAAM,8BAA8B;AACpC,IAAI,kCAA0D;AAE9D,MAAM,mBAAmB,YAAY;AACjC,QAAM,IAAI,QAAc,CAAC,YAAY;AACjC,eAAW,SAAS,CAAC;AAAA,EACzB,CAAC;AACL;AAEO,MAAM,kCAAkC,CAAC,SAAiC;AAC7E,QAAM,gBAAgB,OAAO,QAAQ,gCAAgC;AACjE,UAAM,aAAa,MAAM,KAAK,eAAe,KAAK;AAElD,QAAI,WAAW,WAAW,GAAG;AACzB,aAAO;AAAA,IACX;AAEA,UAAM,KAAK;AAAA,MACP,WAAW,IAAI,CAAC,UAAU;AAAA,QACtB,IAAI,KAAK;AAAA,QACT,YAAY,0BAA0B,IAAI;AAAA,MAC9C,EAAE;AAAA,IACN;AAEA,WAAO,WAAW;AAAA,EACtB;AAEA,QAAM,cAAc,OAAO,QAAQ,gCAAgC;AAC/D,QAAI,QAAQ;AAEZ,WAAO,MAAM;AACT,YAAM,QAAQ,MAAM,cAAc,KAAK;AAEvC,UAAI,CAAC,OAAO;AACR,eAAO;AAAA,MACX;AAEA,eAAS;AAET,UAAI,QAAQ,OAAO;AACf,eAAO;AAAA,MACX;AAEA,YAAM,iBAAiB;AAAA,IAC3B;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;AAEA,MAAM,mCAAmC,gCAAgC;AAAA,EACrE,gBAAgB,OAAO,UAAU;AAC7B,WAAO,OAAO,KAAK,SAAS;AAAA,MACxB,OAAO,EAAE,uBAAuB,EAAE,KAAK,gCAAgC,EAAE;AAAA,MACzE,SAAS,EAAE,IAAI,MAAM;AAAA,MACrB,MAAM;AAAA,MACN,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,SAAS;AAAA,MACb;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,aAAa,OAAO,YAAY;AAC5B,UAAM,OAAO;AAAA,MACT,QAAQ;AAAA,QAAI,CAAC,EAAE,IAAI,WAAW,MAC1B,OAAO,KAAK,OAAO;AAAA,UACf,OAAO,EAAE,GAAG;AAAA,UACZ,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ,CAAC;AAEM,MAAM,8BAA8B,OAAO,UAAmB;AACjE,SAAO,iCAAiC,YAAY,KAAK;AAC7D;AAEO,MAAM,oCAAoC,CAAC,UAAmB;AACjE,MAAI,iCAAiC;AACjC,WAAO;AAAA,EACX;AAEA,oCAAkC,iCAAiC,YAAY,KAAK,EAAE,QAAQ,MAAM;AAChG,sCAAkC;AAAA,EACtC,CAAC;AAED,SAAO;AACX;","names":[]}